Для розробників

TeleTraff API

Надсилайте повідомлення зі своїх Telegram-акаунтів із CRM, скрипта або ШІ-агента, читайте їхні діалоги й отримуйте відповіді вебхуком. Ключ API випускається в панелі, у розділі «Налаштування → API».

Як це влаштовано

  • Закрита бета: API вмикаємо воркспейсу на запит у підтримку.
  • Лише текст: одне повідомлення до 4096 символів, без медіа, файлів і кнопок.
  • Номери телефонів не підтримуються. Пишіть за username або в діалог, який в акаунта вже є.
  • Темп надсилання обирається лише пресетом, за замовчуванням найобережніший. Свої паузи й ліміти API не приймає.
  • Акаунти й проксі налаштовуються в панелі. API надсилає лише з акаунтів, до яких прив'язано проксі.
  • Відповіді співрозмовників приходять вебхуком з акаунтів, у яких увімкнено «Слухати для API».
Написати в підтримку

Підключити ШІ

Інструкція для ChatGPT і Claude

Вставте її першим повідомленням у чат із ШІ. Ключ покладіть у змінну оточення TELETRAFF_API_TOKEN: в інструкції його немає.

Показати текст
Ти допомагаєш мені працювати з API TeleTraff. Це сервіс, який веде мої Telegram-акаунти: надсилає з них повідомлення, читає діалоги й повідомляє про події вебхуками.

Базова адреса API: https://teletraff.com/api/public/v1
Повний опис методів і помилок: https://teletraff.com/api/public/v1/llms.txt. Прочитай його, перш ніж писати код.
OpenAPI-схема: https://teletraff.com/api/public/v1/openapi.json

Токен лежить у змінній оточення TELETRAFF_API_TOKEN і передається заголовком Authorization: Bearer <токен>. Ніколи не вставляй токен у код і не виводь його.

Правила:
1. Кожен запит, який щось змінює, надсилай із заголовком Idempotency-Key: своїм унікальним рядком на одну дію. Повтор із тим самим ключем не надішле повідомлення вдруге.
2. Темп задається лише полем pace: careful, balanced або fast. Типово careful; не змінюй його без моєї прямої вказівки.
3. Спочатку надсилай дію з dry_run: true і показуй мені, що станеться. Справжній запит роби лише після цього.
4. Якщо API повернув помилку, прочитай поля code і hint: hint каже, що робити далі. Не повторюй запит наосліп.
5. На відповідь 429 зачекай стільки секунд, скільки вказано в заголовку Retry-After.

Увесь API одним файлом

llms.txt: довідник англійською, який ШІ читає за посиланням. Схема OpenAPI лежить поруч.

Підключити через MCP

Claude, Cursor та інші агенти отримають інструменти TeleTraff напряму. Замініть tg_sk_live_... на свій ключ.

Cursor
{
  "mcpServers": {
    "teletraff": {
      "url": "https://teletraff.com/api/public/mcp",
      "headers": {
        "Authorization": "Bearer tg_sk_live_..."
      }
    }
  }
}
Claude Desktop
{
  "mcpServers": {
    "teletraff": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://teletraff.com/api/public/mcp",
        "--header",
        "Authorization:${TELETRAFF_AUTH}"
      ],
      "env": {
        "TELETRAFF_AUTH": "Bearer tg_sk_live_..."
      }
    }
  }
}
Claude Code
claude mcp add --transport http teletraff https://teletraff.com/api/public/mcp --header "Authorization: Bearer $TELETRAFF_API_TOKEN"

Довідник

Довідник англійською: це той самий текст, що в llms.txt.

TeleTraff Public API (v1)

Send Telegram messages from your own TeleTraff accounts, read the conversations they hold, and receive replies by webhook -- from your CRM, script or AI agent. JSON over HTTPS, bearer token, English errors with a machine code and a hint.

Overview

  • Base URL: https://teletraff.com/api/public/v1. Every path below is relative to it.
  • OpenAPI 3 document: https://teletraff.com/api/public/v1/openapi.json (no token needed). This file: https://teletraff.com/api/public/v1/llms.txt.
  • MCP server for Claude, Cursor and other agents: https://teletraff.com/api/public/mcp (see MCP).
  • /v1 only grows: fields and endpoints are added, never removed or renamed. Ignore fields you do not know.
  • Times are ISO 8601 in UTC (2026-10-07T12:30:02Z). Text is UTF-8.
  • Call GET /me first: it says what the token may do, its rate limit and the exact numbers of every pace.
  • Messages are queued and paced by the server to keep the accounts alive. POST /messages answers at once with scheduled_at; poll GET /messages/{message_id} or take the message.sent / message.failed webhook.

Quick start:

curl -s https://teletraff.com/api/public/v1/me -H "Authorization: Bearer $TELETRAFF_API_TOKEN"
curl -s https://teletraff.com/api/public/v1/messages -H "Authorization: Bearer $TELETRAFF_API_TOKEN" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"account_id": "acc_17", "to": {"username": "durov"}, "text": "Hi!", "dry_run": true}'

Authentication

Send Authorization: Bearer tg_sk_live_... on every call. Tokens are issued in the panel, Settings -> API; a panel login cookie does not open this API.

Scopes: public:read, public:messages, public:campaigns, public:webhooks. public:read reads accounts, messages and conversations; public:messages sends messages and switches listening; public:webhooks manages webhooks; public:campaigns is reserved and opens nothing in v1 yet. Each endpoint below names its scope; a missing one is 403 scope_denied with details.required_scope. GET /me needs only a valid token.

