Guide

Retries and errors

How the API tells you something went wrong, and how your bot should respond.

The error envelope

Every failure answers { ok: false, error } with a matching HTTP status:

StatusMeaningWhat to do
400Bad inputFix the request; retrying unchanged will fail the same way
401Bad or missing tokenCheck the token; it may have been rotated
403ForbiddenBot blocked, suspended, deleted, or wrong environment
404Unknown idThe bot, chat, or message does not exist (or is not yours)
429Rate limitedBack off and retry; see below
403 on sendMessage most often means one of three things: the user blocked your bot, the bot was suspended after abuse reports, or you used a test token against a live chat (or vice versa). The error message tells you which.

Retrying 429s

Back off exponentially with jitter: wait 1s, then 2s, 4s, 8s, up to a minute. Never retry a 400, and never retry in a tight loop; hammering a 429 turns a brief slowdown into a long one.

Python
import random, time
import requests

def call(method, payload, tries=5):
    for i in range(tries):
        r = requests.post(f"{BASE}/{method}", headers=H, json=payload)
        if r.status_code != 429:
            return r.json()
        time.sleep(min(60, 2 ** i) + random.random())
    raise RuntimeError("rate limited too long")

Polling without gaps or duplicates

Updates are an ordered per-bot sequence. Process a batch, then set offset to the highest update_id + 1 before the next call. If your bot crashes between processing and confirming, you will see the batch again, so make your handlers idempotent (key side effects on update_id).