GLOSSARY

What is TDLib?

TDLib, short for Telegram Database Library, is Telegram's own cross-platform C++ client library: it implements the full MTProto stack, keeps a local encrypted database of chats and messages, and exposes everything through a single asynchronous JSON interface. It is the officially supported way to build a Telegram client without implementing the protocol yourself.

Free forever plan · No credit card required · Cancel anytime

Quick definition

TDLib, short for Telegram Database Library, is Telegram's own cross-platform C++ client library: it implements the full MTProto stack, keeps a local encrypted database of chats and messages, and exposes everything through a single asynchronous JSON interface. It is the officially supported way to build a Telegram client without implementing the protocol yourself.

In a single sentence: everything a Telegram client needs, minus the user interface.

What it means

Writing a Telegram client from scratch means implementing MTProto: the key exchange, the message container format, sequence numbers, acknowledgements, data centre migration, file transfer in chunks, and the update stream with its gap detection. That is months of work before a single message appears on screen.

TDLib is Telegram's answer to that problem. It is a C++ library, maintained by Telegram, that implements all of it and exposes a single asynchronous interface on top. You send JSON objects in, you read JSON objects out, and the protocol is somebody else's problem.

The "Database" in the name is not decoration. TDLib keeps a local, encrypted store of chats, messages, users and downloaded files, so history browsing and search work without a round trip, and a restarted client resumes rather than re-syncing. That store is genuine state: it lives in a directory you configure, it grows, and it has to be treated as part of your deployment.

The interface, concretely

The surface area is tiny, which is the point. You create a client with td_create_client_id, push requests in with td_send, and pull results out with td_receive on a loop. A handful of methods that need no network are called synchronously with td_execute, notably log configuration.

Every object carries an @type field naming its constructor. Because responses and updates arrive on the same queue, correlation is explicit: attach an @extra value to a request, and TDLib echoes it back on the matching response. Anything arriving without an @extra is an unsolicited update.

Errors are ordinary objects too, not exceptions: {"@type":"error","code":400,"message":"CHAT_NOT_FOUND"}. The codes and messages are the same MTProto errors you would see through any other library, which means the flood wait handling you know still applies.

The authorization state machine

Nothing about TDLib surprises newcomers more than this: you cannot just call a method. The client starts in an unauthorised state and drives you through login by emitting updateAuthorizationState events, and requests fail until it reports readiness.

The sequence is listed in full further down this page. The important structural point is that it is event-driven rather than procedural: you do not ask "am I logged in", you react to the state TDLib tells you it is in. That also means the same code handles a fresh login, a resumed session, and a session that got revoked from another device, because all three arrive as state transitions.

What TDLib hides, and why that is a big deal

The most valuable abstraction TDLib provides is peer management.

In raw MTProto, referencing a chat requires an ID and an access hash that your specific account obtained, and getting that bookkeeping wrong is the classic multi-account bug. TDLib removes the problem: it keeps its own peer database and addresses everything with a normalised chat_id in the same shape the Bot API uses, negative and -100-prefixed for supergroups. See peer ID for that encoding.

It also handles file downloads with resumption, data centre migration for media stored away from your home data centre, secret chats, and the update-gap recovery that raw clients have to implement themselves. Each of those is a genuinely hard piece of work that TDLib has already done and tested against Telegram's own clients.

The costs, stated honestly

TDLib is the right answer for building a Telegram client. It is frequently the wrong answer for building a multi-tenant server product, and it is worth being specific about why.

  • It is a native dependency. You compile C++, or you ship a prebuilt binary matched to your base image's libc. Anyone who has moved a native library from a glibc image to a musl one knows how this goes.
  • State is per account, on disk. Every logged-in account needs its own database directory. Fifty customer accounts on one host means fifty growing directories to encrypt, back up and garbage-collect with optimizeStorage.
  • Instances are not free. Each client carries its own threads and memory. That is unremarkable for one account and very noticeable for a hundred.
  • You cross an FFI boundary. Managed languages talk to TDLib through the C interface, which means marshalling, manual lifetime management, and crashes that take the whole process with them rather than raising a catchable exception.