The API must be enabled for the workspace (closed beta); otherwise every call is 403 api_not_enabled.

IDs

Every id is a string with a type prefix, never a bare number, so one kind cannot be passed for another (that is 400 invalid_id, naming the expected prefix):

  • acc_123 -- account
  • msg_123 -- message
  • conv_123 -- conversation
  • wh_123 -- webhook
  • whd_123 -- webhook delivery
  • cmp_123 -- campaign
  • tok_123 -- token
  • evt_123 -- event

Pass ids back exactly as the API returned them.

Pagination

List endpoints take limit (1-100, default 50) and cursor. The answer has data and next_cursor; pass next_cursor back as cursor with the same filters until it is null. Cursors are opaque -- do not build or edit them (400 invalid_cursor). Endpoints with since return only what is newer than that time; keep the newest time you saw and poll with it.

Rate limits

60 requests per 60 seconds per token, in fixed windows, across all endpoints (openapi.json and llms.txt are not counted). Successful answers carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets). Over the limit the answer is 429 rate_limited with the same headers and Retry-After: wait that long, never retry in a tight loop.

Separately, one account holds at most 500 queued messages (429 account_queue_full), and sending above the pace caps is not refused: the message is queued for the earliest allowed time.

Idempotency

POST /messages requires an Idempotency-Key header of 8-128 characters (a UUID works), unique per message and per token:

  • Retrying with the same key and the same body returns the same message (202), never a second send. After a timeout or a network error, retry with the same key.
  • The same key with a different body (account, recipient, text or pace) is 409 idempotency_conflict.
  • Two requests with one key at the same moment: one wins, the other gets 409 idempotency_conflict; retry it.
  • dry_run: true writes nothing and needs no key.

Webhook deliveries are at least once: deduplicate by the X-TeleTraff-Delivery header (or the event id).

Pace presets

Sending tempo is chosen only by pace; default: careful when pace is omitted. Raw delays and caps are not accepted, an unknown name is 422 invalid_pace.

paceprotection modepause between cold messages, scold per account per hourcold per account per day
careful (default)slow90-113520
balancednormal45-561040
fastfast20-242080

A cold message is first contact (or the account wrote last): it waits for the pause after the account's previous cold message and counts against the hourly and daily caps; above a cap it is queued for later, not refused. A reply (the other person wrote last) goes out within seconds and spends no cap. The response's kind and scheduled_at say which it is and when it goes out.

Dry run

POST /messages with "dry_run": true runs every check (ids, recipient, pace, account state, caps) and answers 200 with would_status, kind, scheduled_at and the cold sends left this hour and day -- nothing is written or sent and no Idempotency-Key is needed. Refusals are the same as a real send. Use it to validate a plan before sending.

Errors

Every refusal has one shape; branch on code, show message, follow hint:

{
  "error": {
    "code": "account_not_eligible",
    "message": "This account cannot send a cold message right now (spamblock).",
    "hint": "This account may not write first while spamblocked; it can still reply.",
    "docs_url": "https://teletraff.com/en/docs/api#account_not_eligible",
    "details": {
      "reason": "spamblock"
    }
  }
}

code is stable; message and hint may be reworded. details carries what the code says it does. A 5xx without this shape is a server fault: retry later with the same Idempotency-Key.

unauthorized

HTTP 401. When: The Authorization header is missing or not Bearer <token>, or the token is unknown, revoked or expired, or its operator was removed or deactivated. What to do: Send 'Authorization: Bearer tg_sk_live_...' with a token issued in Settings -> API.

scope_denied

HTTP 403. When: The token is valid but lacks the scope of this operation; details.required_scope names it. What to do: Issue a token that includes details.required_scope in Settings -> API.

forbidden

HTTP 403. When: The operator who owns the token is blocked, so write requests (POST, PATCH, DELETE) are refused. What to do: Contact TeleTraff support; the token works again once access is restored in the TeleTraff panel.

payment_required

HTTP 402. When: The workspace's trial or subscription has ended: write requests are refused, reads still work. What to do: Renew the subscription in the panel (Billing), then retry the request.

api_not_enabled

HTTP 403. When: The public API is switched off for this workspace (it is in closed beta). What to do: The API is in closed beta. Ask TeleTraff support to enable it for your workspace.

rate_limited

HTTP 429. When: This token made more requests in the current fixed window than its limit (GET /me rate_limit). The answer carries Retry-After and X-RateLimit-* headers. What to do: Wait Retry-After seconds. Do not retry in a tight loop.

invalid_request

HTTP 422. When: A field is missing, unknown, of the wrong type or out of range, the JSON does not parse, or the message text was refused. details.errors lists {field, problem} for each. What to do: Fix the fields listed in details.errors.

invalid_id

HTTP 400. When: An id is not of the kind this place takes, e.g. an acc_ id where a conv_ id belongs. details.expected_prefix and details.value say which. What to do: Pass the id exactly as the API returned it; ids look like '<prefix>_123'.

invalid_cursor

HTTP 400. When: cursor was not taken from next_cursor of the same list (or the filters changed between pages). What to do: Omit cursor to start from the first page, then pass next_cursor back unchanged.

not_found

HTTP 404. When: No route matches this path. What to do: Check the path against /api/public/v1/openapi.json.

method_not_allowed

HTTP 405. When: The path exists but not with this HTTP method. What to do: Check the method against /api/public/v1/openapi.json.

bad_request

HTTP 400. When: Generic answer to an HTTP 400 that no more specific code covers. What to do: Check the request against /api/public/v1/openapi.json.

conflict

