Skip to main content

Architecture

botapi exposes the Telegram Bot API surface (types, methods, updates) but implements it directly over MTProto via gotd/td — not over HTTP to api.telegram.org. This page sketches how the Bot API surface maps onto the MTProto building blocks underneath. The Bot API docs (core.telegram.org/bots/api) are the spec.

The translation layer

A user holds a botapi.Message, never a tg.Message. Between the two sits a translation layer that botapi owns; everything below it is gotd/td doing the protocol heavy lifting. botapi never re-implements MTProto, peer resolution, gap recovery or file transfer — it translates.

┌─────────────────────────────────────────┐
BotFather token → │ telegram.Client (MTProto, one per bot) │
│ Auth().Bot(token) │
└───────────────┬──────────────────────────┘
│ API() *tg.Client
┌─────────────────────────────┼───────────────────────────────┐
▼ outgoing ▼ peers ▼ incoming
message.Sender peers.Manager (Storage/Cache) UpdateHandler chain:
uploader/downloader ResolveTDLibID / ResolveDomain peers.UpdateHook
fileid codec InputPeer + access hashes → updates.Manager (gaps)
→ tg.UpdateDispatcher
(OnNewMessage, …)
└──────────────── translation layer (botapi) ─────────────────┘

Bot API types & methods (hand-written)

The building blocks

botapi sits on a handful of gotd/td primitives, each documented in this site:

BlockRoleDocs
telegram.ClientThe MTProto connection; Auth().Bot(token) logs the bot in.First client, Bot auth
message.SenderFluent builder for everything outgoing.Message sender
telegram/peersResolves bare Bot-API chat IDs into InputPeers with access hashes.Peers
telegram/updatesGap-aware update manager (getDifference recovery).Updates recovery
tg.UpdateDispatcherTyped update fan-out — the terminal handler.Handling updates
uploader / downloaderFile transfer for GetFile-style operations.Uploads, Downloads
fileidCodec between Bot API file_id strings and MTProto locations.Downloading files
tgerrRPC error matching, mapped onto Bot API {error_code, description}.Errors & resilience

Update flow — no long-poll, no webhook

Because botapi is on MTProto, there is no getUpdates and no webhook. Updates arrive on the persistent connection and flow through a verified chain:

tg updates → peers.Manager.UpdateHook (harvest access hashes)
→ updates.Manager.Handle (gap recovery)
→ tg.UpdateDispatcher (typed fan-out)
→ botapi mapping (tg.Update* → botapi.Update)
→ handler router (predicates, middleware, handlers)

The peers.UpdateHook is what keeps access hashes fresh without explicit calls: every update's users and chats are harvested into storage before your handler sees it. That is why a bot can later address any peer it has "seen."

Type-safe by construction

  • Sealed-interface unions — where the Bot API uses "one of" objects (ChatID, InputFile, ReplyMarkup, InputMedia, ChatMember, InlineQueryResult, InputMessageContent, …), botapi uses an interface with an unexported marker method and a fixed set of implementations. Illegal states are unrepresentable, and switches over them are checked for exhaustiveness by a linter.
  • Typed enumsParseMode, ChatType, ChatAction, MessageEntityType, … are typed constants, not bare strings the caller can mistype.
  • Zero-reflection request building — there is no JSON marshaling on the wire; methods translate typed params straight into gotd/td builder calls. Hot paths get allocation-test coverage like gotd/td itself.

Conformance

botapi keeps a copy of the published Bot API docs (internal/botdoc) as a reference oracle. A conformance test asserts that every published method is either implemented on *Bot, covered by other means, or explicitly categorized as deferred — so when Telegram ships a new Bot API version, the drift surfaces as a failing test rather than a silent gap.

For the full design rationale and rebuild history, see the upstream architecture.md, building-blocks.md and roadmap.md.