# teleproto — full documentation for LLMs > Telegram MTProto API client library written in TypeScript. Documentation for userbots, automation, sessions, events, and the raw TL surface. teleproto is a Telegram MTProto API client library written in TypeScript for Node.js. It drives real user accounts (userbots) and bots over Telegram's own binary protocol — the same surface Telegram Desktop speaks — not the limited HTTP Bot API. Ideologically inspired by GramJS and GramJS-compatible for most of the public API. npm package: `teleproto` (current version 1.229.0). Source: https://github.com/sanyok12345/teleproto. Docs: https://docs.teleproto.dev. API reference: https://ref.teleproto.dev. Server-side Node.js today — no browser, Deno or Bun build yet (browser support is a possible future direction). Pure JavaScript, no native build step. MIT license. ## Install & credentials ```bash npm install teleproto ``` Types ship in the package — no @types/teleproto. Subpath imports: `teleproto` (TelegramClient, Api, errors, Logger), `teleproto/sessions`, `teleproto/events`, `teleproto/errors`, `teleproto/tl`. Get `api_id` (number) + `api_hash` (string) at https://my.telegram.org → API development tools. These identify the *application*; the account logs in separately. Bots additionally need a bot token from @BotFather. ## Login (user account) ```ts import { TelegramClient } from "teleproto"; import { StringSession } from "teleproto/sessions"; const client = new TelegramClient(new StringSession(""), apiId, apiHash, { connectionRetries: 5, }); await client.start({ phoneNumber: () => ask("Phone: "), phoneCode: () => ask("Code: "), password: (hint) => ask("2FA password: "), // only if 2FA is set onError: console.error, }); console.log(await client.getMe()); console.log("Session:", client.session.save()); // persist this string ``` Next runs: `new StringSession(saved)` skips the whole flow. Every resolver is a callback (sync or async). Optional extra resolvers: `firstAndLastNames` (new-account signup), `emailAddress` + `emailVerification` (returns `{ code }` or `{ token }`), `reCaptchaCallback(siteKey) => token`. Bot login: `await client.start({ botAuthToken: process.env.BOT_TOKEN })`. QR login: `client.signInUserWithQrCode({ apiId, apiHash }, { qrCode, password?, onError, abortSignal? })` — the `qrCode` resolver receives `{ token, expires }` each time Telegram rotates the token (~30 s); render `tg://login?token=`. Pass an AbortController's `abortSignal` to cancel (rejects with AbortError). Logout: `await client.logOut()` — revokes the session server-side AND wipes local session storage. start() on a dead session throws a typed error: `SessionRevokedError`, `AuthKeyUnregisteredError`, `UserDeactivatedError` (all instanceof `UnauthorizedError`). Catch, wipe the saved session, re-login. ## Sessions From `teleproto/sessions`: - `StringSession` — serializes to one base64 string ("1"-prefixed). Best for env vars / secret managers / serverless. Also reads legacy 352-char GramJS/Telethon strings (IPv4 only) for migration. - `StoreSession` — disk-backed directory in cwd (`new StoreSession("my-account")` → ./my-account/), writes through on every change. The name "session" is reserved and throws. Never share one directory between processes. - `MemorySession` — nothing persisted; tests and throwaway scripts. - Custom: extend abstract `Session`. `getInputEntity` and `processEntities` may return Promises, so a session can live fully on Redis/Postgres — storage errors there are logged, never fail the triggering request. A session stores per-DC auth keys, current DC address, the entity cache (id → access_hash), and the testServers flag. A session string IS a login token — anyone holding it controls the account. Env var / secret manager, never git, never logs. ## Sending & messages All high-level methods live on TelegramClient and take an entity first (username, numeric id, or InputPeer): ```ts await client.sendMessage("me", { message: "bold", parseMode: "html", linkPreview: false, silent: true, // both optional // also: replyTo, file, buttons, schedule }); await client.editMessage(peer, { id: msgId, message: "edited" }); await client.deleteMessages(peer, [id1, id2], { revoke: true }); // revoke = for everyone await client.forwardMessages("me", { messages: [1234], fromPeer: "@channel" }); await client.pinMessage(peer, msgId, { notify: false, pmOneside: true }); await client.sendReaction(peer, msgId, "👍", /* big */ true); // null clears await client.markAsRead(peer, msgId?, { clearMentions: true }); // NOT sendReadAcknowledge (GramJS name) for await (const m of client.iterMessages("@channel", { limit: 200, search: "release" })) { console.log(m.id, m.message); } const page = await client.getMessages(peer, { limit: 50 }); // TotalList — has .total ``` Parse modes: `"html"` and `"md"`. Set once with `client.setParseMode("html")` or per call. HTML supports
  