HTTP 409. When: Generic answer to an HTTP 409 that no more specific code covers. What to do: Read the object again and retry with its current state.

unavailable

HTTP 503. When: The service cannot answer right now, e.g. during maintenance. What to do: Retry later with backoff; honour Retry-After when it is present.

error

any other HTTP status. When: Catch-all for any other refusal status. Treat it by its HTTP status. What to do: Retry later if the status is 5xx; otherwise check the request against /api/public/v1/openapi.json.

idempotency_key_required

HTTP 400. When: POST /messages without an Idempotency-Key header of 8-128 characters (not needed with dry_run). What to do: Send a unique key per message (a UUID works) and reuse it only to retry the same request.

idempotency_conflict

HTTP 409. When: The key was already used with a different body, or a parallel request with the same key is still being admitted. What to do: Use a new Idempotency-Key for a new message; to retry, resend exactly the same body.

invalid_target

HTTP 422. When: to gives neither or both of username and conversation_id, or username is not a Telegram username. What to do: Give exactly one of to.username (5-32 characters, letter first, no '@') or to.conversation_id.

invalid_pace

HTTP 422. When: pace is not one of the presets. What to do: Use one of careful, balanced, fast, or omit pace for careful.

account_not_found

HTTP 404. When: The account id does not exist in this workspace. What to do: List accounts with GET /accounts.

conversation_not_found

HTTP 404. When: The conversation id does not exist, belongs to another account than account_id, or is no longer mirrored. What to do: A conversation_id belongs to one account; list them with GET /conversations?account_id=...

account_not_eligible

HTTP 422. When: The account's state forbids this action now; details.reason says why (see the reasons below). What to do: Pick another account from GET /accounts where can_send_cold (first contact) or can_reply (reply) is true, or fix the reason in the panel.

account_queue_full

HTTP 429. When: The account already holds details.limit queued messages. What to do: Wait for queued messages to go out, or send from another account.

message_not_found

HTTP 404. When: The message id does not exist in this workspace. What to do: Use the id returned by POST /messages.

webhook_not_found

HTTP 404. When: The webhook id does not exist in this workspace. What to do: List webhooks with GET /webhooks.

invalid_webhook_url

HTTP 422. When: The URL is not https, embeds credentials, or its host is localhost, private or link-local; details.reason says which. What to do: Use https:// with a public host name; localhost, private and link-local addresses are refused.

webhook_limit_reached

HTTP 409. When: The workspace already has details.limit webhooks. What to do: Delete one with DELETE /webhooks/{webhook_id} first.

delivery_not_found

HTTP 404. When: The delivery id does not exist or belongs to another webhook. What to do: List deliveries with GET /webhooks/{webhook_id}/deliveries.

delivery_in_flight

HTTP 409. When: Replay was asked for a delivery that is being sent at this moment. What to do: Wait a minute and read its state again; replay it only once it is delivered or failed.

account_not_eligible reasons

details.reason of account_not_eligible (the same word GET /accounts shows as blocked_reason). send is POST /messages, listen is PATCH /accounts/{account_id} with api_listen: true.

reasonapplies towhenwhat to do
spamblocksendTelegram's spam block: the account may not write first.It can still reply where the other person wrote last; send cold messages from another account.
cold_holdsendA cold-outreach hold after a peer flood: the account may not write first.It can still reply; send cold messages from another account.
frozensendTelegram froze the account.Use another account; a frozen account cannot send.
bannedsendTelegram banned or deleted the account.Use another account.
blockedsend, listenThe account is blocked, logged out or awaiting re-authorization.Fix it in the panel first, or use another account.
quarantinesend, listenThe panel took the account out of work (quarantine or an operator hold).Return it to work in the panel first, or use another account.
session_invalidsend, listenThe account's Telegram session does not open.Re-authorize the account in the panel.
proxy_failedsend, listenThe proxy bound to the account failed its last check, so the account cannot connect to Telegram.Replace or re-check the proxy in the panel, or pick another account from GET /accounts.
no_proxysend, listenNo proxy is bound to the account.Bind a proxy to the account in the panel; the API never uses an account without its proxy.
api_holdsendThe API holds the account for 24 hours after PeerFlood or a block.Retry after api_hold_until (GET /accounts, or the account.limited webhook), or use another account.

Message failure codes

A message that ends failed carries error: {code, message, details} in GET /messages/{message_id} and in the message.failed webhook. The request itself was accepted. account_unavailable and account_busy mean the account never reached Telegram; details.console_code is the panel's own word for the ending (details.last_retry_cause too when the retries ran out; quote them to support, branch on code).

codemeaningwhat to do
peer_floodTelegram limited this account for writing to strangers (PeerFlood); the API holds it for 24 hours.Do not resend from this account; its other queued messages fail with account_limited.
account_blockedThe account is frozen, banned or deactivated by Telegram.Send from another account.
username_not_foundNo Telegram user has this username.Check the username; do not retry it as is.
privacy_restrictedThe recipient's privacy settings do not allow this account to write to them.Do not retry; this recipient cannot be reached by a stranger.
write_forbiddenThis account may not write to this recipient or chat.Do not retry from this account.
recipient_deactivatedTelegram deleted the recipient's account; the message was not sent.Do not retry; remove this contact from your list.
flood_wait_exhaustedTelegram kept asking the account to wait longer than the message could be held.Send again later with a new Idempotency-Key, or from another account.
expiredThe message could not start within five minutes of its scheduled time.Send again with a new Idempotency-Key.
delivery_unknownTelegram did not confirm whether the message arrived; it was not resent to avoid a duplicate.Check the conversation (GET /conversations/{id}/messages) before sending again.
cancelledThe message was cancelled before it was sent.Send again with a new Idempotency-Key if it is still wanted.
account_limitedThe account was put on hold after PeerFlood or a block, so this queued message was not sent.Send it from another account, or again after the hold ends.
account_unavailableThe account could not connect (proxy or session problem). Nothing was sent to Telegram.Check the account's proxy and session in the panel, or pick another account from GET /accounts; then send again with a new Idempotency-Key.
account_busyThe account was busy with other work until the send deadline passed. Nothing was sent.Send again later with a new Idempotency-Key, or from another account.
send_failedThe message was not sent. details.console_code says why.Send again later with a new Idempotency-Key; if it repeats, use another account and quote details.console_code to TeleTraff support.

