Telegram API error codes
Every error Telegram returns, what it actually means, and the fix. Covers Bot API and MTProto errors observed in production. Updated from real bot traffic logs.
FLOOD_WAIT_X
You're being rate-limited and must wait X seconds.
When: Too many requests in a short window — most commonly from sending messages, joining chats, or username lookups. The X in the error name is the seconds to wait.
Fix: Sleep for X seconds and retry. For sustained high volume, batch and throttle to under ~30 req/sec per user account. Bot API has different per-method limits — see core.telegram.org/bots/faq#how-can-i-message-all-of-my-bot-39s-subscribers-at-once.
PEER_ID_INVALID
The chat or user ID you passed doesn't exist or isn't accessible.
When: Calling a method with a chat_id the session has never interacted with, a deleted user, or a misformatted ID.
Fix: Make sure the entity is in your dialogs first (call get_dialogs() or get_entity(username)). For Bot API, the bot must have received at least one message from a user before it can DM them.
USERNAME_INVALID
The username doesn't match Telegram's format rules.
When: Username is shorter than 5 chars, longer than 32, contains non-alphanumeric chars other than underscore, starts with a digit, or ends with an underscore.
Fix: Validate against the regex /^[a-zA-Z][a-zA-Z0-9_]{3,30}[a-zA-Z0-9]$/ before sending.
USERNAME_NOT_OCCUPIED
Nobody owns this username — but you tried to look it up.
When: Calling resolveUsername on an available name. Often shows up in tgkit's username availability checker.
Fix: Treat as 'available' for availability checks. Otherwise, double-check spelling.
USERNAME_OCCUPIED
Trying to claim a username someone else already has.
When: Attempting to set @yourname via account.updateUsername.
Fix: Pick a different name. Recently-released usernames have a grace period before they can be claimed — try again in 2–3 weeks.
USERNAME_PURCHASE_AVAILABLE
The username is available, but only via Telegram's Fragment marketplace.
When: Premium / short / brand names are listed on Fragment instead of being free.
Fix: Visit fragment.com and bid, or pick a different name.
USER_NOT_PARTICIPANT
The user you're trying to kick/ban/restrict isn't actually in the chat.
When: Calling channels.editBanned or banChatMember on a user who already left, was already kicked, or was never a member.
Fix: Check membership with getChatMember first. Treat 'not in chat' as a successful no-op for most ban flows.
USER_ALREADY_PARTICIPANT
You're trying to add someone who's already in the chat.
When: Calling channels.inviteToChannel for a user who already joined.
Fix: Skip silently — the desired state is already achieved.
USER_PRIVACY_RESTRICTED
The user's privacy settings block this action.
When: Trying to add someone to a group/channel when their 'Who can add me to groups' privacy setting excludes you.
Fix: You can't override their privacy settings. Send them an invite link instead, or have them join the chat themselves.
CHAT_ADMIN_REQUIRED
The action requires admin privileges and you don't have them.
When: Deleting messages, banning users, editing channel info, etc. without the right admin rights.
Fix: Get promoted to admin with the specific permission you need (post_messages, delete_messages, ban_users, etc.).
CHANNEL_PRIVATE
The channel exists but you're not a member, or you've been banned.
When: Trying to read messages from or send to a channel where you have no access.
Fix: Join via an invite link. If you were banned, you can't rejoin without the admin unbanning you.
MESSAGE_NOT_MODIFIED
You tried to edit a message but the new content is identical to the old.
When: Calling editMessageText/editMessageReplyMarkup with the same text+markup as before.
Fix: Diff your new state against the current state before calling edit. Or catch and ignore — it's harmless.
MESSAGE_DELETE_FORBIDDEN
You can't delete that message.
When: Trying to delete a message older than 48h in a private chat (without 'delete for everyone'), or a message in a group/channel where you're not admin.
Fix: For private chats older than 48h, delete only for yourself. In groups/channels, you need delete_messages admin rights.
MESSAGE_TOO_LONG
Message body exceeds 4096 characters (or 1024 for media captions).
When: Stuffing too much text into a single sendMessage call.
Fix: Split into chunks under the limit, or attach the long text as a file with sendDocument.
ENTITY_BOUNDS_INVALID
An entity (bold/italic/link) in your formatted text points to an invalid offset.
When: Hand-constructing MessageEntity offsets that don't match UTF-16 character positions, or that overlap.
Fix: Let your client library compute entities from MarkdownV2/HTML instead of building them manually. Or validate that offset + length stays within the message text.
The user you're targeting has deleted their account.
When: Sending messages to or interacting with users who have closed their Telegram account.
Fix: Treat as a permanent failure — mark the user as unreachable in your DB and stop targeting them.
BUTTON_DATA_INVALID
callback_data exceeds 64 bytes or contains invalid UTF-8.
When: Building reply_markup with a callback_data longer than 64 bytes.
Fix: Use short callback keys (e.g. 'a:42') + a server-side lookup table. Use the tgkit inline keyboard builder to validate.
BUTTON_TYPE_INVALID
You used a button type that isn't allowed for the current keyboard.
When: Mixing inline-only button types (like login_url, switch_inline_query) into a ReplyKeyboardMarkup, or using pay buttons outside an invoice message.
Fix: Use ReplyKeyboardMarkup only for text/contact/location requests. Inline keyboards (InlineKeyboardMarkup) for everything else. pay buttons only on sendInvoice.
BOT_DOMAIN_INVALID
The bot can't open this URL — the domain isn't whitelisted.
When: Setting a web_app button or login_url to a domain you haven't configured with BotFather.
Fix: Message @BotFather → /mybots → pick your bot → Bot Settings → Domain → set the domain.
WEBHOOK_REQUIRE_HTTPS
You tried to set a webhook URL that isn't HTTPS.
When: Calling setWebhook with http:// (or with an invalid/expired TLS cert).
Fix: Use a public HTTPS URL with a valid certificate. For local dev, use a tunneling service like ngrok or cloudflared.
WEBHOOK_ALREADY_SET
A webhook is already configured — getUpdates won't work until it's removed.
When: Calling getUpdates while a webhook is active. Updates flow to the webhook, not to long polling.
Fix: Either delete the webhook (deleteWebhook), or read updates from the webhook instead of getUpdates.
TIMEOUT
The server didn't respond in time.
When: Long-running operations (large file uploads, channels with millions of members, etc.) on a flaky connection.
Fix: Retry with exponential backoff. For uploads, resume from the last successful part using file_part_id.
AUTH_KEY_UNREGISTERED
Your session is dead — Telegram has terminated it.
When: Reusing a session string after a user terminated all sessions from another device, or after Telegram banned/froze the account.
Fix: Re-authenticate from scratch (phone number + code + 2FA). The old session string is gone.
SESSION_PASSWORD_NEEDED
The account has 2FA enabled — you need to provide the password.
When: Logging in via auth.signIn on an account with cloud password protection.
Fix: Call auth.checkPassword with the SRP-hashed password. Most client libraries do this automatically — pass the cloud password to client.start() or equivalent.
PHONE_CODE_INVALID
The login code you submitted doesn't match.
When: Wrong code entered during phone-number login.
Fix: Make sure you're sending the code as a string of digits only (no spaces, no dashes). Re-request via resendCode if the code is stale.
PHONE_CODE_EXPIRED
The login code is no longer valid.
When: Waited too long between requesting the code and submitting it (~5 minutes window).
Fix: Request a new code with auth.resendCode or by calling auth.sendCode again.
FILE_REFERENCE_EXPIRED
The file reference you cached is too old.
When: Re-using a file_id or file_reference more than a few hours after first fetching it.
Fix: Refresh by re-fetching the message that contains the file (messages.getMessages), then read the new file_reference. Or upload the file again.
FILE_PART_INVALID
A chunk of a multi-part upload was malformed.
When: Wrong part size (must be 32KB-512KB, must be exactly the same for every part except the last), or wrong part order.
Fix: Use 512KB parts. Make sure parts are sent in order and the upload is finalized with upload.saveBigFilePart for files >10MB.
CHAT_WRITE_FORBIDDEN
You can't send messages to this chat.
When: Muted in a group, banned, slow-mode triggered, or sending to a channel where you aren't an admin.
Fix: Check restrictions with getChatMember. If you're a regular user in a channel, you can't post — only admins can.
Forbidden: bot was blocked by the user
The user blocked your bot.
When: Sending a message to a user who has blocked the bot.
Fix: Treat as a permanent failure for that user — mark them as unreachable. You cannot unblock yourself; they have to.
Forbidden: bot is not a member of the supergroup chat
The bot was removed from (or never added to) the chat.
When: Trying to send to a chat the bot isn't in.
Fix: Add the bot back via an invite from a chat admin. For new chats, the user adds the bot themselves.
Bad Request: chat not found
The chat_id you're using doesn't resolve.
When: Misformatted chat_id (e.g. -123 vs -1001230000000), or the user never DM'd the bot first.
Fix: Verify the chat_id format. For users, the bot needs to have received at least one message from them before it can initiate DMs.
PHONE_NUMBER_BANNED
Telegram has banned this phone number from creating/using accounts.
When: Spam reports, ToS violations, or registering too many accounts from one number.
Fix: Appeal at https://telegram.org/support — provide a justification. Most bans for legitimate users are reversible.
PHONE_NUMBER_FLOOD
Too many login codes requested for this phone number.
When: Spamming auth.sendCode for the same number.
Fix: Wait several hours. Avoid requesting a new code more than once per minute.
Don't see your error?
If you hit an error code that isn't here, drop it in an issue at hello@tgkit.io with the API method that triggered it. We add new codes whenever we observe them in production.
Reference docs: