# gotd/td: a pure Go MTProto client for Telegram users and bots

> A generated Telegram API layer, a hand-written transport stack, and a testing story that runs against real servers in CI plus a canary bot in production.

**gotd/td** — Telegram client, in Go. (MTProto API)

- Repository: https://github.com/gotd/td
- Website: https://gotd.dev
- Stars: 2,358 · Forks: 209
- Language: Go
- License: MIT
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/gotd-td

## A low level client and the warning that comes with it

The project describes itself as a Telegram MTProto API client in Go for users and bots, and the second line of that description matters more than the first. The README says plainly that gotd is a pretty low-level library, and points readers toward GoTGProto if they want session strings, stored peers and extracted chat or user ids.

That distinction is the whole evaluation. TDLib, the official Telegram library, is a higher-level C++ library with its own opinions about state. gotd gives you the protocol. The README sets the goal as a stable, performant and safe client for Telegram in pure Go with a simple and convenient API and feature parity with TDLib.

Before any of that, the README asks readers to read the How To Not Get Banned guide in .github/SUPPORT.md. That ordering is deliberate and worth honouring. A Telegram client that mishandles rate limits or retries gets an account restricted, and the repository treats that as a design constraint rather than an operational detail. It is reinforced elsewhere in the feature list by rate limiting and FLOOD_WAIT middleware and by graceful request cancellation via context.

The project also states it is fully non-commercial and not affiliated with any commercial organization including Telegram LLC. There are 2348 stars, 211 forks and 15 open issues, the last push was 2026-09-21, and the module is MIT licensed with a Go 1.25.0 floor.

Chat support runs through Telegram in English, Russian and Chinese, with an online count badge, which tells you the user base is international.

## Getting a client running and authenticating a bot

Installation is a single go get, and the README is upfront that documentation for the `tg` package does not render on pkg.go.dev because of limitations there, so it hosts its own reference at ref.gotd.dev instead. Worth knowing before you conclude the package is undocumented.

```console
go get github.com/gotd/td
```

The usage snippet establishes a pattern you will see everywhere in the library. You construct a client with an app ID and app hash from the Telegram API developer portal, then call Run with a context and a function. The client is only valid while that function has not returned and the context is not cancelled, which is stated in a comment in the snippet itself:

```go
client := telegram.NewClient(appID, appHash, telegram.Options{})
if err := client.Run(context.Background(), func(ctx context.Context) error {
	api := client.API()
	return nil
}); err != nil {
	panic(err)
}
```

Bot authentication is one line, with the token taken from BotFather:

```go
if err := client.Auth().Bot(ctx, "token:12345"); err != nil {
  panic(err)
}
```

User authentication is more involved and uses td/telegram/auth.Flow, which composes authenticators. The README snippet shows a code prompt callback reading from stdin, and a note about using the ssh/terminal package to read the password without echoing it. Then it wraps a phone number, password and authenticator in an auth.Constant call with auth.SendCodeOptions, and runs the flow against client.Auth(). The comment in the snippet flags an easy mistake: if the account does not require a 2FA password, use telegram.CodeOnlyAuth instead of telegram.ConstantAuth.

## The directory tree is a map of the protocol

The tree at the root of the repository is unusually legible, because each directory corresponds to a layer you would otherwise have to hunt for in documentation.

mtproto/ is the protocol itself, and internal/mtproto/_data/public_keys.pem is where the vendored Telegram public keys live. exchange/ handles the key exchange and cipher setup. crypto/ holds the cryptographic primitives. rpc/ and proto/ cover the request layer and the encoding. transport/ is where the wire transports live, and wsutil/ sits beside it for the WebSocket path that the feature list says works in WASM. session/ is pluggable session storage. pool/ is connection pooling. telegram/ holds the user-facing package with the helpers.