The alternative is a pure managed MTProto library: WTelegramClient on .NET, Telethon or Pyrogram on Python, GramJS on Node. Those give you the raw TL schema and make you manage peers and update state yourself, in exchange for a single-language deployment, cheap per-account instances and ordinary exceptions. Pinlyx runs WTelegramClient for exactly those reasons.

Why it matters

The choice of Telegram client library is a long-lived architectural decision, because the peer model, the update model and the error surface all differ between the options. Migrating later is not a refactor, it is a rewrite of the integration layer.

A useful rule: if you are building something a person opens and looks at, with chat history, search and media, TDLib's local database is doing most of your work for you and you should take it. If you are building a server that holds many accounts and mostly sends, receives and routes, a managed library with your own storage will be cheaper to operate for the whole life of the product.

Common mistakes

  • Calling methods before authorizationStateReady. They fail, and the failure looks like a bug in your request rather than a lifecycle issue.
  • Blocking inside the receive loop. That loop is the only path for both responses and updates. Do work on another thread.
  • Matching responses by order. The stream is asynchronous and interleaved with updates. Use @extra.
  • Deleting the database directory to fix things. That is a full re-sync, which costs traffic and rate-limit budget every time.
  • Leaving storage unmanaged. Downloaded media accumulates. Call optimizeStorage on a schedule.
  • Assuming TDLib makes bulk sending safe. It handles the protocol. Behaviour, pacing and flood waits are still on you.

Related concepts

  • MTProto: the protocol TDLib implements end to end.
  • Access hash: the bookkeeping TDLib takes off your hands.
  • Session string: the portable credential other libraries use where TDLib uses a database directory.
  • Userbot: the pattern TDLib is most often used to build.
  • Telegram Bot API: the hosted alternative, with none of the deployment cost and none of the capability.
  • Peer ID: the normalised chat identifiers TDLib exposes.

How Pinlyx handles it

Pinlyx does not run TDLib. The Telegram layer is built on WTelegramClient, a managed .NET MTProto implementation, which keeps the API image free of native client dependencies and lets one process hold many accounts cheaply. The trade-off is that peer caching, update state and error classification are ours to implement, which is why those get their own careful treatment: a per-account peer cache keyed on (account_id, peer_id), a rate limiter with configurable hourly and daily caps, and exponential back-off on FLOOD_WAIT. Teams evaluating TDLib for their own build usually reach the same fork we did: excellent for a client, expensive for a multi-tenant Telegram CRM.

Reference · the login state machine

Every TDLib client walks this path.

States arrive as updateAuthorizationState events. You react to them; you never poll for them.

1
authorizationStateWaitTdlibParameters

TDLib needs its configuration: api_id, api_hash, database directory, device model, system language. Nothing works until you answer this.

2
authorizationStateWaitPhoneNumber

Send a phone number with setAuthenticationPhoneNumber, or a bot token with checkAuthenticationBotToken. The same library drives both account types.

3
authorizationStateWaitCode

Telegram delivered a login code. Answer with checkAuthenticationCode.

4
authorizationStateWaitPassword

The account has two-factor authentication. Answer with checkAuthenticationPassword. This state simply never appears on accounts without 2FA.

5
authorizationStateReady

Logged in. Only now will ordinary requests succeed, and only now does the update stream carry real chat data.

6
authorizationStateClosed

The client shut down or was logged out. The instance is finished; create a new one rather than reusing it.

Cheat sheet · the JSON interface

Request, correlate, receive.

Three object shapes cover almost everything you will ever send or read.

// A request. @extra is yours: TDLib echoes it back on the response.
td_send(client_id, {
  "@type": "sendMessage",
  "chat_id": -1001234567890,
  "input_message_content": {
    "@type": "inputMessageText",
    "text": { "@type": "formattedText", "text": "Order shipped" }
  },
  "@extra": "job-77812"
})

// A response, matched by @extra
{ "@type": "message", "id": 33554432, "chat_id": -1001234567890,
  "@extra": "job-77812" }