Endpoints

Refusals in the lists below are error.code values; see Errors. Any endpoint can also answer 401 unauthorized, 403 scope_denied, 403 api_not_enabled and 429 rate_limited; a write (POST, PATCH, DELETE) also 402 payment_required or 403 forbidden.

getMe: GET /me

Describe this token, its limits and the allowed pace presets. Scope: any valid token.

Call this first. It tells what the token may do, how many requests it may make, and the exact limits of every pace value.

Responses:

  • 200 -> MeResponse: Successful Response

Example 200:

{
  "api_version": "v1",
  "workspace": "default",
  "token": {
    "id": "tok_42",
    "scopes": [
      "public:read",
      "public:messages"
    ]
  },
  "rate_limit": {
    "limit": 60,
    "window_seconds": 60
  },
  "default_pace": "careful",
  "pace_presets": [
    {
      "cold_per_account_day": 20,
      "cold_per_account_hour": 5,
      "delay_seconds_max": 113,
      "delay_seconds_min": 90,
      "name": "careful"
    }
  ],
  "accounts_total": 12,
  "listening_accounts": 2
}

sendMessage: POST /messages

Send a message from one of your accounts. Scope: public:messages.

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.

A 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.

Send 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.

Parameters:

  • Idempotency-Key (in header, optional, string or null): Unique key per message, 8-128 characters (a UUID works). Required unless dry_run is true. Example: 7f9c2a5e-3b1d-4f0a-9c8e-1d2b3c4d5e6f.

Request body: SendMessageRequest (fields under Objects). Example:

{
  "account_id": "acc_17",
  "to": {
    "username": "durov"
  },
  "text": "Hi! Saw your post about the meetup, are you still looking for speakers?",
  "pace": "careful",
  "dry_run": false
}

Responses:

  • 202 -> MessageResponse: Accepted and queued (or the earlier answer to the same Idempotency-Key).
  • 200 -> DryRunResponse: dry_run: true: nothing was written.
  • 400: idempotency_key_required (missing or not 8-128 characters) or invalid_id.
  • 404: account_not_found or conversation_not_found.
  • 409: idempotency_conflict: the key was used with a different body.
  • 422: 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.
  • 429: account_queue_full (500 queued messages on this account) or rate_limited.

Example 202:

{
  "id": "msg_1052",
  "status": "queued",
  "kind": "cold",
  "account_id": "acc_17",
  "conversation_id": "conv_381",
  "to_username": "durov",
  "pace": "careful",
  "scheduled_at": "2026-10-07T12:31:40Z",
  "sent_at": null,
  "error": null,
  "created_at": "2026-10-07T12:30:02Z"
}

Example 200:

{
  "would_status": "queued",
  "kind": "cold",
  "pace": "careful",
  "scheduled_at": "2026-10-07T12:31:40Z",
  "cold_remaining_hour": 4,
  "cold_remaining_day": 19
}

getMessage: GET /messages/{message_id}

Read one message and its outcome. Scope: public:read.

The current state of a message sent with POST /messages. sent and failed are final; a failed message carries error.code.

Parameters:

  • message_id (in path, required, string): Message id returned by POST /messages. Example: msg_1052.

Responses:

  • 200 -> MessageResponse: Successful Response
  • 400: invalid_id.
  • 404: message_not_found.
  • 422: invalid_request.

Example 200:

{
  "id": "msg_1052",
  "status": "queued",
  "kind": "cold",
  "account_id": "acc_17",
  "conversation_id": "conv_381",
  "to_username": "durov",
  "pace": "careful",
  "scheduled_at": "2026-10-07T12:31:40Z",
  "sent_at": null,
  "error": null,
  "created_at": "2026-10-07T12:30:02Z"
}

listAccounts: GET /accounts

List your Telegram accounts and what each may send now. Scope: public:read.

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.

Parameters:

  • limit (in query, optional, integer, min 1, max 100, default 50): Page size, 1-100. Example: 50.
  • cursor (in query, optional, string or null): next_cursor of the previous page; omit for the first page. Example: acc_17.

Responses:

  • 200 -> AccountListResponse: Successful Response
  • 400: invalid_cursor.
  • 422: invalid_request: limit is not 1-100.

Example 200:

