Every Telegram bot developer eventually hits a 429 Too Many Requests error. It usually happens when you try to broadcast a message to all your users, or when your bot gets popular enough that natural traffic exceeds the limits. Understanding what those limits are — and planning for them from day one — saves you from broken bots and frustrated users.
Telegram enforces rate limits at three levels. All of them apply simultaneously.
| Scope | Limit | What it means |
|---|---|---|
| Per individual chat | ~1 message/second | You can't spam a single user faster than 1 msg/sec. Short bursts are tolerated, but sustained traffic will get 429 errors. |
| Per group/channel | 20 messages/minute | Bots in groups can send at most 20 messages per minute to the same group. This includes all message types. |
| Global (all chats) | 30 messages/second | Your bot cannot send more than 30 messages per second across all chats combined. This is the broadcasting ceiling. |
sendMessage. Calls to editMessageText, answerCallbackQuery, sendPhoto, deleteMessage, etc. all count. A bot that sends a message, edits it, and answers a callback is consuming 3 API calls for a single user interaction. Lightweight methods like answerCallbackQuery rarely trigger limits on their own, but they add up during traffic spikes. Plan your rate budget accordingly.
Outgoing API calls (your bot sending messages, editing, deleting, answering queries) count toward the rate limits. Incoming updates — whether received via getUpdates (polling) or webhooks — do not count toward the 30 msg/sec budget. This is a common misconception. When using polling, set the timeout parameter to 30 seconds or more — this keeps one long-lived connection open and only returns when there's an update, avoiding unnecessary requests.
Telegram returns an HTTP 429 Too Many Requests response with a retry_after field telling you how many seconds to wait before trying again. If you keep hitting the limit without respecting retry_after, the cooldown period increases progressively — from a few seconds to tens of seconds. Your bot is never permanently blocked, but sustained violations make the cooldowns longer and your bot effectively unusable.
Here's what a 429 response looks like:
{
"ok": false,
"error_code": 429,
"description": "Too Many Requests: retry after 5",
"parameters": {"retry_after": 5}
}
Your bot sends a welcome message and responds to commands. At 500 daily active users, each sending 3–5 messages, you're well under all limits. No queuing needed — just respond to each message normally.
You want to send a daily digest to all users. At 30 messages/second theoretical maximum, broadcasting to 5,000 users takes 3–4 minutes in practice (accounting for network round-trips and occasional retries). This is manageable, but you need a message queue. Sending 5,000 API calls in a tight loop without delays will trigger 429 errors within seconds.
At this scale, a single broadcast takes 30+ minutes at the free tier's 30 msg/sec. You need a robust queue with per-chat rate limiting, retry logic, and ideally a background worker process. Consider Paid Broadcasts if the delay is unacceptable for your use case.
The key to staying within limits is controlling the send rate with a token-bucket approach. The aiolimiter library provides a clean async rate limiter. Combined with per-chat locks, this respects both the global and per-chat limits:
# pip install aiolimiter python-telegram-bot
import asyncio
from aiolimiter import AsyncLimiter
from collections import defaultdict
from telegram.error import RetryAfter
# 25/sec global (leave headroom below the 30/sec hard limit)
global_limiter = AsyncLimiter(25, 1)
# Per-chat: 1 message/sec
chat_limiters = defaultdict(lambda: AsyncLimiter(1, 1))
async def safe_send(bot, chat_id, text, **kwargs):
async with global_limiter:
async with chat_limiters[chat_id]:
try:
return await bot.send_message(chat_id, text, **kwargs)
except RetryAfter as e:
await asyncio.sleep(e.retry_after)
return await bot.send_message(chat_id, text, **kwargs)
# Broadcast in batches to avoid flooding memory with coroutines
async def broadcast(bot, user_ids, text, batch_size=100):
for i in range(0, len(user_ids), batch_size):
batch = user_ids[i:i + batch_size]
tasks = [safe_send(bot, uid, text) for uid in batch]
await asyncio.gather(*tasks, return_exceptions=True)
The queuing strategy above handles the happy path, but network timeouts and transient errors still happen. Wrap your rate-limited sends with retry logic for production resilience:
import asyncio
from telegram.error import RetryAfter, TimedOut
async def send_with_retry(bot, chat_id, text, max_retries=3):
for attempt in range(max_retries):
try:
return await bot.send_message(chat_id, text)
except RetryAfter as e:
# Telegram tells us exactly how long to wait
await asyncio.sleep(e.retry_after)
except TimedOut:
# Network timeout — exponential backoff
await asyncio.sleep(2 ** attempt)
raise Exception(f"Failed to send after {max_retries} retries")
In production, combine both patterns: use safe_send from the queuing section for rate limiting, and wrap it with send_with_retry for resilience against transient failures. The rate limiter prevents 429 errors proactively; the retry logic handles the ones that slip through.
Log every 429 error with the chat_id and timestamp. If you see them clustered around a specific time, your broadcast schedule needs spreading. If they come from a specific group, that group is hitting the 20/min limit.
Track your messages-per-second metric. If you're consistently above 20 msg/sec during peaks, you're one traffic spike away from hitting the wall. Add queuing before it becomes a problem.
A simple counter that resets every second is enough for most bots. Log a warning when you cross 20 msg/sec so you have early signal before users start seeing delays.
Once your rate limiting is solid, the next challenge is usually message formatting. See our complete guide to MarkdownV2 escape characters to avoid the other most common Telegram bot error.
Check if your bot will hit rate limits before it happens. Enter your users, messages/day, and groups to see where you stand.
Try the Rate Limit Calculator →