The generated layer has its own cluster of directories. tg/ is the generated types package, generated by ./cmd/gotdgen from the gotd/tl parser with embedded official documentation from the Telegram schema. tgacc/ is a generated convenience accessor layer, tgerr/ holds error types, tgmock/ and tgtest/ are testing aids, tgtrace/ and oteltg/ handle tracing, and tmap/ handles maps. tdjson/ is for JSON conversion and tdp/ appears to be a decoding helper. gen/ holds the generator itself, _schema/ holds the downloaded schema files, and _fuzz/ plus _tools/ are the fuzzing and tooling module directories.

Calls are supported through pion/webrtc, and the feature list calls out voice and video calls, both 1:1 and group calls, with a call example and a groupcall example in the examples directory. There are also examples for account-banned, bg-run, bot-auth-manual, bot-echo, bot-inline, bot-upload, deeplink, dialogs, get-participants, gif-download, mtproxy-connect, pretty-print, rich-message, save-media and secure-password.

The examples directory is the fastest way to understand intended usage, because each one is a small complete program rather than a fragment.

## Generated from three schema sources, and how you keep it in sync

The Makefile is where the update workflow is written down, and it is short enough to read in one go. Two targets matter most.

The first is download_schema, which fetches schema files from three upstream repositories and merges them:

```bash
go run ./cmd/dltl -base https://raw.githubusercontent.com/tdlib/td -branch master -dir td/generate/scheme -f telegram_api.tl -o _schema/tdlib.tl
go run ./cmd/dltl -base https://raw.githubusercontent.com/telegramdesktop/tdesktop -branch dev -dir Telegram/SourceFiles/mtproto/scheme -f api.tl -merge _schema/legacy.tl -o _schema/telegram.tl
```

So the schema is not a single file. It is layered from TDLib's telegram_api.tl, tdesktop's api.tl and a legacy schema file kept in the repository for compatibility. Telegram's API history is long enough that you cannot just take the newest one, which is why the merge step exists.

The second target is download_public_keys, which regenerates the vendored key file:

```bash
go run ./cmd/dlkey -o internal/mtproto/_data/public_keys.pem
```

That is the mechanism behind the feature list item about vendored Telegram public keys being kept up to date. You do not hand-edit the pem file.

There are also targets for the end-to-end test schema, taken from secret_api.tl into _schema/encrypted.tl, and for the TDLib schema, from td_api.tl into _schema/tdapi.tl. Those two exist for the end-to-end testing story described below.

The consistency check is check_generated, which runs generate and then fails if git diff is not empty. That single target is how a repository with thousands of generated lines guarantees the committed output matches the input. test and coverage targets wrap go.test.sh and go.coverage.sh.

## Testing against real servers, a Go server, and a canary

The feature list describes the testing setup more concretely than most libraries do, and it is the strongest part of the README. The claim is end-to-end testing with a real Telegram server in CI, end-to-end testing with a gotd Telegram server implemented in pure Go, lots of unit testing, fuzzing, and a 24/7 canary bot in production that tests reconnects, update handling, memory leaks and performance.

The pure Go Telegram server is the interesting piece, because it is what makes end-to-end tests possible without spending rate limits on a live account. That server needs its own schema, which is where the secret_api.tl download comes in, and a test-only account layer, which is what tgacc/ is for. tgmock/ and tgtest/ cover the mocked and fixture-driven cases, and _fuzz/ is a separate module for fuzz targets.

A canary that runs continuously and specifically checks for memory leaks and reconnect handling is a stronger statement than a coverage badge. It is also the reason the README feels comfortable claiming 150kb per idle client and the ability to handle thousands of concurrent clients. Those numbers are tied to a test setup that watches for the failure modes.

The dependency list supports the picture. github.com/stretchr/testify for assertions, github.com/rogpeppe/go-internal for test tooling, and github.com/go-faster/xor alongside memguard, which is a memory-locking library, plus crypto/x/crypto. memguard being a direct dependency rather than something vendored suggests secrets in memory are treated as a real concern, which lines up with the claim of a secure PRNG and replay attack protection in the security guidelines conformance list.

CI configuration lives in .github/ along with SECURITY.md and SUPPORT.md, and there is a .codecov.yaml at the root, a .golangci.yml for linting, a CLAUDE.md and an ISSUES.md. ARCHITECTURE.md and ROADMAP.md are both linked from the README and are where the design rationale should be.

