How to read a Telegram Bot API error before you look it up
Telegram's HTTP status codes are coarse - almost everything is a 400 - but the description string is precise and documented. Read the description, not the code, and you will usually know the fix before you finish the sentence.
Last verified: 2026-08-20 against the Telegram Bot API. Error strings quoted below are the literal responses the API returns.
A failed call always has the same shape:
{
"ok": false,
"error_code": 429,
"description": "Too Many Requests: retry after 34",
"parameters": {"retry_after": 34}
}Three fields matter. error_code mirrors the HTTP status. description is the real error - a stable, documented English string, usually prefixed with the status name (Bad Request:, Forbidden:, Conflict:). And the optional parameters object carries machine-readable follow-ups: retry_after (seconds to wait) and migrate_to_chat_id (the group became a supergroup; here is its new ID). Code that logs only the status code throws away everything useful.
The five families, by symptom:
| Code | Family | Typical symptom |
|---|---|---|
| 409 | Update-delivery conflicts | Bot is completely or intermittently silent |
| 400 | Bad Request - your payload | Specific sends fail; the rest work |
| 403 | Forbidden - the recipient | Sends to certain users or chats fail permanently |
| 429 | Flood control | Worked in testing, drops messages at volume |
| 401 / 404 | Token problems | Every single call fails |
This guide walks each family code-first. If your bot is silent and you do not have an error string yet, start instead with our symptom-first companion, Telegram bot not responding - and for any literal string, the Telegram error directory has a dedicated page per error.
409 Conflict: the errors that make a bot silent
Telegram permits exactly one consumer of updates per bot token - you either poll with getUpdates or receive pushes at a webhook, never both, and never two pollers. Both violations return 409, and both silence your bot while every other part of the system looks healthy.
{"ok":false,"error_code":409,"description":"Conflict: can't use getUpdates method while webhook is active; use deleteWebhook to delete the webhook first"}A webhook is registered against the token and your code is polling. Many libraries swallow this error and retry forever, so the process runs, the logs look fine, and nothing ever arrives. Fix: deleteWebhook?drop_pending_updates=true, then restart polling - or stop polling if the webhook was intentional. Full handling on the webhook-active 409 page.
{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}Two processes are polling the same token, and each message goes to whichever holds the connection - the classic bot that answers roughly half the time. Hunt for a dev process left running, a container scaled to two replicas, or an old deploy that never died. Polling does not scale horizontally; if you need more than one instance, use webhooks behind a load balancer. Details on the terminated-by-other-getUpdates page.
Both 409s are configuration states, not transient faults: retrying without changing anything reproduces them forever. The one-command diagnostic is getWebhookInfo - an empty url means polling mode, a populated one means webhook mode, and a URL you do not recognise means a stale deploy is stealing your updates.
400 Bad Request: sends that fail because of the message itself
The 400 family is the largest, and it splits by what you got wrong. First, the message content errors - these are deterministic, so the bot works for most messages and dies on specific ones.
{"ok":false,"error_code":400,"description":"Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 42"}You set parse_mode and the text contains characters the parser reads as markup. MarkdownV2 requires escaping _ * [ ] ( ) ~ ` > # + - = | { } . ! even in prose - a sentence ending in a full stop can fail. Escape interpolated user content, or switch to HTML parse mode, which needs only &, <, >. The byte offset in the description points at the exact culprit; the can't parse entities page has escaping tables for both modes.
Bad Request: message is too long
Bad Request: message text is emptyText messages cap at 4,096 characters - AI-generated replies overrun this constantly, so split on a paragraph boundary before sending (message is too long). The empty variant usually means an interpolation produced "" or a template variable never resolved (message text is empty).
Then the addressing errors:
Bad Request: chat not found
Bad Request: user not found
Bad Request: chat_id is emptychat not found means the chat_id does not correspond to a chat your bot can reach: a username where a numeric ID is needed, a dropped negative sign (group IDs are negative, supergroups start with -100), a chat ID truncated by a 32-bit integer column, or a group the bot was removed from. A user ID your bot has never seen in an update gives user not found - Telegram bots cannot look up arbitrary users; they can only address chats they have met.
400 Bad Request: editing, deleting, and replying to messages
A second cluster fires when you manipulate existing messages. Each description names its own rule:
| Literal description | The rule you hit |
|---|---|
| message is not modified | Your edit produced identical content - common in loops that re-render unchanged state. Compare before editing. |
| message can't be edited | Too old, or not sent by your bot. Bots can only edit their own messages, within Telegram's edit window. |
| message to delete not found | Already deleted, or the ID belongs to a different chat. Message IDs are per-chat, not global. |
| message to be replied not found | The reply_to_message_id points at a deleted or foreign message. Send without the reply parameter as a fallback. |
| query is too old and response timeout expired or query ID is invalid | You answered a callback query too late. answerCallbackQuery must be called promptly - answer first, process after, or the user's button spins forever. |
The callback-query row deserves emphasis because its symptom is so misleading: users report "the buttons stopped working", but the bot is receiving every tap - it is just answering after the query expired. The same acknowledge-first pattern that keeps webhooks healthy applies here: answer the callback immediately, then do the slow work.
400 Bad Request: group permissions and the supergroup migration
Group-related 400s look like bugs and are actually group administration:
Bad Request: have no rights to send a message
Bad Request: not enough rights to send text messages to the chat
Bad Request: CHAT_WRITE_FORBIDDENAll three mean the bot is in the chat but muted: restricted by an admin, stripped of send permissions, or blocked from writing in that channel or topic. The fix is in the group's member settings, not in code - promote the bot or lift the restriction (have no rights to send a message, CHAT_WRITE_FORBIDDEN).
The one that breaks bots permanently:
{"ok":false,"error_code":400,"description":"Bad Request: group chat was upgraded to a supergroup chat","parameters":{"migrate_to_chat_id":-1001234567890}}When a group upgrades to a supergroup its chat ID changes forever, and the old ID never works again. Telegram hands you the new ID in parameters.migrate_to_chat_id - store it the moment you see it, and also handle the migrate_to_chat_id field that arrives in updates when the migration happens. Bots that ignore this silently lose the group; the supergroup migration page has the handler pattern.
400 Bad Request: webhook registration refused
These fire at setWebhook time, which is good news - the failure is loud and immediate instead of silent:
Bad Request: bad webhook: HTTPS url must be provided for webhook
Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443
Bad Request: bad webhook: failed to resolve hostThe rules behind them: webhooks are HTTPS-only with no development exception; only four ports are allowed, so the dev server on port 3000 can never receive a webhook (the ports 80, 88, 443, 8443 page covers the workarounds - a reverse proxy or a tunnel); and the hostname must resolve on the public internet, so localhost and internal DNS names are rejected (failed to resolve host).
Once registration succeeds, failures move to delivery time and stop being errors you receive - they become entries in getWebhookInfo's last_error_message field, which is where you look when a registered webhook goes quiet. That debugging path - certificates, timeouts, the acknowledge-then-process pattern - is shared across every platform, and our webhook debugging guide walks it in one pass. The mechanics of what a webhook actually is, if the term is new, take two minutes to absorb.
400 Bad Request: files, photos, and buttons
Media and keyboard errors round out the 400 family:
| Literal description | Cause and fix |
|---|---|
| wrong file identifier/HTTP URL specified | The file_id is malformed, from a different bot (file_ids are bot-scoped), or the URL is not a plain HTTPS file link. |
| file is too big | The cloud Bot API caps downloads at 20MB and uploads at 50MB. A self-hosted Bot API server raises both to roughly 2000MB. |
| PHOTO_INVALID_DIMENSIONS | Width/height ratio or size outside Telegram's photo rules - send extreme aspect ratios as a document instead. |
| failed to get HTTP URL content | Telegram's servers could not fetch the URL you passed - dead link, auth-gated file, or a host blocking Telegram's fetchers. Host media somewhere publicly reachable. |
| BUTTON_URL_INVALID / BUTTON_DATA_INVALID | An inline keyboard button carries a malformed URL, or callback_data over the 64-byte cap. Use short opaque tokens in callback_data and look the payload up server-side. |
The 64-byte callback_data cap is the one that surprises people: serializing a JSON state object into the button works in testing and breaks the moment the state grows. Store state server-side, keyed by a short ID.
403 Forbidden: the recipient is the problem, and retrying is pointless
Every 403 means the same thing structurally: your request was valid, your token is fine, and the recipient is unreachable as a matter of policy. None of them are fixed by retrying - the correct response is always to update your database and stop sending.
{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}The user blocked your bot. There is no API to check block status in advance - the send attempt is the check. Mark the chat inactive; bots that keep retrying blocked users burn their own rate limit (bot was blocked by the user).
Forbidden: bot can't initiate conversation with a userThe rule every notification system meets on day one: a Telegram bot can never message someone first. The user must send /start or tap a deep link like https://t.me/YourBotName?start=user_12345 before you may send anything. A phone number or user ID from another system grants nothing (can't initiate conversation).
The rest of the family, by literal string:
Forbidden: bot was kicked from the group chat- removed by an admin; delete the stored chat and stop.Forbidden: user is deactivated- the account was deleted or deactivated; permanently unreachable.Forbidden: bot is not a member of the channel chat- to post to a channel the bot must be added as an administrator, not merely know the channel's ID.Forbidden: bot can't send messages to bots- bots cannot talk to each other, by design. If two of your systems need to communicate, use your own backend, not Telegram.
A well-behaved sender treats any 403 as a permanent state transition for that chat. Log it once, flag the record, move on.
429 Too Many Requests: flood control and retry_after
The bot that answered everything in testing and drops messages at launch is meeting flood control:
{"ok":false,"error_code":429,"description":"Too Many Requests: retry after 34","parameters":{"retry_after":34}}Telegram's published guidance for bots: roughly 30 messages per second overall, about 1 message per second sustained to an individual chat, and around 20 messages per minute to a single group. The full set of ceilings lives on the Telegram limits page.
The contract when you exceed one: honour retry_after exactly. Retrying sooner extends the penalty, and sustained abuse can get a token limited far longer than the number suggests. The wrong fix is a sleep sprinkled into the send loop; the right fix is a token-bucket queue that paces broadcasts below 30 per second and spaces per-chat sends - the same architecture every platform ends up needing, as our chatbot API rate limiting guide covers cross-platform. Back-off rules and code patterns are on the 429 reference page, and the general concept on the rate limiting glossary page.
A subtler 429 trigger affects conversational bots: sending three quick bubbles to feel human - greeting, question, menu - can trip the per-chat limit for one user. Insert a short delay between bubbles; it also reads more naturally.
401 Unauthorized and 404 Not Found: the token itself
When every call fails, stop debugging payloads - the token is broken in one of two ways:
{"ok":false,"error_code":401,"description":"Unauthorized"}
{"ok":false,"error_code":404,"description":"Not Found"}401 Unauthorized means the token has a valid shape but does not authenticate: revoked via BotFather (regenerating a token instantly kills the old one), or corrupted in transit into your environment - a trailing newline from a secrets manager, quote characters included in the value, or a truncated paste. 404 Not Found usually means the token is malformed enough that the URL path itself is wrong - a missing bot prefix (/bot<TOKEN>/getMe), a typo'd method name, or an empty token variable producing /bot/getMe.
Verify the token in isolation before touching anything else: open https://api.telegram.org/bot<TOKEN>/getMe in a browser, or use our Telegram bot token checker, which runs the same call from the browser and explains the response. A healthy token returns the bot's username and ID. If getMe succeeds locally but production returns 401, the token is fine and the deployed environment variable is not.
One trap with no error at all: a valid but wrong token - the staging bot's token deployed to production. Both tokens pass getMe, sends succeed, and users report silence because the replies are going out through a bot nobody is looking at. When logs show success and users see nothing, confirm which bot the token belongs to.
502 and 504: when the failure is not yours
Two gateway errors round out the list, and they matter mostly because of what you should not do about them:
502 Bad Gateway- the Bot API server itself is having trouble, or a proxy between you and it is. Transient; retry with back-off.504 Gateway Timeout- the request did not complete in time. Also transient, but if it correlates with your own long-polling timeout settings, tunegetUpdates'timeoutparameter rather than hammering.
The rule for 5xx is the inverse of the 403 rule: these are the only errors in this guide where blind retrying is correct, provided it backs off. Everything else in this guide is deterministic - a 400 will fail identically on retry, a 403 is a policy state, a 409 is configuration. A retry queue that cannot tell these classes apart will hammer permanent errors and give up on transient ones; classify on error_code first, then on the description string.
Distinguish carefully between a 502 from Telegram and a 502 in your webhook's last_error_message - the second means Telegram called your server and your server answered 502, which is your app crashing, not Telegram's. getWebhookInfo tells you which side you are on.
Avoiding this layer altogether
Every error family above is transport plumbing: update-consumer exclusivity, escaping rules, per-chat pacing, token hygiene, migration handling. None of it is your product, and all of it recurs on every platform under different names - Meta's error codes, Slack's, LINE's. Handling it well is genuinely necessary if you operate the Bot API directly, and genuinely undifferentiating work if what you actually want is a bot that answers customers.
A managed platform absorbs this layer. With Conferbot's Telegram integration, you connect a bot token and build the conversation in a visual flow editor - webhook lifecycle, retry classification, rate-limit pacing, and message formatting are handled for you, and the same flow deploys to WhatsApp, LINE, and a website widget without re-implementing each platform's error table.
Start free with Conferbot - no credit card required, so you can compare a managed flow against your hand-rolled handler this afternoon.
If you are staying hands-on: bookmark the Telegram error directory for the string-level pages, keep the limits page next to your queue configuration, and when the bot is silent with no error in sight, work through the symptom-first guide - between the two, every failure in this family tree has a page.
Was this article helpful?
Build and deploy in 10 minutes. No coding needed.
Telegram Bot API Error Codes FAQ
Everything you need to know about chatbots for telegram bot api error codes.
About the Author
The Conferbot team writes about building, deploying, and improving AI chatbots.
View all articlesRelated Articles
From the reference shelf
Fact-checked reference pages and free tools for the platform this article covers.