// An error is a normal object, not an exception
{ "@type": "error", "code": 400, "message": "CHAT_NOT_FOUND" }

// An update: no @extra, arrives unsolicited on the same queue
{ "@type": "updateNewMessage", "message": { ... } }

// The receive loop, in any language with an FFI
while (running) {
  const event = td_receive(1.0);        // seconds
  if (!event) continue;
  if (event["@extra"]) resolvePending(event["@extra"], event);
  else                 dispatchUpdate(event);
}

One queue for everything

Responses and updates interleave. Correlate with @extra, never by arrival order.

State lives on disk

One encrypted database directory per account. Back it up, or pay for a full re-sync.

Native, not managed

A C++ binary tied to your base image's libc, reached across an FFI boundary.

Choosing a library

Client or server? That is the whole decision.

Building something a person opens: chat list, history, search, media, offline access. TDLib's local database is doing most of that work for you, and reimplementing it on top of a raw library is a poor use of a year.

Building a server that holds many accounts and mostly sends, receives and routes: a managed MTProto library keeps deployment boring, instances cheap and errors catchable. You will write your own peer cache and update-state handling, and for a multi-tenant product that is usually the smaller bill.

Watch out for

A protocol library is not a safety feature.

TDLib removes an entire class of protocol bugs, and that is worth a lot. It removes exactly none of the account risk. The same account sending the same volume of cold messages will collect the same flood waits and the same PEER_FLOOD whichever library sends them. Warm-up, pacing and rotation are not library features, they are operating decisions.

TDLib: FAQ

What to know before committing an integration to a native Telegram client library.

Four things, mostly. It maintains a local encrypted database of chats, messages and users, so history and search work offline. It resolves peers for you, which means you never touch access hashes. It handles data centre migration, file download resumption and reconnection internally. And it presents one stable, versioned interface instead of the raw TL schema, so a Telegram layer bump does not immediately break your code. What you give up in exchange is control and deployment simplicity.
No, and that is one of its biggest practical advantages. TDLib keeps its own peer database and addresses everything by a normalised chat_id in the same style the Bot API uses, with supergroups negative and prefixed. The per-account access hash bookkeeping that breaks so many hand-rolled multi-account systems is handled inside the library. The trade-off is that the database directory becomes state you must back up, migrate and encrypt, because losing it means re-syncing everything.
Yes. The authorization flow branches at the phone-number state: send a phone number for a user account, or call checkAuthenticationBotToken for a bot. A bot driven through TDLib is not subject to the hosted Bot API file limits, since it speaks MTProto directly, which is one of the less obvious reasons to choose it over api.telegram.org.
Everything is asynchronous over one queue. You call td_send with a JSON object carrying an @type field, and you read results with td_receive on a separate loop. Because responses and updates share the same stream, you correlate a reply to its request by putting an @extra value on the request and matching it on the response. A small number of methods are synchronous and are called through td_execute instead, notably log configuration and text-entity parsing.
Because TDLib is a native dependency with per-account state. You compile a C++ library, ship it with your container, bind to it through FFI, and give every logged-in account its own instance with its own database directory and threads. For one client that is fine. For a CRM holding dozens of customer accounts on one host, the disk footprint, the memory per instance and the FFI boundary all become operational work. Pure managed libraries such as WTelegramClient on .NET, or Telethon on Python, trade TDLib's conveniences for a much simpler deployment.
Not at all. TDLib is a client library, not a permission. An account driven by TDLib that sends 300 cold DMs an hour collects the same flood waits, the same PEER_FLOOD, and eventually the same ban as one driven by any other library. What TDLib does is remove protocol-level mistakes; behaviour is still entirely your responsibility.
Ready to ship

Skip the client library. Ship the CRM.

Pinlyx already runs the MTProto layer, the peer cache and the rate limiter, so your team can work on the product instead of the protocol.

Free forever plan · GDPR-ready · No credit card required

We value your privacy

We use cookies to improve our site, analyze traffic, and personalize ads. You can accept all, reject non-essential, or customize your choices. Read our Cookie Policy.