{
  "data": [
    {
      "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": "acc_17"
}

updateAccount: PATCH /accounts/{account_id}

Turn listening for incoming replies on or off for one account. Scope: public:messages.

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.

Parameters:

  • account_id (in path, required, string): Account id from GET /accounts. Example: acc_17.

Request body: UpdateAccountRequest (fields under Objects). Example:

{
  "api_listen": true
}

Responses:

  • 200 -> AccountInfo: Successful Response
  • 400: invalid_id.
  • 404: account_not_found.
  • 422: account_not_eligible (details.reason: no_proxy, session_invalid, blocked, quarantine) or invalid_request.

Example 200:

{
  "id": "acc_17",
  "display_name": "Anna, sales",
  "username": "anna_sales",
  "can_send_cold": true,
  "can_reply": true,
  "blocked_reason": null,
  "proxy_bound": true,
  "api_hold_until": null,
  "cold_sent_last_hour": 1,
  "cold_sent_last_day": 6,
  "api_listen": false
}

listConversations: GET /conversations

List private conversations of your accounts. Scope: public:read.

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.

Parameters:

  • account_id (in query, optional, string or null): Only conversations of this account (acc_...); omit for every account. Example: acc_17.
  • since (in query, optional, string (ISO 8601 date-time, UTC) or null): ISO 8601 time; only conversations whose newest message is later. Without a zone it is UTC. Example: 2026-10-07T14:00:00Z.
  • limit (in query, optional, integer, min 1, max 100, default 50): Page size, 1-100. Example: 50.
  • cursor (in query, optional, string or null): next_cursor of the previous page (with the same filters); omit for the first page. Example: MjAyNi0xMC0wN1QxNDowNDo1OHwzODE.

Responses:

  • 200 -> ConversationListResponse: Successful Response
  • 400: invalid_id or invalid_cursor.
  • 404: account_not_found.
  • 422: invalid_request: limit is not 1-100 or since is not an ISO 8601 time.

Example 200:

{
  "data": [
    {
      "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": "MjAyNi0xMC0wN1QxNDowNDo1OHwzODE"
}

listConversationMessages: GET /conversations/{conversation_id}/messages

Read the messages of one conversation. Scope: public:read.

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.

Parameters:

  • conversation_id (in path, required, string): Conversation id from GET /conversations. Example: conv_381.
  • since (in query, optional, string (ISO 8601 date-time, UTC) or null): ISO 8601 time; only messages sent after it, oldest first. Without a zone it is UTC. Example: 2026-10-07T14:00:00Z.
  • limit (in query, optional, integer, min 1, max 100, default 50): Page size, 1-100. Example: 50.
  • cursor (in query, optional, string or null): next_cursor of the previous page (with the same since); omit for the first page. Example: MjAyNi0xMC0wN1QxNDowNDo1OHw0MDIx.

Responses:

  • 200 -> ConversationMessageListResponse: Successful Response
  • 400: invalid_id or invalid_cursor.
  • 404: conversation_not_found.
  • 422: invalid_request: limit is not 1-100 or since is not an ISO 8601 time.

Example 200:

{
  "data": [
    {
      "actor": "contact",
      "direction": "incoming",
      "sent_at": "2026-10-07T14:04:58Z",
      "telegram_message_id": 4021,
      "text": "Yes, interested"
    }
  ],
  "next_cursor": "MjAyNi0xMC0wN1QxNDowNDo1OHw0MDIx"
}

listWebhooks: GET /webhooks

List your webhooks. Scope: public:webhooks.

Every webhook of this workspace, oldest first. The signing secret is never listed.

Responses:

  • 200 -> WebhookListResponse: Successful Response

Example 200:

{
  "data": [
    {
      "created_at": "2026-10-07T12:30:02Z",
      "description": "Production CRM",
      "enabled": true,
      "id": "wh_3",
      "url": "https://crm.example.com/teletraff/webhook"
    }
  ]
}

createWebhook: POST /webhooks

Register an HTTPS endpoint for events. Scope: public:webhooks.

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).

Request body: CreateWebhookRequest (fields under Objects). Example:

{
  "url": "https://crm.example.com/teletraff/webhook",
  "description": "Production CRM"
}

Responses:

  • 201 -> CreatedWebhook: Successful Response
  • 409: webhook_limit_reached: the workspace already has 5 webhooks.
  • 422: invalid_webhook_url (not https, or a localhost/private/link-local host) or invalid_request.

Example 201:

{
  "id": "wh_3",
  "url": "https://crm.example.com/teletraff/webhook",
  "enabled": true,
  "description": "Production CRM",
  "created_at": "2026-10-07T12:30:02Z",
  "secret": "whsec_8VQf3m1yJqzK0cT9pWb2xR7nLd4sHa6uEo5iYg1kMvc"
}

deleteWebhook: DELETE /webhooks/{webhook_id}

Delete a webhook. Scope: public:webhooks.

Stops deliveries to this endpoint at once and removes its delivery log.

Parameters:

  • webhook_id (in path, required, string): Webhook id from GET /webhooks. Example: wh_3.

Responses:

  • 204: Successful Response
  • 400: invalid_id.
  • 404: webhook_not_found.
  • 422: invalid_request.

sendTestEvent: POST /webhooks/{webhook_id}/test

Send a test event to one webhook. Scope: public:webhooks.

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}.

Parameters:

  • webhook_id (in path, required, string): Webhook id from GET /webhooks. Example: wh_3.

Responses:

  • 202 -> DeliveryAccepted: Successful Response
  • 400: invalid_id.
  • 404: webhook_not_found.
  • 422: invalid_request.

Example 202:

{
  "delivery_id": "whd_981"
}

listWebhookDeliveries: GET /webhooks/{webhook_id}/deliveries

Read the delivery log of a webhook. Scope: public:webhooks.

Deliveries to this webhook, newest first, 50 per page by default, with the last status code and error.

Parameters:

  • webhook_id (in path, required, string): Webhook id from GET /webhooks. Example: wh_3.
  • limit (in query, optional, integer, min 1, max 100, default 50): Page size, 1-100. Example: 50.
  • cursor (in query, optional, string or null): next_cursor of the previous page; omit for the newest page. Example: whd_932.

Responses:

  • 200 -> DeliveryListResponse: Successful Response
  • 400: invalid_id or invalid_cursor.
  • 404: webhook_not_found.
  • 422: invalid_request: limit is not 1-100.

Example 200:

{
  "data": [
    {
      "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": "whd_932"
}

replayWebhookDelivery: POST /webhooks/{webhook_id}/deliveries/{delivery_id}/replay

Send a delivery again. Scope: public:webhooks.

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.

Parameters:

  • webhook_id (in path, required, string): Webhook id from GET /webhooks. Example: wh_3.
  • delivery_id (in path, required, string): Delivery id from the delivery log. Example: whd_981.

Responses:

  • 202 -> DeliveryAccepted: Successful Response
  • 400: invalid_id.
  • 404: webhook_not_found or delivery_not_found.
  • 409: delivery_in_flight: the delivery is being sent right now.
  • 422: invalid_request.

Example 202:

{
  "delivery_id": "whd_981"
}

Objects

AccountInfo

  • id (string, required): Account id to pass as account_id.
  • display_name (string, required): Name of the account in the panel.
  • username (string or null, required): The account's own Telegram username, if it has one.
  • can_send_cold (boolean, required): Whether the account may write first (cold message) right now.
  • can_reply (boolean, required): Whether the account may reply where the other person wrote last. A spamblocked account can often still reply.
  • blocked_reason (string or null, required): 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.
  • proxy_bound (boolean, required): Whether a proxy is bound; an account without one cannot send.
  • api_hold_until (string (ISO 8601 date-time, UTC) or null, required): Until when the API holds this account after PeerFlood or a block (UTC); null when not held.
  • cold_sent_last_hour (integer, required): Cold messages this account sent through the API in the last hour.
  • cold_sent_last_day (integer, required): Cold messages this account sent through the API in the last 24 hours.
  • api_listen (boolean, required): 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.

AccountListResponse

  • data (array of AccountInfo, required): Accounts of this page, oldest first.
  • next_cursor (string or null, required): Pass as cursor to get the next page; null on the last page.

ConversationInfo

  • id (string, required): Conversation id; use it as to.conversation_id in POST /messages.
  • account_id (string, required): Account this conversation belongs to.
  • peer_id (string, required): The other person as Telegram knows them, user:<id>; same as from.peer_id in message.received.
  • username (string or null, required): Their Telegram username without '@', when known.
  • title (string, required): Their display name, or @username, or peer_id when nothing else is known.
  • last_message_at (string (ISO 8601 date-time, UTC) or null, required): When the newest message of the conversation was sent (UTC); null when unknown.
  • last_message_preview (string or null, required): Start of the newest message, or a word for an attachment.
  • last_direction (one of incoming, outgoing or null, required): Who wrote last: incoming (they did) or outgoing (your account); null when unknown.
  • unread_count (integer, required): Incoming messages the account has not read.

ConversationListResponse

  • data (array of ConversationInfo, required): Conversations of this page, most recent message first.
  • next_cursor (string or null, required): Pass as cursor to get the next page; null on the last page.

ConversationMessage

  • telegram_message_id (integer, required): Telegram's id of the message; same as telegram_message_id in webhook events.
  • direction (one of incoming, outgoing, required): incoming (they wrote) or outgoing (your account wrote).
  • text (string or null, required): Message text; null for an attachment without a caption.
  • sent_at (string (ISO 8601 date-time, UTC) or null, required): When the message was sent (UTC).
  • message_id (string or null, required): Id of the API message (msg_...) when it was sent with POST /messages; null otherwise.
  • actor (one of api, ai, operator, contact, required): 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).

ConversationMessageListResponse

  • data (array of ConversationMessage, required): Messages of this page: oldest first with since, newest first without it.
  • next_cursor (string or null, required): Pass as cursor (with the same since) to get the next page; null on the last page.

CreateWebhookRequest

  • url (string, required, min length 1, max length 2048): HTTPS endpoint that receives a POST per event. Must be reachable on the public internet: http, localhost, private and link-local addresses are refused.
  • description (string or null): Your own note about this endpoint, up to 255 characters.

CreatedWebhook

  • id (string, required): Webhook id.
  • url (string, required): Endpoint events are POSTed to.
  • enabled (boolean, required): Whether events are delivered to this endpoint.
  • description (string or null, required): Your note about this endpoint.
  • created_at (string (ISO 8601 date-time, UTC), required): When the webhook was created (UTC).
  • secret (string, required): 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>")>.

DeliveryAccepted

  • delivery_id (string, required): Delivery that will be attempted within seconds; follow it in GET /webhooks/{webhook_id}/deliveries.

DeliveryInfo

  • id (string, required): Delivery id; also sent as the X-TeleTraff-Delivery header.
  • event_id (string, required): Event id; also the id of the delivered JSON body.
  • event_type (string, required): 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).
  • state (one of pending, claimed, sending, retry, delivered, failed, required): 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).
  • attempt_count (integer, required): Attempts made so far.
  • last_status_code (integer or null, required): HTTP status of the last attempt; null when there was no answer.
  • last_error (string or null, required): 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.
  • next_attempt_at (string (ISO 8601 date-time, UTC) or null, required): When the next attempt is due (UTC); null once delivered or failed.
  • delivered_at (string (ISO 8601 date-time, UTC) or null, required): When your endpoint accepted it (UTC); null until delivered.
  • created_at (string (ISO 8601 date-time, UTC), required): When the delivery was created (UTC).

DeliveryListResponse

  • data (array of DeliveryInfo, required): Deliveries to this webhook, newest first.
  • next_cursor (string or null, required): Pass as cursor to get older deliveries; null on the last page.

DryRunResponse

  • would_status (string, required): The status the message would get. A message that would be refused gets the same error a real send gets.
  • kind (one of cold, reply, required): 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.
  • pace (one of careful, balanced, fast, required): 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.
  • scheduled_at (string (ISO 8601 date-time, UTC), required): When the message would go out (UTC).
  • cold_remaining_hour (integer, required): Cold messages this account could still schedule in the current rolling hour, counting this one as sent.
  • cold_remaining_day (integer, required): Cold messages this account could still schedule in the current rolling day, counting this one as sent.

MeResponse

  • api_version (string, required): Version of this API.
  • workspace (string, required): Workspace the token belongs to.
  • token (TokenInfo, required): The token making this request and its scopes.
  • rate_limit (RateLimitInfo, required): How many requests this token may make per window.
  • default_pace (one of careful, balanced, fast, required): Pace used when a request does not pass pace. Always careful.
  • pace_presets (array of PacePresetInfo, required): Every allowed pace value with its exact limits. Replies into a conversation where the other person wrote last do not count against these caps.
  • accounts_total (integer, required): Telegram accounts in this workspace; GET /accounts lists them.
  • listening_accounts (integer, required): 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.

MessageError

  • code (string, required): 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.
  • message (string, required): What happened, in English.
  • details (object or null): 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.

MessageResponse

  • id (string, required): Message id; poll it with GET /messages/{id}.
  • status (one of queued, sending, sent, failed, required): queued (waiting for its scheduled time), sending, sent or failed (see error). sent and failed are final.
  • kind (one of cold, reply, required): 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.
  • account_id (string, required): Account the message is sent from.
  • conversation_id (string or null, required): Conversation the message belongs to; null for a cold message until it is sent.
  • to_username (string or null, required): Recipient username, when known.
  • pace (one of careful, balanced, fast, required): 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.
  • scheduled_at (string (ISO 8601 date-time, UTC), required): When the message is due to go out (UTC). Cold messages are spaced by the pace preset.
  • sent_at (string (ISO 8601 date-time, UTC) or null, required): When Telegram accepted the message (UTC); null until sent.
  • error (MessageError or null, required): Set only when status is failed.
  • created_at (string (ISO 8601 date-time, UTC), required): When the request was accepted (UTC).

MessageTarget

  • username (string or null): Telegram username to write to, without '@' (5-32 characters: a letter, then letters, digits or underscores). Give this or conversation_id, not both.
  • conversation_id (string or null): Id of a conversation of this same account (conv_...). Give this or username, not both.

PacePresetInfo

  • name (one of careful, balanced, fast, required): Value to pass as pace.
  • delay_seconds_min (number, required): Shortest pause between two cold messages from one account.
  • delay_seconds_max (number, required): Longest pause between two cold messages from one account.
  • cold_per_account_hour (integer, required): Cold messages one account may send per hour.
  • cold_per_account_day (integer, required): Cold messages one account may send per day.

RateLimitInfo

  • limit (integer, required): Requests allowed per window for this token.
  • window_seconds (integer, required): Window length in seconds.

SendMessageRequest

  • account_id (string, required): Account to send from (acc_..., see GET /accounts).
  • to (MessageTarget, required): Recipient: exactly one of username or conversation_id.
  • text (string, required, min length 1, max length 4096): Message text, 1-4096 characters, not blank.
  • pace (one of careful, balanced, fast, default "careful"): 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.
  • dry_run (boolean, default false): 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.

TokenInfo

  • id (string, required): Id of the token making this request.
  • scopes (array of string, required): Scopes this token holds.

UpdateAccountRequest

  • api_listen (boolean, required): 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.

WebhookInfo

  • id (string, required): Webhook id.
  • url (string, required): Endpoint events are POSTed to.
  • enabled (boolean, required): Whether events are delivered to this endpoint.
  • description (string or null, required): Your note about this endpoint.
  • created_at (string (ISO 8601 date-time, UTC), required): When the webhook was created (UTC).

WebhookListResponse

  • data (array of WebhookInfo, required): Every webhook of this workspace (at most 5), oldest first. Secrets are never listed.

Webhooks

Register up to 5 HTTPS endpoints per workspace with POST /webhooks; the answer carries the signing secret (whsec_...) once -- store it. Each event is POSTed as JSON to every enabled webhook. Events: message.received, message.sent, message.failed, account.limited, webhook.test.

{
  "id": "evt_5512",
  "type": "message.received",
  "created_at": "2026-10-07T12:30:02Z",
  "data": {
    "account_id": "acc_17",
    "conversation_id": "conv_381",
    "telegram_message_id": 4411,
    "from": {
      "username": "durov",
      "peer_id": "user:1"
    },
    "text": "Sounds good, call me tomorrow",
    "sent_at": "2026-10-07T12:30:01Z"
  }
}

data by event type:

  • message.received: someone wrote to a listening account (PATCH /accounts/{account_id} api_listen: true). account_id, conversation_id, telegram_message_id, from (username, peer_id), text, sent_at. Private conversations only.
  • message.sent / message.failed: outcome of a message. message_id (null when not sent through the API), account_id, conversation_id, actor (api, ai for a Dialogs AI reply, operator for the panel), telegram_message_id, text, error ({code, message, details} on message.failed, see Message failure codes).
  • account.limited: the API holds an account after PeerFlood or a block. account_id, reason, held_until.
  • webhook.test: sent only by POST /webhooks/{webhook_id}/test. webhook_id, message.

Request headers (header names are case-insensitive; most servers lower-case them):

  • Content-Type: application/json
  • User-Agent: TeleTraff-Webhooks/1
  • X-TeleTraff-Event: the event type.
  • X-TeleTraff-Delivery: delivery id (whd_...); the same on every retry of this delivery -- dedupe by it.
  • X-TeleTraff-Timestamp: Unix seconds when this attempt was signed.
  • X-TeleTraff-Signature: sha256= + hex HMAC-SHA256, key = the whole whsec_... secret, message = <X-TeleTraff-Timestamp>.<raw body bytes>.

Answer any 2xx within 10 seconds; do slow work after answering. Anything else -- another status, a timeout, a redirect (never followed) -- is retried after 30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, 8 h, 8 h; after 10 attempts the delivery is failed. GET /webhooks/{webhook_id}/deliveries shows every attempt and POST .../replay sends one again.

Verifying the signature

Compute the HMAC over the raw body bytes as received (parsing and re-serializing the JSON changes them), compare in constant time, and reject a timestamp more than 300 seconds from your clock.

Python (standard library only):

import hashlib
import hmac
import time

def verify_teletraff_signature(secret, headers, raw_body, tolerance_seconds=300):
    """True when this delivery was signed with your webhook secret and is fresh.

    secret: the whole whsec_... string. headers: the request headers (any mapping).
    raw_body: the request body as bytes, exactly as received -- not re-serialized JSON.
    """
    lowered = {str(name).lower(): str(value) for name, value in headers.items()}
    timestamp = lowered.get("x-teletraff-timestamp", "")
    signature = lowered.get("x-teletraff-signature", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance_seconds:
        return False
    signed = timestamp.encode("ascii") + b"." + raw_body
    expected = "sha256=" + hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask:   verify_teletraff_signature(SECRET, request.headers, request.get_data())
# FastAPI: verify_teletraff_signature(SECRET, request.headers, await request.body())

Node.js (built-in crypto only):

const crypto = require("node:crypto");

// secret: the whole whsec_... string. headers: the request headers.
// rawBody: the request body as a Buffer, exactly as received -- not re-serialized JSON.
function verifyTeletraffSignature(secret, headers, rawBody, toleranceSeconds = 300) {
  const lowered = {};
  for (const [name, value] of Object.entries(headers)) lowered[name.toLowerCase()] = String(value);
  const timestamp = lowered["x-teletraff-timestamp"] || "";
  const signature = lowered["x-teletraff-signature"] || "";
  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
    return false;
  }
  const expected = "sha256=" + crypto.createHmac("sha256", secret)
    .update(Buffer.concat([Buffer.from(timestamp + ".", "ascii"), Buffer.from(rawBody)]))
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: app.post("/teletraff/webhook", express.raw({ type: "application/json" }), (req, res) => {
//   if (!verifyTeletraffSignature(SECRET, req.headers, req.body)) return res.sendStatus(401);
//   const event = JSON.parse(req.body); res.sendStatus(200); /* handle event after answering */ });
module.exports = { verifyTeletraffSignature };

MCP

https://teletraff.com/api/public/mcp is a Model Context Protocol server (Streamable HTTP: JSON-RPC 2.0 over POST, answers in application/json, protocol version 2025-06-18). It takes the same bearer token, and each tool call is the REST call named below -- same scopes, rate limit, idempotency, pace and dry_run. A refused call returns isError: true with the text code: message — hint, then the error's details and Retry-After when there are any; structuredContent.error is the REST error object.

Every tool call is one REST request and counts against the token's rate limit (60 per 60 s); initialize, tools/list and ping do not. The server is stateless: no Mcp-Session-Id, and GET answers 405 (no server-sent stream).

toolcallsarguments / returns
get_meGET /metoken scopes, rate limit, pace presets with their numbers, account counts
list_accountsGET /accountsaccounts and whether each may send now; limit, cursor
send_messagePOST /messagesaccount_id, to, text, pace?, dry_run?, idempotency_key?. Without idempotency_key the tool derives one from account_id, to, text and pace, so repeating the same call never sends twice
get_messageGET /messages/{message_id}status and outcome of one message
list_conversationsGET /conversationsaccount_id?, since?, limit, cursor
get_conversation_messagesGET /conversations/{conversation_id}/messagessince?, limit, cursor
list_webhooksGET /webhooksregistered webhooks (secrets are never listed)

Claude Code:

claude mcp add --transport http teletraff https://teletraff.com/api/public/mcp --header "Authorization: Bearer $TELETRAFF_API_TOKEN"

Cursor (~/.cursor/mcp.json) and other clients that take a URL with headers:

{
  "mcpServers": {
    "teletraff": {
      "url": "https://teletraff.com/api/public/mcp",
      "headers": {
        "Authorization": "Bearer tg_sk_live_..."
      }
    }
  }
}

Limits

  • Text only: one message is 1-4096 characters of plain text. No media, files, buttons or formatting.
  • Phone numbers are not supported: write to a Telegram username, or into a conversation_id the account already has.
  • Conversations are private one-to-one chats; groups and channels are not listed.
  • Listening: at most 8 listening accounts are connected at once per console runner process; accounts beyond that wait for a free slot, so their message.received events arrive later. Listening also counts against your plan's stream limit.
  • 500 queued messages per account; 5 webhooks per workspace; 60 requests per 60 s per token.
  • A queued message that cannot start within five minutes of its scheduled_at fails with expired.
  • Accounts and proxies are managed in the panel; the API sends only from accounts that already have their proxy bound.