(→ newline), , tg://user?id=… mentions, expandable
, , and decodes all HTML5 entities — LLM/template output passes through unmodified. ## Files & media ```ts await client.sendFile("me", { file: "./report.pdf", caption: "…" }); // file: path | Buffer | ReadStream | URL | uploaded handle // also: forceDocument, voiceNote, videoNote, mimeType, attributes, thumb, // workers (parallel upload chunks, use 4–8 for >10 MB), progressCallback const handle = await client.uploadFile({ file: "./big.pdf", workers: 4 }); // upload once, send many const buf = await client.downloadMedia(message); // Buffer await client.downloadMedia(message, { outputFile: "./out.jpg" }); // or path/Writable const avatar = await client.downloadProfilePhoto("@user", { isBig: true }); ``` Client-level tuning: `maxConcurrentDownloads`, and `downloadPool: { poolSize, workers, requestDeadlineMs }` (raise requestDeadlineMs on slow networks). Transfers chunk transparently; uploads use a sliding window so one slow part doesn't stall the batch; idle media sessions stay warm 60 s. ## Events ```ts import { NewMessage, NewMessageEvent } from "teleproto/events"; client.addEventHandler(async (event: NewMessageEvent) => { if (event.message.message === "ping") await event.message.reply({ message: "pong" }); }, new NewMessage({ incoming: true /* also: chats, fromUsers, pattern */ })); ``` Builders from `teleproto/events`: NewMessage, EditedMessage, DeletedMessage, CallbackQuery, InlineQuery, Album, ChatAction, MessageRead, UserUpdate, Raw (catch-all — receives every TL update). No middleware, no dispatcher, no FSM — plain handlers. `client.removeEventHandler(cb, builder)` detaches. After a restart, `await client.catchUp()` replays updates missed offline (depends on stored pts/qts; a too-stale session gets UpdatesTooLong). ## Entities & peers Three shapes: `Peer` (id only — what updates carry), `InputPeer` (id + access_hash — what requests want), full entity (`Api.User`/`Chat`/`Channel` — what getEntity returns). - `client.getInputEntity(x)` — cache-first, cheap; feed this to raw calls. - `client.getEntity(x)` — full object, may hit the network. - `client.getMe()`, `client.getPeerId(x, addMark?)`. access_hash is per-account — never reuse across sessions. IDs are `bigInt` instances from npm `big-integer`, NOT native BigInt: print with .toString(), JSON.stringify silently emits {} for them. Entity cache is bounded by default (LRU, 4096 peers/segment, optional TTL): `entityCache: true | false | { max, ttl }`; `entityCache: {}` restores unbounded; inspect via `client.entityCache`. "Could not find the input entity" → the account has never seen that peer; iterate dialogs once or have the account interact with it first. ## Raw TL API Two equivalent forms — the typed facade (preferred) and invoke: ```ts const cfg = await client.api.help.getConfig({}); await client.api.messages.sendMessage({ peer, message: "hi", silent: true }); import { Api } from "teleproto"; await client.invoke(new Api.messages.SendMessage({ peer, message: "hi" })); ``` `client.api..({...})` mirrors the entire TL schema, camelCase, full types for params and results, core.telegram.org docs as JSDoc. Same retry/FloodWait path as invoke. Use `new Api.*` constructors for TL *objects* you build by hand (Api.InputPeerUser, Api.PeerChannel, …) — or pass plain literals with a `_` tag (mtproto-core style), which coerce to the generated class at serialization: ```ts await client.api.payments.getPaymentForm({ invoice: { _: "inputInvoiceSlug", slug: "AbCdEf012345" }, }); ``` `_` is the TL constructor name (inputPeerSelf, inputInvoiceSlug); it's optional when the expected type has exactly one constructor, and an ambiguous literal throws listing the candidates. `randomId` fields auto-generate when omitted. Results are TL unions — narrow with instanceof before reading fields. `tl` namespace is an alias of `Api`. Current TL layer: 228. ## Errors Every Telegram RPC error is a generated class — catch with instanceof, never string-match: ```ts import { errors } from "teleproto"; // or named imports from "teleproto/errors" try { await client.sendMessage(peer, { message: "hi" }); } catch (err) { if (err instanceof errors.FloodWaitError) await sleep(err.seconds * 1000); else if (err instanceof errors.ChannelPrivateError) { /* not a member */ } else if (err instanceof errors.SlowModeWaitError) { /* .seconds */ } else throw err; } ``` Hierarchy by HTTP code: RPCError → BadRequestError (400), UnauthorizedError (401), ForbiddenError (403), NotFoundError (404), FloodError (420), ServerError (500), TimedOutError (503), InvalidDCError (`.newDc` on UserMigrateError/PhoneMigrateError/…; handled internally). Codes newer than the build fall back to the HTTP-code base class; `.errorMessage` always holds the raw server string ("FLOOD_WAIT_42"). Frozen accounts: FrozenMethodInvalidError, FrozenParticipantMissingError (old FrozenError / FrozenMethodError / FrozenParticipantError names remain as deprecated aliases). `floodSleepThreshold: 60` on client params auto-sleeps short FloodWaits. Never auto-retry BadRequestError/ForbiddenError. ## Production notes - Long-running Node 18+ process. NOT serverless/edge — MTProto needs the socket to stay open. (This applies to the bot process, not to hosting docs.) - Proxies: SOCKS5 (`proxy: { ip, port, socksType: 5, username?, password? }`) and MTProxy (`proxy: { ip, port, MTProxy: true, secret }` — both "dd" padded and "ee" fake-TLS secrets auto-detected). No HTTP proxy. `useIPV6: true` for v6-only networks. - Reconnects are automatic (`autoReconnect: true` default; `connectionRetries`, `retryDelay`). Connection state events reach handlers. Connects time out and fail over to alternate DC addresses. - Multi-account = one TelegramClient per account, each with its own session. - Logging: `client.setLogLevel("warn")`; or construct `new Logger(level)`, assign `logger.handler = ({ level, message, error, date }) => …` and pass as `baseLogger` to pipe records into pino/winston. - Graceful shutdown: `await client.disconnect()` (keeps auth key for next boot). `destroy()` also discards the auth key. - `testServers: true` targets Telegram's test DCs for CI. - Userbot automation violates a strict reading of Telegram ToS; aggressive patterns (mass DMs, mass joins, scraping) get accounts frozen. Use a number you can afford to lose. ## Versioning Version scheme is 1.. — e.g. 1.229.0 tracks TL layer 229. Update with `npm i teleproto@latest` to get new Telegram schema features. ## Docs map https://docs.teleproto.dev/ (introduction) · /installation · /quick-start · /authentication · /sessions · /migrating-from-gramjs · /concepts/entities · /concepts/updates · /concepts/errors · /concepts/raw-api · /concepts/bot-api-vs-mtproto · /guides/messages · /guides/media · /guides/chats · /guides/events · /production · /faq · internals (how the library works inside): /internals/connection · /internals/auth-keys · /internals/entity-cache · /internals/file-transfer · /internals/updates · API reference: https://ref.teleproto.dev