Errors worth branching on
These six failures cover the message send (POST /api/v1/messages/telegram) and bot onboarding, hammered by every auto-response in the chat flow. Each block below is a full { error, meta } envelope as the API returns it, and the matrix at the bottom answers retry vs surface for the same six classes. For the platform-wide decision table these branches plug into, see the error handling guide.
401 — Unauthorized
401 on this page is the bearer key failing before the route ran — it never means the resource is wrong. Rotate the key or re-mint the scoped token; retrying the same request changes nothing.
403 — Forbidden
403 means the key authenticated but the operation is gated by scope — check the key’s scopes on the developer page; a 403 is never a data-not-found shape.
422 — Schema
422 means the payload did not match the request schema — branch on error.details.field and resend with the numeric chat id or the tenant-registered channel slug instead of blind-retrying the same body.
429 — Rate
Retry-After header and error.details.retry_after are both set on every 429 — resend the SAME request after the lower of the two.
402 — Feature gate
409 — Bot already claimed
409 fires on POST /api/v1/telegram/connect-bot when the BotFather token you pasted resolves to a bot another organization already claimed. Disconnect the bot on the other organization first; for a token leak, revoke the token in BotFather and follow the ownership-disputes recovery flow. Retrying the same connect just re-raises the 409.
60-second retry matrix
Worked request and response samples
The scanner flagged this page because the worked sections below shipped as bare<CodeGroup> blocks — no <RequestExample> wrappers for the samples index to count. Each it registers — send and state-read — now carries RequestExample over a six-language CodeGroup (cURL, Node SDK escape hatch, Python, Go, Ruby, PHP), paired with a ResponseExample success envelope. The errors matrix above is unchanged; the envelope it branches on is rendered here.
Send a message — POST /api/v1/messages/telegram
The copy-paste pair for the production send path.POST /api/v1/messages/telegram delivers to any numeric chat id or @channel slug your connected bot can reach — the recipient goes in to, the message text in body (both required) — unlike the onboarding POST /api/v1/telegram/test/send operation the generated block below documents (onboarding-only, no recipient parameter; see “Test send is onboarding-only” under this section).
202 means accepted, not delivered — the 200-response walkthrough of the onboarding test/send sample below explains WHEN the mode-aware test operation matters; production traffic uses the pair above.
Read the bot state — GET /api/v1/telegram/channel-state
TELEGRAM_BOT_TOKEN_REVOKED (402) is the state-read branch, TELEGRAM_CHANNEL_NOT_CONNECTED (409) is what the send raises when channel-state reports not_connected. Keep the lifecycle samples below ordered the same way.
Worked Telegram samples
The generated operations below carry the raw request/response schemas. This section walks the exact calls the dashboard and your integration make when they run the Telegram send/link flow: check channel state, send a message on the right mode, connect or disconnect your own bot, and watch the rollout cost. Each sample shows a curl request, a TypeScript fetch, and the 200 envelope the API returns. Four-hundred-series failures on these calls map to the branch list in the errors section above — resending is application-level when your handler replays a message (e.g. a chabot auto-response), so keep retries limited to429 here.
1. Check channel state
GET /api/v1/telegram/channel-state is the call you make first, every time — the response tells you which mode the next four samples run in (not_connected → verifying → test_active → production). Bot details (username, status, connected_at) are only present in production mode; test counters only on test states.
production envelope (the BYO bot block is the whole state):
200
test_active envelope drops the bot block and surfaces the verified chat + counters that gate /test/send:
200
verifying mid-flow state carries the single-use linking token (verify_token) the FE renders as a deep link / QR — the same token POST /test/start-verify mints below.
2. Send a test message
POST /api/v1/telegram/test/send fires one message on the right delivery path for the mode: in production it goes out your own bot to the chat you verified in step 1; in test_active it goes out the shared platform bot and bumps the daily cap. The 409 TELEGRAM_CHANNEL_NOT_CONNECTED from sample 1 (before a bot is connected) is the guard the operation itself raises.
sent / message_id / mode shape the send-schemas give:
200
3. Connect and disconnect your own bot
Bring-your-own-bot onboarding is two owner/admin/developer-gated calls:POST /api/v1/telegram/connect-bot accepts the BotFather token and mints the webhook; POST /api/v1/telegram/disconnect-bot revokes the webhook upstream and soft-deletes the row. The connected mode locks this page to production.
200
4. Watch the rollout cost
GET /api/v1/telegram/analytics/costs?days=30 (default window, clamp 1–90) returns the per-tenant Telegram cost rollup for the dashboard donut, so test-mode/street-traffic reads stay inside the same envelope discipline.
X-Orbit-Signature before trusting the body — the full per-language verification handler is at Verify webhook signatures.
Lifecycle send-receive-analytics samples
The four samples above exercise the onboarding flow. These five cover the day-two lifecycle — the calls your integration makes once the bot is inproduction mode: send, receive, measure, and monitor.
1. Send a text message
POST /api/v1/messages/telegram accepts a message for delivery through the connected bot. The recipient to is the chat id you verified during onboarding (a numeric id, or the @channel slug for channels). The API enqueues the send and returns the Devotel message id, so your handler can track the DLR without holding the HTTP window open.
202
202 here means the message is accepted, not yet delivered — the lifecycle flips to delivered (or failed with an error code) on the DLR webhook. If the connection is not in production mode the same call raises the 409 TELEGRAM_CHANNEL_NOT_CONNECTED above, so keep the launch gate on sample 4 (bot profile reads).
2. Send rich content with inline buttons
The same endpoint carries one media attachment per message viamedia_url. Inline-keyboard buttons and captions travel in metadata — keep button capacity to two rows and let the captions come through the text surface, which is how Telegram renders rich messages in DM chats:
status stays queued until the Bot API acknowledges the attachment). The same shape works for video/… and document/… attachments — swap the media type in the URL, keep caption under 1024 characters.
3. Inbound webhook handler — /start makes it a conversation
Customer replies arrive on the HTTPS endpoint you register on the webhooks page. The handler below verifies theX-Orbit-Signature HMAC before answering a /start — the pattern every inbound surface repeats:
200 promptly — the webhook surface re-delivers with backoff on non-2xx, so the reply send belongs behind an async queue the moment the handler grows past a trivial auto-answer. The same signature pattern is written out per SDK language at Verify webhook signatures.
4. Read channel analytics
GET /api/v1/analytics/telegram is the Telegram-only KPI surface the generic per-channel rollup can’t express — the Bot API has no carrier DLR, so the funnel counters you care about are outbound volume, the bot-blocked share (users who blocked the bot or closed the chat), custom-emoji usage, and the group-vs-DM split:
200
bot_blocked_rate — when it climbs past your baseline the bot is being blocked by users, and the fix is content-side (fewer messages, deeper opt-out), not transport-side. The response is cached for 60s, so dashboards can poll it per page-load without per-user cost.
5. Bot profile reads — health before the first send
The connect call returns the bot profile once;GET /api/v1/telegram/channel-state re-reads it on demand, which is the right pre-send check for any automated sender (an “is the bot still connected and active?” gate that catches a revoked token before the first send attempt of the run).
200
production-only bot block above is the single source of truth for connection health on this page.
Test send is onboarding-only
POST /api/v1/telegram/test/send (the “Send Telegram test message” operation generated below) is an onboarding-only endpoint: it delivers exactly one message to the chat you verified during onboarding and takes no recipient or chat_id parameter — the only required body field is text (1–1024 characters). For production delivery to any chat id or @channel slug your bot can reach, use POST /api/v1/messages/telegram — worked sample in “Send a message” at the top of this page.
The success envelope the send returns (the same shape the worked send section shows):
200
POST /api/v1/telegram/connect-bot (required body bot_token, the BotFather token) returns the connected-bot profile along with the webhook URL — returned once at connect time, and re-readable any time from GET /api/v1/telegram/channel-state:
200
GET /api/v1/telegram/analytics/costs rollup that feeds the dashboard donut (per-tenant cost estimate; Telegram currently bills at $0):
200