HTTP API
A plain REST API. JSON in, JSON out. Every route runs one of the tools, so the MCP endpoint and REST do the same thing with the same parameters. The Tools page has each tool’s full parameter list; this page covers how to call the REST side, with examples.
Authentication
When TELETHON_AUTH_KEY is set, every route except /healthz and /metrics needs the key:
Authorization: Bearer your-secret-key
A missing or wrong key answers 401 with {"detail":"unauthorized"}. /ws/updates takes the key as ?token= instead, because browsers cannot set headers on a websocket. Without TELETHON_AUTH_KEY there is no auth at all; see Security.
Responses
A 2xx answer is the resource itself: an object for one thing, an array for a list. There is no {"result": ...} wrapper. Errors are {"detail": ...}, FastAPI’s shape; the status codes are listed under Errors.
Every /api/... response also carries throttle headers (X-Throttle-Multiplier, X-Throttle-Flood-Events-1h, X-RateLimit-Remaining-<bucket>), described in Observability.
Quick reference
| Endpoint | Tool | Required | What it does |
|---|---|---|---|
GET /api/me | get_me | none | Account profile. |
GET /api/entities | get_entity | chat | Resolve a username/ID/link to a profile. |
POST /api/entities/bulk | bulk_resolve | chats | Bulk resolve, honoring the resolve-username bucket. |
GET /api/dialogs | get_dialogs | none | List dialogs. Optional: limit, archived, search (substring on title/@). |
GET /api/messages | get_messages | chat | Read recent messages. Optional: limit, offset_id, search. |
GET /api/messages/{id} | get_message | chat | Fetch a single message. |
GET /api/messages/{id}/media | download_media | chat | Download the attachment as raw bytes, with its type in Content-Type (or application/octet-stream when the sender’s type is not a plain type/subtype) and its name in Content-Disposition. Optional: max_bytes. Over MCP, download_media returns base64 instead. |
POST /api/messages | send_message, send_file | chat, text or file_url | Send a message. With file_url, the server downloads it and sends it as media with text as the caption (send_file); otherwise it sends text (send_message). Optional with text: parse_mode, reply_to, silent, link_preview, schedule. Optional with a file: parse_mode, silent, force_document, max_bytes. |
POST /api/messages/forward | forward_messages | from_chat, to_chat, message_ids | Forward messages. |
POST /api/messages/read | mark_read | chat | Mark as read. Optional max_id. |
PATCH /api/messages/{id} | edit_message | chat, text | Edit. |
DELETE /api/messages | delete_messages | chat, message_ids | Bulk delete (body has the list). |
POST /api/messages/{id}/pin | pin_message | chat | Pin. Optional silent, pm_oneside. |
POST /api/messages/{id}/unpin | unpin_message | chat | Unpin. |
POST /api/messages/{id}/reactions | set_reaction | chat, emoji | React. Optional big. |
DELETE /api/messages/{id}/reactions | remove_reaction | chat | Remove your reaction. |
GET /api/participants | get_participants | chat | List members. Optional: limit, search. |
POST /api/chats | create_group | title | Create supergroup or channel. |
DELETE /api/chats | delete_chat | chat | Delete supergroup/channel you own (body). |
POST /api/chats/join | join_chat | chat | Join public channel/group. |
POST /api/chats/invite | join_via_invite | invite | Join via private t.me/+hash. |
POST /api/chats/leave | leave_chat | chat | Leave. |
GET /api/chats/{chat}/linked | get_linked_chat | none | Resolve a channel’s linked discussion group. |
POST /api/chats/{chat}/admin/ban | ban_user | user | Ban a user. Optional until_seconds. |
POST /api/chats/{chat}/admin/unban | unban_user | user | Lift a ban. |
POST /api/chats/{chat}/admin/kick | kick_user | user | Kick (ban + immediate unban). |
POST /api/chats/{chat}/admin/promote | promote_user | user | Grant the admin rights set to true (at least one), with an optional title. |
POST /api/chats/{chat}/admin/demote | demote_user | user | Strip admin rights. |
POST /api/polls | create_poll | chat, question, options | Create a poll. Optional multiple_choice, or quiz with correct_option and solution. |
POST /api/polls/{id}/vote | vote_poll | chat, options | Vote (0-based indices). |
GET /api/polls/{id}/results | get_poll_results | chat | Current vote counts. |
GET /api/throttle/status | throttle_status | none | Live rate-limit + cache state. |
GET /api/account/health | account_health | none | Flood-risk tier. |
GET /metrics | none | none | Prometheus exposition (no auth). |
WS /ws/updates | none | ?token=... | Stream incoming Telegram events. |
Path parameters take the same chat references as the body: @username, a numeric ID or me. A phone number (+...) or a t.me/... link in the path needs URL-encoding (%2B and so on).
GET /api/me
Who am I right now.
GET /api/me
{
"id": 123456789,
"type": "User",
"username": "someone",
"first_name": "Some",
"phone": "+15550000000"
}
Parameters: get_me.
GET /api/entities
Resolve any chat reference to a full profile.
GET /api/entities?chat=@telegram
{
"id": 1234567,
"type": "Channel",
"username": "telegram",
"title": "Telegram"
}
Parameters: get_entity.
GET /api/dialogs
GET /api/dialogs?limit=10&archived=false
Parameters: get_dialogs.
[
{
"id": 123456789,
"type": "User",
"username": "someone",
"first_name": "Some",
"last_name": "One",
"unread_count": 3,
"pinned": true,
"last_message": {
"id": 999,
"date": "2026-04-29T11:00:00+00:00",
"chat_id": 123456789,
"sender_id": 123456789,
"text": "hey",
"out": false,
"reply_to_msg_id": null,
"media": false,
"media_type": null
}
}
]
GET /api/messages
GET /api/messages?chat=me&limit=5&search=hello
Parameters: get_messages.
[
{
"id": 4242,
"date": "2026-04-29T12:00:00+00:00",
"chat_id": 12345,
"sender_id": 67890,
"text": "hello",
"out": false,
"reply_to_msg_id": null,
"media": false,
"media_type": null
}
]
Newest first. Returns [] if nothing matches.
POST /api/messages
Send a message. The same endpoint handles text and files. If the body contains file_url, the URL is fetched and sent as media (with text becoming the caption); otherwise text is sent as a plain message.
POST /api/messages
Content-Type: application/json
{
"chat": "@someone",
"text": "**hello** from a container",
"parse_mode": "md",
"silent": true,
"reply_to": 4241
}
POST /api/messages
Content-Type: application/json
{
"chat": "@someone",
"file_url": "https://example.com/photo.jpg",
"text": "look at this shit",
"silent": true
}
Parameters: send_message for text, send_file with file_url.
{
"id": 4242,
"date": "2026-04-29T12:00:00+00:00",
"chat_id": 12345,
"sender_id": 67890,
"text": "hello from a container",
"out": true,
"reply_to_msg_id": 4241,
"media": false,
"media_type": null
}
PATCH /api/messages/{id}
Edit a message. Message ID goes in the URL, everything else in the body.
PATCH /api/messages/4242
Content-Type: application/json
{
"chat": "me",
"text": "fixed version",
"parse_mode": "md"
}
Parameters: edit_message.
{
"id": 4242,
"date": "2026-04-29T12:00:00+00:00",
"chat_id": 99999,
"sender_id": 123456789,
"text": "fixed version",
"out": true,
"reply_to_msg_id": null,
"media": false,
"media_type": null
}
DELETE /api/messages
Delete messages by ID.
DELETE /api/messages
Content-Type: application/json
{
"chat": "@someone",
"message_ids": [4242, 4243],
"revoke": true
}
Parameters: delete_messages.
{
"deleted": 2,
"requested": 2
}
POST /api/messages/forward
POST /api/messages/forward
Content-Type: application/json
{
"from_chat": "@sourcechannel",
"to_chat": "me",
"message_ids": [101, 102, 103],
"silent": true
}
Parameters: forward_messages.
[
{
"id": 5001,
"date": "2026-04-29T12:01:00+00:00",
"chat_id": 99999,
"sender_id": 123456789,
"text": "forwarded content here",
"out": true,
"reply_to_msg_id": null,
"media": false,
"media_type": null
}
]
POST /api/messages/read
Mark messages as read.
POST /api/messages/read
Content-Type: application/json
{ "chat": "@someone", "max_id": 0 }
Parameters: mark_read.
{
"ok": true
}
GET /api/participants
List members of a group or channel.
GET /api/participants?chat=-1001234567890&limit=50&search=john
Parameters: get_participants.
[
{
"id": 123456789,
"type": "User",
"username": "johndoe",
"first_name": "John"
}
]
Large public channels may return a limited set or require admin rights.
POST /api/chats
Create a supergroup or broadcast channel.
POST /api/chats
Content-Type: application/json
{ "title": "my-group", "megagroup": true }
Parameters: create_group.
{
"id": 1234567890,
"type": "Channel",
"title": "my-group"
}
DELETE /api/chats
Delete a supergroup or channel you own. Irreversible.
DELETE /api/chats
Content-Type: application/json
{ "chat": "-1001234567890" }
{
"ok": true
}
Parameters: delete_chat.
POST /api/chats/join
Join a public channel or supergroup.
POST /api/chats/join
Content-Type: application/json
{ "chat": "@somegroup" }
{
"ok": true
}
Parameters: join_chat.
POST /api/chats/leave
Leave a channel, supergroup or basic group.
POST /api/chats/leave
Content-Type: application/json
{ "chat": "@somegroup" }
{
"ok": true
}
Parameters: leave_chat.
Errors
| Status | When |
|---|---|
400 | Bad JSON, a body that is not an object, a validation failure, or a value the tool refuses (such as a file over max_bytes). detail says what was wrong; for a validation failure it is pydantic’s list of errors. |
401 | Missing or wrong Bearer token (only when TELETHON_AUTH_KEY is set). |
403 | A write while TELETHON_READ_ONLY is on: read-only mode: write operations are disabled. |
404 | Unknown endpoint, or an unknown admin action in /api/chats/{chat}/admin/{action}. |
429 | Telegram sent a FLOOD_WAIT longer than TELETHON_FLOOD_SLEEP_THRESHOLD. Retry-After holds the seconds to wait, and detail is {"telegram_error": "FloodWaitError", "message": ..., "seconds": ...}. |
502 | Telegram refused the call for another reason (ChatWriteForbiddenError, MessageIdInvalidError and so on). detail is {"telegram_error": <Telethon error class>, "message": ...}. |
500 | A bug. The server log has the traceback; please open an issue. |
Health
GET /healthz
{ "status": "ok", "authorized": true }
Always public, no auth required. The server only starts answering once the session is authorized, so a running server says "authorized": true. A bad or revoked session stops the container at startup instead; see Troubleshooting.