Errors
Every refusal has a real HTTP status, a stable string code, and a request id you can quote to support.
Shape#
HTTP/1.1 403 Forbidden
X-Request-Id: 01JB2H9M4T7Q2V8X5C1N6R3K0W
{"ok": false,
"error": {"code": "forbidden_right", "message": "The bot lacks the right to do this here",
"retry_after": null, "parameter": "can_send"},
"request_id": "01JB2H9M4T7Q2V8X5C1N6R3K0W"}
code: program against this. Codes are stable; new ones may be added, so handle unknown codes by their HTTP status.message: for people reading logs. It may change; never parse it.parameter: the parameter at fault (bad_request), the missing right (forbidden_right), ornull.retry_after: seconds to wait (with429, and some503s); also sent as theRetry-Afterheader.request_idand theX-Request-Idheader: a ULID on every API response, successful or not. Quote it when you contact support; never send your token.
Answers from the edge in front of the API keep the same ok/error shape but have no request_id: a 429 from the edge limits, and {"ok":false,"error":{"code":"not_found"}} for any path outside /v1/. Always use https://: the API is not served over plain HTTP, and a request sent over plain HTTP has already exposed its token — revoke it.
Codes#
| HTTP | code | Meaning | What to do |
|---|---|---|---|
400 | bad_request | A parameter is missing or invalid. parameter names it (also method when a POST-only method was called with GET, and Idempotency-Key for a malformed key). | Fix the request. Do not retry unchanged. |
400 | token_in_url | The token appeared in the URL path or query string. That token has been revoked and the owner was told by QivBot. | Reveal a new token in the app and send it only in the Authorization header. |
400 | bad_key | setMyEncryptionKey: a key, the proof of possession or its time t is not valid, the proof was already used, or the X25519 key belongs to another bot. parameter is x25519, ed25519, pop or t. | Generate a fresh key pair and sign pop with the current time. |
400 | webhooks_disabled | setWebhook: webhooks are not offered on this platform yet. Nothing was checked or saved. Not temporary: retrying gives the same answer. | Receive updates with getUpdates (long polling). The changelog will say when webhooks open. |
401 | unauthorized | No Authorization: Bot <token> header, a malformed one, or an unknown token. | Check the header. Repeated failures from one address are rate limited before the token is even checked. |
401 | token_revoked | The token is known but revoked or past its 10-minute overlap window (a newer token was revealed, the tokens were revoked, the bot changed owners). | Use the current token. Never retry with the old one. |
403 | bot_suspended | The bot is suspended, banned or deleted. | Stop calling. The owner was told why in QivBot; appeals go to support. |
403 | bot_restricted | The bot is restricted by the Qivuna team: read methods work, writes are refused. | Keep reading; writes resume when the restriction is lifted. |
403 | forbidden_right | The bot's grant in this room lacks a right. parameter names it (can_send, can_post, can_send_media, can_delete_messages, …). Also returned when a group allows only admins to send. | Ask a room admin to grant the right. Do not retry. |
403 | user_not_started | A 1:1 send to a person who never pressed Start. | Wait for the person to start the bot (a start link helps). |
403 | user_blocked_bot | The person stopped or blocked the bot. | Stop sending to this person until they start the bot again. |
403 | unsolicited_limit | The person has not written to the bot or pressed its button in 24 h and the bot used today's unsolicited messages for them (3 / 10 / 50 by tier). | Send only when asked, or wait for the next UTC day. |
403 | e2ee_required | Plaintext into an end-to-end encrypted context: an encrypted bot's private chat, an encrypted group the bot is attached to, or a plaintext answer to an encrypted button press. | Send an envelope instead (the SDKs do this). |
403 | e2ee_unsupported | Not possible where the room is end-to-end encrypted: moderation, media and member lists in an encrypted group, and every call through a grant that was suspended when the group became encrypted (except getChat and leaveChat). | Nothing to retry. An admin can re-add the bot in encrypted (addressed) mode. |
403 | reply_window_closed | reply_token missing, expired (1 h), used up (5 sends) or voided (key change, detach, the person left the group). | Answer the next request instead; a reply needs a fresh token. |
403 | target_protected | Moderation against the group's creator, an owner or admin, or another bot (for deletes: a bot's message). | Not allowed for bots. Do not retry. |
404 | not_found | Unknown method, a method that is not available yet (for example the encryption-key methods while encrypted bots are off), or the bot platform is switched off. | Check the method name; check the changelog. |
404 | chat_not_found | The chat id is unknown, the bot is not in that room, cannot see it, the person is outside the rollout, or it never started the bot. One answer for all of these, on purpose. | Do not retry the same id. |
404 | message_not_found | Unknown message, a message of another chat, a deleted one, an end-to-end encrypted one, or an event line. | Do not retry the same id. |
404 | user_not_found | The user_id does not decode for this bot, is a bot, or is not (or no longer) in the group. | Do not retry the same id. |
404 | callback_not_found | The callback query is unknown, older than 15 minutes, already answered, or not this bot's. | Answer presses promptly, once. |
404 | file_not_found | getFile or a download: an unknown file_id, one no longer visible to this bot, or an expired download link. | Call getFile again while the file is still visible to the bot. |
409 | conflict | getUpdates while a webhook is set, or while another getUpdates of this bot runs; or a second setWebhook while one is running. message says which. | Run one poller per bot, or call deleteWebhook first. |
409 | idempotency_mismatch | The Idempotency-Key was used in the last hour with a different request. | Use a new key for a new request. |
409 | idempotency_in_progress | A request with the same Idempotency-Key is still running. | Retry shortly with the same key. |
409 | key_not_active | An encrypted send, edit or answer (or setWebhook with encrypt_payload) while the bot has no active key. | Propose a key and have the owner confirm it in the app. |
410 | room_closed | The room was closed by moderation. | Stop writing to it. |
413 | payload_too_large | A JSON body over 1 MB, a file over 20 MB, form fields of an upload over 1 MB, or a file sent to a method other than the three send methods. | Send less. Do not retry unchanged. |
422 | message_too_old | Editing a message older than 48 h, or deleting someone else's message older than 48 h. | Send a new message instead. |
422 | invalid_markup | The inline keyboard breaks a limit or has an unknown field. parameter is reply_markup. | Fix the keyboard (see Keyboards). |
422 | invalid_entities | Entities outside the text or overlapping, an unknown type, a non-https link, or HTML with an unsupported tag. | Fix the formatting (see Text and formatting). |
422 | content_blocked | The text, a button, a caption, a description or a moderation reason matched the platform's anti-phishing rules. The answer never says which rule. Each block adds a strike to the owner (at most one a day). | Do not ask people for codes, passwords or recovery phrases, and do not link to look-alikes of Qivuna. |
422 | content_not_allowed | setMyDescription or setMyShortDescription: the text has words that cannot be published (the platform's word filter for public texts). parameter names the field. No strike. | Change the text. |
422 | webhook_unverified | The ownership challenge failed. message says why without the response body: the status, “without the challenge”, or “could not be reached”. | Answer the challenge (see Webhooks). |
429 | rate_limited | A rate limit. retry_after (seconds) and a Retry-After header say when to try again. | Wait retry_after seconds, then retry. |
500 | internal | An unexpected error on our side. The request_id identifies it in our logs. | Retry reads. Retry writes only with the same Idempotency-Key. |
503 | platform_paused | The bot platform is paused (the global kill switch). Every method answers this; nothing is queued while paused. | Back off and retry later; updates created while paused are not kept. |
503 | webhook_unavailable | setWebhook: webhooks are not available right now (no egress, or too many ownership challenges in flight; then retry_after is 5). | Use getUpdates, or retry later. |
Handling errors#
- Retry
429afterretry_after;409 idempotency_in_progressshortly;500and lost connections — reads freely, writes only with the sameIdempotency-Key;503with growing back-off. - Do not retry other 4xx unchanged. In particular,
401 token_revokedand403 bot_suspendedwill not fix themselves. - Stop sending to a person after
403 user_blocked_bot, and to a room after410 room_closedor losing it inmy_chat_member. - The 404 answers (
chat_not_found,message_not_found,user_not_found) deliberately do not say whether something does not exist or is not visible to your bot.