Telegram has two APIs. The Bot API — neutered, restricted, limited to whatever Telegram decided bots are allowed to do. And MTProto — the actual protocol your phone uses, with full account access, no restrictions. Bots can’t read message history before they joined a chat. Bots can’t see who’s in a private group they’re not in. Bots can’t act as you. MTProto can.
Telethon is a Python MTProto client. telethon-plus wraps it in a Docker container with both an HTTP REST API and an MCP server. POST some JSON, or point an AI agent at /mcp/, and it talks to Telegram as you with full account access. One login. One session string. Never type a code again.
Userbot, Not a Bot
This is a userbot, not a bot. The distinction matters.
A bot is what BotFather gives you. Limited write access, no message history, no group membership without explicit invite, can’t react to most events. The Bot API is fine for sending notifications. It’s useless for anything that requires reading what’s actually happening in your chats.
A userbot is your account, controlled programmatically. Same access you have when you open Telegram on your phone. Read every message in every chat you’re in. Send messages as yourself. Forward, delete, edit. Join groups. Create channels. Act with your real identity, full history, full visibility.
The downside: it’s your account. If you abuse it, your account gets banned. Telegram rate-limits aggressively (FloodWaitError). Don’t spam. Use it for what it’s for: programmatic access to your Telegram, not mass messaging.
Quick Start
services:
telethon-plus:
image: psyb0t/telethon-plus
ports:
- "8080:8080"
environment:
TELETHON_API_ID: "123456"
TELETHON_API_HASH: "your-api-hash"
TELETHON_SESSION: "1Aa...long-string-from-login..."
TELETHON_AUTH_KEY: "your-bearer-token"
restart: unless-stoppedGet API_ID and API_HASH from my.telegram.org/apps. Get the session string from the login helper.
First-Time Login
Telegram makes you prove you’re human once — phone number, SMS code, optionally 2FA. The container has an interactive login mode that walks you through it and spits out a session string at the end:
docker run --rm -it
-e TELETHON_API_ID=123456
-e TELETHON_API_HASH=your-api-hash
psyb0t/telethon-plus loginOr if you’ve cloned the repo: cp .env.example .env, fill in TELETHON_API_ID and TELETHON_API_HASH, then make login — it builds the image, runs the flow, and writes TELETHON_SESSION straight into your .env.
The session string is full account access. Whoever has it is you. Don’t commit it. Don’t paste it in Slack. Don’t tattoo it anywhere.
The Tools
Same tool registry, two surfaces. Every tool works as both a REST endpoint and an MCP tool — JSON in, JSON out, schema-validated by pydantic. Send garbage, get a 400 back with exactly what’s wrong.
- get_me — return the authorized account profile
- get_entity — resolve a username/ID/link to a full profile
- send_message — send a text message (Markdown/HTML, reply, silent, link preview)
- get_messages — read recent messages from a chat (newest first, search, pagination)
- get_dialogs — list your chats, groups, and channels
- forward_messages — forward messages between chats
- delete_messages — delete messages by ID (revoke for everyone or just yourself)
- edit_message — edit a message you sent
- mark_read — mark messages in a chat as read
- send_file — download a file from an HTTPS URL and send it to a chat
- get_participants — list members of a group or channel
- create_group — create a new supergroup or broadcast channel
- delete_chat — delete a supergroup or channel you own
- join_chat — join a public channel or supergroup
- leave_chat — leave a channel or supergroup
- get_message — fetch a single message by ID
- download_media — pull a message’s media back as base64
- bulk_resolve — resolve many usernames/IDs in one call instead of N round-trips
- set_reaction / remove_reaction — react to messages
- pin_message / unpin_message — pin management
- create_poll, vote_poll, get_poll_results — polls end to end
- promote_user, demote_user, ban_user, unban_user, kick_user — channel admin
- join_via_invite — join through a private invite link
- get_linked_chat — the discussion group behind a channel, or vice versa
- account_health — is this account in trouble with Telegram right now
- throttle_status — what the rate limiter currently thinks
Thirty-four tools total, and every one of them works identically over REST and MCP.
Chat references accept whatever Telethon accepts: @username, phone numbers, t.me/... links, numeric IDs as strings, supergroup/channel IDs (negative numbers like -1001234567890), or me for your own Saved Messages.
HTTP API
Standard REST. /api/me, /api/messages, /api/dialogs, etc. As of v0.4.0 a 2xx returns the resource directly — the old {"result": ...} envelope is gone. If you wrote a client against v0.3.x, unwrap one level less.
# who am I
curl https://ciprian.51k.eu80/api/me
-H "Authorization: Bearer $TELETHON_AUTH_KEY"
# send a message with Markdown
curl -X POST https://ciprian.51k.eu80/api/messages
-H "Authorization: Bearer $TELETHON_AUTH_KEY"
-H "Content-Type: application/json"
-d '{"chat": "@somebody", "text": "**hello** from a container", "parse_mode": "md"}'
# read recent messages with full-text search
curl "https://ciprian.51k.eu80/api/messages?chat=me&limit=5&search=hello"
-H "Authorization: Bearer $TELETHON_AUTH_KEY"
# send a file from an HTTPS URL — never touches your disk
# (v0.4.0 folded /api/files into /api/messages; `caption` is now `text`)
curl -X POST https://ciprian.51k.eu80/api/messages
-H "Authorization: Bearer $TELETHON_AUTH_KEY"
-H "Content-Type: application/json"
-d '{"chat": "@psyb0t", "file_url": "https://example.com/photo.jpg", "text": "look at this"}'Telegram auto-detects media type from the file extension. .jpg becomes a photo, .mp4 a video, .mp3 audio. Use force_document: true to override.
Validation is strict — pydantic schemas reject extra fields and bad types with 400 and a detailed error body. Telegram RPC errors (FloodWaitError, ChatWriteForbiddenError) come back as 502 with the error class and message preserved.
Health check at /healthz always public, no auth required. Returns {"status": "ok", "authorized": true} — authorized: false means the session is fucked (bad string, revoked, or Telegram unreachable).
MCP Server
Mounted at /mcp/ using the streamable HTTP transport. Every tool from the list above shows up automatically as an MCP tool with the same name and schema. Stateless — every request is independent, no session juggling.
http://your-host:8080/mcp/Drop it into Claude Desktop, a custom agent, an LLM gateway, anything that speaks MCP over streamable HTTP. Works out of the box.
This is the part that matters. Any function-calling model can now read your Telegram, write to it, manage groups, forward content. The model decides when. The agent acts. Want Claude to summarize the last 50 messages from a chat into a daily digest? Plug in the MCP endpoint, ask. Want a model to forward customer support questions from a public channel to your team chat? Same. Want an agent that monitors a group for mentions and replies on your behalf? Done.
Not Getting Your Account Banned
This is the part that matters when you point automation at your own Telegram account: Telegram notices. Hammer the API and you get FLOOD_WAIT, then restrictions, then a dead account.
So there’s a real rate limiter now. Per-method token buckets, per-chat send and read intervals, and adaptive backoff when Telegram does return a FLOOD_WAIT. The defaults are sized to sit well under Telegram’s observed soft limits without you tuning anything. throttle_status tells you where you stand; account_health tells you whether the account is already in trouble.
Two safety switches for when you’re wiring something new: TELETHON_READ_ONLY refuses every mutating call, and TELETHON_DRY_RUN goes through the motions without actually sending. Point an LLM at your account for the first time with one of those on.
Entity cache. Resolving a username costs a real API call, and repeatedly resolving the same one is how you burn your quota for nothing. The cache persists across container restarts, so repeat lookups become free hits. v0.4.1 fixed a nasty bug in that path — the refresh was calling get_entity with a bare int, which Telethon treats as a PeerUser, so cached channels and groups raised, invalidated, and burned a ResolveUsername slot each time. Exactly the operation the cache existed to avoid. It wraps the correct Peer type now.
Observability
/metrics exposes Prometheus format. /ws/updates is a websocket that streams updates as they arrive. Set TELETHON_POST_TO_URL and every update also fires as an outbound webhook — which is the easy way to wire it into n8n or your own service without holding a socket open.
There’s a 44-test unit suite that runs without touching Telegram at all (make test-unit), separate from the integration tests that hit a real account.
Skill and Plugin
The repo ships an agent skill plus an OpenClaw plugin under .agents/, published to ClawHub by CI on tag pushes. An agent installs it and knows the whole tool surface without you pasting the list into a prompt.
Auth
Set TELETHON_AUTH_KEY to enable Bearer token auth on every endpoint except /healthz. If you don’t set it, the API runs unauthenticated — fine for localhost, suicidal exposed to a network. Always set it.
Authorization: Bearer your-secret-keyThe session string is the real secret — losing it means losing your account. Don’t even put it in compose YAML committed to a repo. .env file, gitignored, sourced at runtime.
Configuration
Everything via environment variables.
TELETHON_API_ID,TELETHON_API_HASH,TELETHON_SESSION— required, from my.telegram.org and the login helperTELETHON_AUTH_KEY— Bearer token for REST + MCP. Empty = no auth. Set this.TELETHON_HTTP_LISTEN_ADDRESS— default0.0.0.0:8080TELETHON_REQUEST_TIMEOUT— per-request timeout (default 60 seconds)TELETHON_FLOOD_SLEEP_THRESHOLD— auto-sleep through FloodWait errors below this many seconds (default 60). The safety valve.TELETHON_DEVICE_MODEL,TELETHON_SYSTEM_VERSION,TELETHON_APP_VERSION— what Telegram thinks your client isTELETHON_DOWNLOAD_DIR— scratch space forsend_fileuploads (default/tmp/telethon-plus)TELETHON_PROXY— optional SOCKS5 proxy (socks5://user:pass@host:port)
The flood sleep threshold is the safety valve — Telegram will rate-limit you when you push too hard. Below the threshold, the client transparently sleeps and retries. Above it, you get a 502 back. Tune it for your use case.
Tests Hit Real Telegram
No mocking. The test suite runs the container against your actual account, with messages getting sent and deleted in a chat you specify (TEST_CHAT — use me for Saved Messages so nobody else sees the noise).
cp .env.example .env
$EDITOR .env # TELETHON_API_ID, TELETHON_API_HASH, TELETHON_SESSION, TEST_CHAT
make testTests cover validation errors, send-edit-fetch-delete roundtrips, dialog listing, entity resolution, public channel reads, group create-and-delete, participant lists, MCP tool discovery and invocation, auth middleware (401 on missing/wrong tokens, healthz always public). If credentials aren’t set, the suite skips cleanly.
Security
The session string equals your account. Treat it like a credit card number that’s also your photo ID. Specifically:
- Always set
TELETHON_AUTH_KEY— never run unauthenticated except on isolated localhost - Never commit
TELETHON_SESSIONto git —.envfile, gitignored - If you suspect leakage, log into Telegram on your phone and revoke active sessions immediately
- Don’t expose port
8080to the public internet — put it behind a reverse proxy with TLS, or a Cloudflare Tunnel - Rotate the auth key if anything ever leaks
The container itself is small — Python 3.12, Telethon, FastAPI, MCP SDK. /healthz is the only public endpoint. Everything else needs the bearer token if TELETHON_AUTH_KEY is set.
Plugged Into aigate
This is wired into aigate as one of its MCP servers. Flip TELETHON=1 in the aigate .env, set the credentials, and any function-calling model running through the gateway can now talk to your Telegram. Groq calls a tool, the tool hits Telegram, you get a message. The model decides when.
A free-tier Groq model that searches the web, scrapes a page, generates an image, and sends the result to your Saved Messages — zero tokens paid, one conversation, multiple tool calls. That’s the point.
The Bottom Line
Bot API is fine if you only need to send notifications. Userbots are what you want when you actually need to do things on Telegram — read messages, manage groups, act as yourself programmatically. telethon-plus puts a real MTProto client behind HTTP and MCP, so any tool that speaks JSON or any MCP-aware agent can drive your account.
Go grab it: github.com/psyb0t/docker-telethon-plus
Licensed under WTFPL — do whatever the fuck you want.
Installing It Into Your Agent
The skill installs into Claude Code and Codex directly now. Everything under .agents/ is catalogued in one marketplace, so it is two commands:
claude plugin marketplace add psyb0t/agents
claude plugin install telethon-plus@psyb0tCodex uses the same marketplace with a different verb — codex plugin add telethon-plus@psyb0t, because there is no codex plugin install. It also finds the skill on its own in a checkout of the repo, since it scans .agents/skills/ natively with nothing installed at all. It is also listed on the official MCP Registry now, so a client that resolves servers from there can find it without being handed a URL.