{"openapi":"3.1.0","info":{"title":"TeleTraff Public API","description":"TeleTraff public API. Authenticate with `Authorization: Bearer tg_sk_live_...` (Settings -> API). Every error is `{\"error\": {code, message, hint, docs_url, details}}`. Sending tempo is set only by `pace`; when omitted it is `careful`.","version":"1.0.0"},"paths":{"/me":{"get":{"summary":"Describe this token, its limits and the allowed pace presets","description":"Call this first. It tells what the token may do, how many requests it may make, and the exact limits of every `pace` value.","operationId":"getMe","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}}}},"/messages":{"post":{"summary":"Send a message from one of your accounts","description":"Queue one Telegram message from one of your accounts. Give `to.username` for first contact or `to.conversation_id` to write into an existing conversation of the same account.\n\nA **cold** message (first contact, or the account wrote last) is spaced from the account's previous cold message by the `pace` preset and counted against its hourly and daily caps; `scheduled_at` says when it will go out. A **reply** (the other person wrote last) goes out within seconds and is not counted. `pace` defaults to `careful`; raw delays are refused.\n\nSend a unique `Idempotency-Key` per message and reuse it only to retry: a retry with the same body returns the same message, a different body is `409 idempotency_conflict`. `dry_run: true` writes nothing and answers 200 with what would happen. Poll GET /messages/{id} for the outcome.","operationId":"sendMessage","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Unique key per message, 8-128 characters (a UUID works). Required unless `dry_run` is true.","examples":["7f9c2a5e-3b1d-4f0a-9c8e-1d2b3c4d5e6f"],"title":"Idempotency-Key"},"description":"Unique key per message, 8-128 characters (a UUID works). Required unless `dry_run` is true."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageRequest"}}}},"responses":{"202":{"description":"Accepted and queued (or the earlier answer to the same Idempotency-Key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"200":{"description":"`dry_run: true`: nothing was written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DryRunResponse"}}}},"400":{"description":"`idempotency_key_required` (missing or not 8-128 characters) or `invalid_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`account_not_found` or `conversation_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"409":{"description":"`idempotency_conflict`: the key was used with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`, `invalid_target`, `invalid_pace`, or `account_not_eligible` with `details.reason` one of spamblock, cold_hold, frozen, banned, blocked, quarantine, session_invalid, no_proxy, api_hold.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"429":{"description":"`account_queue_full` (500 queued messages on this account) or `rate_limited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/messages/{message_id}":{"get":{"summary":"Read one message and its outcome","description":"The current state of a message sent with POST /messages. `sent` and `failed` are final; a failed message carries `error.code`.","operationId":"getMessage","parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","description":"Message id returned by POST /messages.","examples":["msg_1052"],"title":"Message Id"},"description":"Message id returned by POST /messages."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"description":"`invalid_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`message_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/accounts":{"get":{"summary":"List your Telegram accounts and what each may send now","description":"Accounts of this workspace, oldest first, with whether each may write first (`can_send_cold`) or reply (`can_reply`), why not (`blocked_reason`), and its cold messages of the last hour and day. Use an `id` as `account_id` in POST /messages. Follow `next_cursor` until it is null.","operationId":"listAccounts","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size, 1-100.","examples":[50],"default":50,"title":"Limit"},"description":"Page size, 1-100."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` of the previous page; omit for the first page.","examples":["acc_17"],"title":"Cursor"},"description":"`next_cursor` of the previous page; omit for the first page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountListResponse"}}}},"400":{"description":"`invalid_cursor`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`: `limit` is not 1-100.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/accounts/{account_id}":{"patch":{"summary":"Turn listening for incoming replies on or off for one account","description":"With `api_listen: true` TeleTraff keeps this account connected so a reply reaches you within seconds instead of when someone opens the panel. Needs a bound proxy, a working session and an account that is not banned or taken out of work (a frozen one is fine). Returns the account as GET /accounts lists it.","operationId":"updateAccount","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","description":"Account id from GET /accounts.","examples":["acc_17"],"title":"Account Id"},"description":"Account id from GET /accounts."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAccountRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountInfo"}}}},"400":{"description":"`invalid_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`account_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`account_not_eligible` (`details.reason`: `no_proxy`, `session_invalid`, `blocked`, `quarantine`) or `invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/conversations":{"get":{"summary":"List private conversations of your accounts","description":"Private (one-to-one) conversations mirrored for your accounts, most recent message first. With `since`, only conversations that had a message after it -- poll this with the time of your last sync to find where new replies are. Follow `next_cursor` until it is null.","operationId":"listConversations","parameters":[{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only conversations of this account (`acc_...`); omit for every account.","examples":["acc_17"],"title":"Account Id"},"description":"Only conversations of this account (`acc_...`); omit for every account."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO 8601 time; only conversations whose newest message is later. Without a zone it is UTC.","examples":["2026-10-07T14:00:00Z"],"title":"Since"},"description":"ISO 8601 time; only conversations whose newest message is later. Without a zone it is UTC."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size, 1-100.","examples":[50],"default":50,"title":"Limit"},"description":"Page size, 1-100."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` of the previous page (with the same filters); omit for the first page.","examples":["MjAyNi0xMC0wN1QxNDowNDo1OHwzODE"],"title":"Cursor"},"description":"`next_cursor` of the previous page (with the same filters); omit for the first page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListResponse"}}}},"400":{"description":"`invalid_id` or `invalid_cursor`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`account_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`: `limit` is not 1-100 or `since` is not an ISO 8601 time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/conversations/{conversation_id}/messages":{"get":{"summary":"Read the messages of one conversation","description":"Without `since`: newest first, for showing the history. With `since`: every message sent after it, oldest first -- the same messages webhooks deliver, for catching up after a missed or failed delivery. `actor` says who wrote each one; `message_id` links a message sent with POST /messages.","operationId":"listConversationMessages","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"Conversation id from GET /conversations.","examples":["conv_381"],"title":"Conversation Id"},"description":"Conversation id from GET /conversations."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO 8601 time; only messages sent after it, oldest first. Without a zone it is UTC.","examples":["2026-10-07T14:00:00Z"],"title":"Since"},"description":"ISO 8601 time; only messages sent after it, oldest first. Without a zone it is UTC."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size, 1-100.","examples":[50],"default":50,"title":"Limit"},"description":"Page size, 1-100."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` of the previous page (with the same `since`); omit for the first page.","examples":["MjAyNi0xMC0wN1QxNDowNDo1OHw0MDIx"],"title":"Cursor"},"description":"`next_cursor` of the previous page (with the same `since`); omit for the first page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationMessageListResponse"}}}},"400":{"description":"`invalid_id` or `invalid_cursor`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`conversation_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`: `limit` is not 1-100 or `since` is not an ISO 8601 time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/webhooks":{"get":{"summary":"List your webhooks","description":"Every webhook of this workspace, oldest first. The signing secret is never listed.","operationId":"listWebhooks","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}}}},"post":{"summary":"Register an HTTPS endpoint for events","description":"Registers an endpoint (at most 5 per workspace) and returns its signing `secret` -- the only time the secret is shown. Each event is POSTed as JSON `{id, type, created_at, data}` with headers `X-TeleTraff-Event`, `X-TeleTraff-Delivery`, `X-TeleTraff-Timestamp` and `X-TeleTraff-Signature: sha256=<hex HMAC-SHA256(secret, \"<timestamp>.<raw body>\")>`. Answer 2xx within 10 seconds; otherwise it is retried after 30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, 8 h and 8 h, then marked `failed`. Delivery is at least once: deduplicate by `X-TeleTraff-Delivery` or the event `id`. Redirects are not followed. Event types: `message.received` (someone wrote to one of your accounts), `message.sent` and `message.failed` (outcome of a message from the API, a Dialogs AI reply or the panel), `account.limited` (the API holds an account after PeerFlood or a block) and `webhook.test` (sent only by POST /webhooks/{webhook_id}/test).","operationId":"createWebhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhook"}}}},"409":{"description":"`webhook_limit_reached`: the workspace already has 5 webhooks.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_webhook_url` (not https, or a localhost/private/link-local host) or `invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/webhooks/{webhook_id}":{"delete":{"summary":"Delete a webhook","description":"Stops deliveries to this endpoint at once and removes its delivery log.","operationId":"deleteWebhook","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"Webhook id from GET /webhooks.","examples":["wh_3"],"title":"Webhook Id"},"description":"Webhook id from GET /webhooks."}],"responses":{"204":{"description":"Successful Response"},"400":{"description":"`invalid_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`webhook_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/webhooks/{webhook_id}/test":{"post":{"summary":"Send a test event to one webhook","description":"Queues one `webhook.test` event to this webhook only (no other webhook receives it); it goes out within seconds, signed like every other event. `data` is `{webhook_id, message}`.","operationId":"sendTestEvent","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"Webhook id from GET /webhooks.","examples":["wh_3"],"title":"Webhook Id"},"description":"Webhook id from GET /webhooks."}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryAccepted"}}}},"400":{"description":"`invalid_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`webhook_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/webhooks/{webhook_id}/deliveries":{"get":{"summary":"Read the delivery log of a webhook","description":"Deliveries to this webhook, newest first, 50 per page by default, with the last status code and error.","operationId":"listWebhookDeliveries","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"Webhook id from GET /webhooks.","examples":["wh_3"],"title":"Webhook Id"},"description":"Webhook id from GET /webhooks."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size, 1-100.","examples":[50],"default":50,"title":"Limit"},"description":"Page size, 1-100."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` of the previous page; omit for the newest page.","examples":["whd_932"],"title":"Cursor"},"description":"`next_cursor` of the previous page; omit for the newest page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryListResponse"}}}},"400":{"description":"`invalid_id` or `invalid_cursor`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`webhook_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`: `limit` is not 1-100.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}},"/webhooks/{webhook_id}/deliveries/{delivery_id}/replay":{"post":{"summary":"Send a delivery again","description":"Puts the delivery back on the schedule from scratch (state `pending`, attempts reset) with the same event body and `X-TeleTraff-Delivery` id. Refused while it is being sent.","operationId":"replayWebhookDelivery","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","description":"Webhook id from GET /webhooks.","examples":["wh_3"],"title":"Webhook Id"},"description":"Webhook id from GET /webhooks."},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","description":"Delivery id from the delivery log.","examples":["whd_981"],"title":"Delivery Id"},"description":"Delivery id from the delivery log."}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryAccepted"}}}},"400":{"description":"`invalid_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"404":{"description":"`webhook_not_found` or `delivery_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"409":{"description":"`delivery_in_flight`: the delivery is being sent right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}},"422":{"description":"`invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicErrorResponse"}}}}}}}},"components":{"schemas":{"AccountInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Account id to pass as `account_id`.","examples":["acc_17"]},"display_name":{"type":"string","title":"Display Name","description":"Name of the account in the panel.","examples":["Anna, sales"]},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"The account's own Telegram username, if it has one.","examples":["anna_sales"]},"can_send_cold":{"type":"boolean","title":"Can Send Cold","description":"Whether the account may write first (cold message) right now.","examples":[true]},"can_reply":{"type":"boolean","title":"Can Reply","description":"Whether the account may reply where the other person wrote last. A spamblocked account can often still reply.","examples":[true]},"blocked_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blocked Reason","description":"Why the account cannot send cold messages; null when it can. One of `spamblock`, `cold_hold`, `frozen`, `banned`, `blocked`, `quarantine`, `proxy_failed`, `session_invalid`, `no_proxy`, `api_hold`.","examples":[null]},"proxy_bound":{"type":"boolean","title":"Proxy Bound","description":"Whether a proxy is bound; an account without one cannot send.","examples":[true]},"api_hold_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Api Hold Until","description":"Until when the API holds this account after PeerFlood or a block (UTC); null when not held.","examples":[null]},"cold_sent_last_hour":{"type":"integer","title":"Cold Sent Last Hour","description":"Cold messages this account sent through the API in the last hour.","examples":[1]},"cold_sent_last_day":{"type":"integer","title":"Cold Sent Last Day","description":"Cold messages this account sent through the API in the last 24 hours.","examples":[6]},"api_listen":{"type":"boolean","title":"Api Listen","description":"Whether TeleTraff keeps listening on this account so incoming replies arrive within seconds (switch with PATCH /accounts/{account_id}). A flagged account that loses its proxy or session, or is banned or taken out of work, keeps the flag but is not listened to until that is fixed.","examples":[false]}},"type":"object","required":["id","display_name","username","can_send_cold","can_reply","blocked_reason","proxy_bound","api_hold_until","cold_sent_last_hour","cold_sent_last_day","api_listen"],"title":"AccountInfo"},"AccountListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AccountInfo"},"type":"array","title":"Data","description":"Accounts of this page, oldest first.","examples":[[{"api_listen":false,"can_reply":true,"can_send_cold":true,"cold_sent_last_day":6,"cold_sent_last_hour":1,"display_name":"Anna, sales","id":"acc_17","proxy_bound":true,"username":"anna_sales"}]]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get the next page; null on the last page.","examples":["acc_17"]}},"type":"object","required":["data","next_cursor"],"title":"AccountListResponse"},"ConversationInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Conversation id; use it as `to.conversation_id` in POST /messages.","examples":["conv_381"]},"account_id":{"type":"string","title":"Account Id","description":"Account this conversation belongs to.","examples":["acc_17"]},"peer_id":{"type":"string","title":"Peer Id","description":"The other person as Telegram knows them, `user:<id>`; same as `from.peer_id` in `message.received`.","examples":["user:123456789"]},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Their Telegram username without '@', when known.","examples":["lead_name"]},"title":{"type":"string","title":"Title","description":"Their display name, or `@username`, or `peer_id` when nothing else is known.","examples":["Maria K."]},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At","description":"When the newest message of the conversation was sent (UTC); null when unknown.","examples":["2026-10-07T14:04:58Z"]},"last_message_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Preview","description":"Start of the newest message, or a word for an attachment.","examples":["Yes, interested"]},"last_direction":{"anyOf":[{"type":"string","enum":["incoming","outgoing"]},{"type":"null"}],"title":"Last Direction","description":"Who wrote last: `incoming` (they did) or `outgoing` (your account); null when unknown.","examples":["incoming"]},"unread_count":{"type":"integer","title":"Unread Count","description":"Incoming messages the account has not read.","examples":[1]}},"type":"object","required":["id","account_id","peer_id","username","title","last_message_at","last_message_preview","last_direction","unread_count"],"title":"ConversationInfo"},"ConversationListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ConversationInfo"},"type":"array","title":"Data","description":"Conversations of this page, most recent message first.","examples":[[{"account_id":"acc_17","id":"conv_381","last_direction":"incoming","last_message_at":"2026-10-07T14:04:58Z","last_message_preview":"Yes, interested","peer_id":"user:123456789","title":"Maria K.","unread_count":1,"username":"lead_name"}]]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get the next page; null on the last page.","examples":["MjAyNi0xMC0wN1QxNDowNDo1OHwzODE"]}},"type":"object","required":["data","next_cursor"],"title":"ConversationListResponse"},"ConversationMessage":{"properties":{"telegram_message_id":{"type":"integer","title":"Telegram Message Id","description":"Telegram's id of the message; same as `telegram_message_id` in webhook events.","examples":[4021]},"direction":{"type":"string","enum":["incoming","outgoing"],"title":"Direction","description":"`incoming` (they wrote) or `outgoing` (your account wrote).","examples":["incoming"]},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Message text; null for an attachment without a caption.","examples":["Yes, interested"]},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At","description":"When the message was sent (UTC).","examples":["2026-10-07T14:04:58Z"]},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id","description":"Id of the API message (`msg_...`) when it was sent with POST /messages; null otherwise.","examples":[null]},"actor":{"type":"string","enum":["api","ai","operator","contact"],"title":"Actor","description":"Who wrote it: `api` (POST /messages), `ai` (a Dialogs AI reply), `operator` (anyone else writing from your account: the panel, a mailing, a phone) or `contact` (the other person).","examples":["contact"]}},"type":"object","required":["telegram_message_id","direction","text","sent_at","message_id","actor"],"title":"ConversationMessage"},"ConversationMessageListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ConversationMessage"},"type":"array","title":"Data","description":"Messages of this page: oldest first with `since`, newest first without it.","examples":[[{"actor":"contact","direction":"incoming","sent_at":"2026-10-07T14:04:58Z","telegram_message_id":4021,"text":"Yes, interested"}]]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` (with the same `since`) to get the next page; null on the last page.","examples":["MjAyNi0xMC0wN1QxNDowNDo1OHw0MDIx"]}},"type":"object","required":["data","next_cursor"],"title":"ConversationMessageListResponse"},"CreateWebhookRequest":{"properties":{"url":{"type":"string","maxLength":2048,"minLength":1,"title":"Url","description":"HTTPS endpoint that receives a POST per event. Must be reachable on the public internet: http, localhost, private and link-local addresses are refused.","examples":["https://crm.example.com/teletraff/webhook"]},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description","description":"Your own note about this endpoint, up to 255 characters.","examples":["Production CRM"]}},"additionalProperties":false,"type":"object","required":["url"],"title":"CreateWebhookRequest"},"CreatedWebhook":{"properties":{"id":{"type":"string","title":"Id","description":"Webhook id.","examples":["wh_3"]},"url":{"type":"string","title":"Url","description":"Endpoint events are POSTed to.","examples":["https://crm.example.com/teletraff/webhook"]},"enabled":{"type":"boolean","title":"Enabled","description":"Whether events are delivered to this endpoint.","examples":[true]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your note about this endpoint.","examples":["Production CRM"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the webhook was created (UTC).","examples":["2026-10-07T12:30:02Z"]},"secret":{"type":"string","title":"Secret","description":"Signing secret, shown only in this response; store it now. Every delivery carries `X-TeleTraff-Signature: sha256=<hex HMAC-SHA256(secret, \"<X-TeleTraff-Timestamp>.<raw body>\")>`.","examples":["whsec_8VQf3m1yJqzK0cT9pWb2xR7nLd4sHa6uEo5iYg1kMvc"]}},"type":"object","required":["id","url","enabled","description","created_at","secret"],"title":"CreatedWebhook"},"DeliveryAccepted":{"properties":{"delivery_id":{"type":"string","title":"Delivery Id","description":"Delivery that will be attempted within seconds; follow it in GET /webhooks/{webhook_id}/deliveries.","examples":["whd_981"]}},"type":"object","required":["delivery_id"],"title":"DeliveryAccepted"},"DeliveryInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Delivery id; also sent as the `X-TeleTraff-Delivery` header.","examples":["whd_981"]},"event_id":{"type":"string","title":"Event Id","description":"Event id; also the `id` of the delivered JSON body.","examples":["evt_5512"]},"event_type":{"type":"string","title":"Event Type","description":"Event types: `message.received` (someone wrote to one of your accounts), `message.sent` and `message.failed` (outcome of a message from the API, a Dialogs AI reply or the panel), `account.limited` (the API holds an account after PeerFlood or a block) and `webhook.test` (sent only by POST /webhooks/{webhook_id}/test).","examples":["message.received"]},"state":{"type":"string","enum":["pending","claimed","sending","retry","delivered","failed"],"title":"State","description":"`pending` (not tried yet), `claimed` / `sending` (being sent now), `retry` (failed, tried again at `next_attempt_at`), `delivered` (your endpoint answered 2xx within 10 s) or `failed` (10 attempts over about 23.7 hours failed, or the endpoint was refused).","examples":["delivered"]},"attempt_count":{"type":"integer","title":"Attempt Count","description":"Attempts made so far.","examples":[1]},"last_status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Status Code","description":"HTTP status of the last attempt; null when there was no answer.","examples":[200]},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Why the last attempt failed, e.g. `http_500`, `timeout`, `connection_failed`, `tls_failed`, `redirect_not_followed`, `url_blocked`, `webhook_disabled`; null after a success.","examples":[null]},"next_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Attempt At","description":"When the next attempt is due (UTC); null once delivered or failed.","examples":[null]},"delivered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Delivered At","description":"When your endpoint accepted it (UTC); null until delivered.","examples":["2026-10-07T12:30:03Z"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the delivery was created (UTC).","examples":["2026-10-07T12:30:02Z"]}},"type":"object","required":["id","event_id","event_type","state","attempt_count","last_status_code","last_error","next_attempt_at","delivered_at","created_at"],"title":"DeliveryInfo"},"DeliveryListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/DeliveryInfo"},"type":"array","title":"Data","description":"Deliveries to this webhook, newest first.","examples":[[{"attempt_count":1,"created_at":"2026-10-07T12:30:02Z","delivered_at":"2026-10-07T12:30:03Z","event_id":"evt_5512","event_type":"message.received","id":"whd_981","last_status_code":200,"state":"delivered"}]]},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass as `cursor` to get older deliveries; null on the last page.","examples":["whd_932"]}},"type":"object","required":["data","next_cursor"],"title":"DeliveryListResponse"},"DryRunResponse":{"properties":{"would_status":{"type":"string","const":"queued","title":"Would Status","description":"The status the message would get. A message that would be refused gets the same error a real send gets.","examples":["queued"]},"kind":{"type":"string","enum":["cold","reply"],"title":"Kind","description":"`cold` when this is first contact (no conversation yet, or the account wrote last): paced and counted against the cold caps. `reply` when the other person wrote last: sent within seconds and not counted.","examples":["cold"]},"pace":{"type":"string","enum":["careful","balanced","fast"],"title":"Pace","description":"Sending tempo preset. Optional; the default is `careful`. `careful`, `balanced` and `fast` differ in the pause between two cold messages from one account and in the hourly and daily cold caps (see GET /me). Raw delays or caps are not accepted.","examples":["careful"]},"scheduled_at":{"type":"string","format":"date-time","title":"Scheduled At","description":"When the message would go out (UTC).","examples":["2026-10-07T12:31:40Z"]},"cold_remaining_hour":{"type":"integer","title":"Cold Remaining Hour","description":"Cold messages this account could still schedule in the current rolling hour, counting this one as sent.","examples":[4]},"cold_remaining_day":{"type":"integer","title":"Cold Remaining Day","description":"Cold messages this account could still schedule in the current rolling day, counting this one as sent.","examples":[19]}},"type":"object","required":["would_status","kind","pace","scheduled_at","cold_remaining_hour","cold_remaining_day"],"title":"DryRunResponse"},"MeResponse":{"properties":{"api_version":{"type":"string","const":"v1","title":"Api Version","description":"Version of this API.","examples":["v1"]},"workspace":{"type":"string","title":"Workspace","description":"Workspace the token belongs to.","examples":["default"]},"token":{"$ref":"#/components/schemas/TokenInfo","description":"The token making this request and its scopes."},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo","description":"How many requests this token may make per window."},"default_pace":{"type":"string","enum":["careful","balanced","fast"],"title":"Default Pace","description":"Pace used when a request does not pass `pace`. Always `careful`.","examples":["careful"]},"pace_presets":{"items":{"$ref":"#/components/schemas/PacePresetInfo"},"type":"array","title":"Pace Presets","description":"Every allowed `pace` value with its exact limits. Replies into a conversation where the other person wrote last do not count against these caps.","examples":[[{"cold_per_account_day":20,"cold_per_account_hour":5,"delay_seconds_max":113,"delay_seconds_min":90,"name":"careful"}]]},"accounts_total":{"type":"integer","title":"Accounts Total","description":"Telegram accounts in this workspace; GET /accounts lists them.","examples":[12]},"listening_accounts":{"type":"integer","title":"Listening Accounts","description":"Accounts with `api_listen` on that are eligible to listen now (proxy bound, session works, not banned or taken out of work); at most 8 are connected at once per console runner process, the rest wait for a slot.","examples":[2]}},"type":"object","required":["api_version","workspace","token","rate_limit","default_pace","pace_presets","accounts_total","listening_accounts"],"title":"MeResponse"},"MessageError":{"properties":{"code":{"type":"string","title":"Code","description":"Why the message was not sent: `peer_flood`, `account_blocked`, `username_not_found`, `privacy_restricted`, `write_forbidden`, `recipient_deactivated`, `flood_wait_exhausted`, `expired`, `delivery_unknown`, `cancelled`, `account_limited`, `account_unavailable`, `account_busy` or `send_failed`.","examples":["privacy_restricted"]},"message":{"type":"string","title":"Message","description":"What happened, in English.","examples":["The recipient's privacy settings do not allow this account to write to them."]},"details":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Details","description":"`console_code`: the panel's own word for how the send ended (e.g. `proxy-unavailable`); when it is `telegram-retry-exhausted`, `last_retry_cause` names what the retries were waiting on. Quote them to support; they are not a stable vocabulary, branch on `code`.","examples":[{"console_code":"telegram-privacy-restricted"}]}},"type":"object","required":["code","message"],"title":"MessageError"},"MessageResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Message id; poll it with GET /messages/{id}.","examples":["msg_1052"]},"status":{"type":"string","enum":["queued","sending","sent","failed"],"title":"Status","description":"`queued` (waiting for its scheduled time), `sending`, `sent` or `failed` (see `error`). `sent` and `failed` are final.","examples":["queued"]},"kind":{"type":"string","enum":["cold","reply"],"title":"Kind","description":"`cold` when this is first contact (no conversation yet, or the account wrote last): paced and counted against the cold caps. `reply` when the other person wrote last: sent within seconds and not counted.","examples":["cold"]},"account_id":{"type":"string","title":"Account Id","description":"Account the message is sent from.","examples":["acc_17"]},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Conversation the message belongs to; null for a cold message until it is sent.","examples":["conv_381"]},"to_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To Username","description":"Recipient username, when known.","examples":["durov"]},"pace":{"type":"string","enum":["careful","balanced","fast"],"title":"Pace","description":"Sending tempo preset. Optional; the default is `careful`. `careful`, `balanced` and `fast` differ in the pause between two cold messages from one account and in the hourly and daily cold caps (see GET /me). Raw delays or caps are not accepted.","examples":["careful"]},"scheduled_at":{"type":"string","format":"date-time","title":"Scheduled At","description":"When the message is due to go out (UTC). Cold messages are spaced by the pace preset.","examples":["2026-10-07T12:31:40Z"]},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At","description":"When Telegram accepted the message (UTC); null until sent.","examples":[null]},"error":{"anyOf":[{"$ref":"#/components/schemas/MessageError"},{"type":"null"}],"description":"Set only when `status` is `failed`.","examples":[null]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the request was accepted (UTC).","examples":["2026-10-07T12:30:02Z"]}},"type":"object","required":["id","status","kind","account_id","conversation_id","to_username","pace","scheduled_at","sent_at","error","created_at"],"title":"MessageResponse"},"MessageTarget":{"properties":{"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Telegram username to write to, without '@' (5-32 characters: a letter, then letters, digits or underscores). Give this or `conversation_id`, not both.","examples":["durov"]},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Id of a conversation of this same account (`conv_...`). Give this or `username`, not both.","examples":["conv_381"]}},"additionalProperties":false,"type":"object","title":"MessageTarget"},"PacePresetInfo":{"properties":{"name":{"type":"string","enum":["careful","balanced","fast"],"title":"Name","description":"Value to pass as `pace`.","examples":["careful"]},"delay_seconds_min":{"type":"number","title":"Delay Seconds Min","description":"Shortest pause between two cold messages from one account.","examples":[90]},"delay_seconds_max":{"type":"number","title":"Delay Seconds Max","description":"Longest pause between two cold messages from one account.","examples":[113]},"cold_per_account_hour":{"type":"integer","title":"Cold Per Account Hour","description":"Cold messages one account may send per hour.","examples":[5]},"cold_per_account_day":{"type":"integer","title":"Cold Per Account Day","description":"Cold messages one account may send per day.","examples":[20]}},"type":"object","required":["name","delay_seconds_min","delay_seconds_max","cold_per_account_hour","cold_per_account_day"],"title":"PacePresetInfo"},"PublicErrorDetail":{"properties":{"code":{"type":"string","title":"Code","description":"Stable machine-readable error code.","examples":["account_not_eligible"]},"message":{"type":"string","title":"Message","description":"What went wrong, in English.","examples":["This account cannot send a cold message right now (spamblock)."]},"hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hint","description":"What to do next.","examples":["This account may not write first while spamblocked; it can still reply."]},"docs_url":{"type":"string","title":"Docs Url","description":"Documentation of this code.","examples":["https://teletraff.com/en/docs/api#account_not_eligible"]},"details":{"additionalProperties":true,"type":"object","title":"Details","description":"Machine-readable specifics, e.g. `reason`.","examples":[{"reason":"spamblock"}]}},"type":"object","required":["code","message","hint","docs_url","details"],"title":"PublicErrorDetail"},"PublicErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/PublicErrorDetail","description":"Every refusal of this API has this one shape."}},"type":"object","required":["error"],"title":"PublicErrorResponse"},"RateLimitInfo":{"properties":{"limit":{"type":"integer","title":"Limit","description":"Requests allowed per window for this token.","examples":[60]},"window_seconds":{"type":"integer","title":"Window Seconds","description":"Window length in seconds.","examples":[60]}},"type":"object","required":["limit","window_seconds"],"title":"RateLimitInfo"},"SendMessageRequest":{"properties":{"account_id":{"type":"string","title":"Account Id","description":"Account to send from (`acc_...`, see GET /accounts).","examples":["acc_17"]},"to":{"$ref":"#/components/schemas/MessageTarget","description":"Recipient: exactly one of `username` or `conversation_id`.","examples":[{"username":"durov"},{"conversation_id":"conv_381"}]},"text":{"type":"string","maxLength":4096,"minLength":1,"title":"Text","description":"Message text, 1-4096 characters, not blank.","examples":["Hi! Saw your post about the meetup, are you still looking for speakers?"]},"pace":{"type":"string","enum":["careful","balanced","fast"],"title":"Pace","description":"Sending tempo preset. Optional; the default is `careful`. `careful`, `balanced` and `fast` differ in the pause between two cold messages from one account and in the hourly and daily cold caps (see GET /me). Raw delays or caps are not accepted.","default":"careful","examples":["careful"]},"dry_run":{"type":"boolean","title":"Dry Run","description":"When true nothing is written or sent: the answer says whether the message would be accepted and when it would go out. No Idempotency-Key needed.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["account_id","to","text"],"title":"SendMessageRequest"},"TokenInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Id of the token making this request.","examples":["tok_42"]},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Scopes this token holds.","examples":[["public:read","public:messages"]]}},"type":"object","required":["id","scopes"],"title":"TokenInfo"},"UpdateAccountRequest":{"properties":{"api_listen":{"type":"boolean","title":"Api Listen","description":"true: keep a listener on this account so replies arrive within seconds; false: stop (always allowed). Turning it on needs a bound proxy, a working session and an account that is neither banned nor taken out of work, otherwise `account_not_eligible` with `details.reason` `no_proxy`, `session_invalid`, `blocked` or `quarantine`. A frozen account can be listened to.","examples":[true]}},"additionalProperties":false,"type":"object","required":["api_listen"],"title":"UpdateAccountRequest"},"WebhookInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Webhook id.","examples":["wh_3"]},"url":{"type":"string","title":"Url","description":"Endpoint events are POSTed to.","examples":["https://crm.example.com/teletraff/webhook"]},"enabled":{"type":"boolean","title":"Enabled","description":"Whether events are delivered to this endpoint.","examples":[true]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your note about this endpoint.","examples":["Production CRM"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the webhook was created (UTC).","examples":["2026-10-07T12:30:02Z"]}},"type":"object","required":["id","url","enabled","description","created_at"],"title":"WebhookInfo"},"WebhookListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookInfo"},"type":"array","title":"Data","description":"Every webhook of this workspace (at most 5), oldest first. Secrets are never listed.","examples":[[{"created_at":"2026-10-07T12:30:02Z","description":"Production CRM","enabled":true,"id":"wh_3","url":"https://crm.example.com/teletraff/webhook"}]]}},"type":"object","required":["data"],"title":"WebhookListResponse"}}},"servers":[{"url":"/api/public/v1"}]}