## Release cadence and what recent versions changed

The releases tell you the maintenance shape. v0.162.0 published 2026-09-18, v0.161.0 on 2026-07-14, and v0.160.0 on 2026-07-07. So this is a v0 module that still ships breaking changes, on a cadence of weeks rather than months.

The dependency bot dominates the changelogs, which is expected for a library whose generated layer tracks several upstream schemas. In v0.161.0 the entries are almost entirely bumps: golang.org/x/tools, ogen, goldmark, plus the Telegram schema update. v0.160.0 follows the same pattern with actions/checkout, actions/cache and x/tools bumps.

Two entries are worth pulling out because they show what feature work looks like here. v0.161.0 includes an MTProto-over-HTTP transport using http_wait long-polling, attributed to a dcs prefix, which is a real addition to the transport layer rather than a schema refresh. v0.162.0 includes a change allowing the schema layer to be overridden in initConnection, which is the sort of API that only matters if you are integrating with something that does not follow the default layer.

v0.162.0 also contains the recurring schema update to the latest layer, submitted by gotd-bot, which is the automated half of the loop that download_schema and check_generated support.

So the update story is: a scheduled or bot-driven schema pull, regeneration, a diff check in CI, dependabot bumps, and a human adding features at the transport and helper layers between schema refreshes. If you depend on gotd, that is the rhythm you are signing up for, and v0.x means you should read the changelog rather than assume semver compatibility.

## Conclusion

gotd/td is the closest thing to a reference implementation of MTProto in Go, and the repository is organised to make that legible. The directory layout maps almost one to one onto the protocol: mtproto/, rpc/, exchange/, transport/, crypto/, session/, tg/, with codegen isolated under gen/ and _schema/. The performance claims are specific enough to be checkable, 150kb per idle client and a 24/7 canary testing reconnects and leaks, so treat those as the interesting parts. It is deliberately low level, which is the main thing to understand before adopting it. The current release is v0.162.0 from 2026-09-18 and the last push was 2026-09-21, so the v0 line is moving regularly. Read ARCHITECTURE.md and the ban guide in SUPPORT.md before writing anything that talks to Telegram.

## FAQ

### What is gotd?

gotd is a Telegram client library written in pure Go, implementing the MTProto 2.0 protocol for both user and bot accounts. The project describes it as a low-level library and points to GoTGProto as a higher-level helper if you want session strings and stored peers. It is MIT licensed, non-commercial, and not affiliated with Telegram LLC.

### How do I authenticate a bot with gotd?

Take a token from BotFather and call `client.Auth().Bot(ctx, "token:12345")` inside the function passed to `client.Run`. The client is only valid while that function has not returned and the context has not been cancelled, so the call belongs inside it rather than after it.

### Why does gotd use several Telegram schema files?

The Makefile's download_schema target pulls telegram_api.tl from TDLib and api.tl from tdesktop, then merges tdesktop's output with a legacy schema file kept in the repository. Telegram's API history is long enough that the newest schema alone is not sufficient, so the merged layer is what the generator consumes.

### How does gotd test against Telegram?

Three ways, according to the README: end to end against a real Telegram server in CI, end to end against a gotd Telegram server implemented in pure Go, and a 24/7 canary bot in production that tests reconnects, update handling, memory leaks and performance. Unit tests and fuzzing sit alongside, with fuzz targets in a separate _fuzz/ module.

### Can I avoid getting banned for too many requests?

The README asks you to read the How To Not Get Banned guide in .github/SUPPORT.md before using the library. On the technical side it ships rate limiting and FLOOD_WAIT middleware in gotd/contrib, plus graceful request cancellation through context, so retries can be shaped rather than fired blindly.

## Sources

- [gotd/td on GitHub](https://github.com/gotd/td)
- [License: MIT](https://github.com/gotd/td/blob/main/LICENSE)
- [Project website](https://gotd.dev)
- [README](https://github.com/gotd/td/blob/main/README.md)
- [Releases](https://github.com/gotd/td/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gotd-td
