MTProto Extensions
Blah serves Telegram’s MTProto API unchanged and adds a small namespace of its own, blah.*.
Everything on this page is in that namespace; everything else behaves as Telegram documents it.
The authoritative schema is Schemas/blah.tl in the Blah repository.
Conventions
- A constructor id is the CRC32 of its declaration, computed exactly as for Telegram’s schema, so any TL toolchain can generate a client for it.
blah.*declarations use Telegram types such asInputPeer,UserandUpdate. Those are encoded at the Telegram layer the connection announced.- Profiles, device proofs and challenges are canonical CBOR carried in
bytesfields. Session device IDs are raw 32-byte digests; their lists use ordinary TL vectors. - The device identity RPCs need a DC with a federation identity. Without one they fail with
FEDERATION_NOT_CONFIGURED.
Blah layers
The blah.* declarations are versioned by the Blah layer, not by Telegram’s. The current
Blah layer is 1.
blah.invokeWithLayer#fb8f651a {X:Type} layer:int query:!X = X;blah.invokeWithLayer is the Blah counterpart of Telegram’s invokeWithLayer. It announces
which Blah layer the client speaks and runs query under it:
- The two layers are independent.
invokeWithLayergoverns Telegram’s declarations, and the Telegram types thatblah.*declarations carry.blah.invokeWithLayergoverns only theblah.*declarations. - The server remembers the announced layer for the connection, as it does Telegram’s. Wrap the first query you send on a connection. Later queries don’t need the wrapper.
- The wrapper can go anywhere in the wrapper chain and around any query, Telegram’s included.
For example,
invokeWithLayer(232, initConnection(…, blah.invokeWithLayer(1, query))). - A connection that never announces a Blah layer is served layer 1. Clients should still announce the layer they were built against.
- The server serves the newest Blah layer it supports that isn’t newer than the announced one.
- A layer below 1 is refused with
400 BLAH_LAYER_INVALIDuntil the connection announces a valid layer.
Device identity
The layer-1 invocation RPCs let a Diem device authorize one exact MTProto call.
blah.config#642ad48e layer:int dc_identity:bytes discovery_domain:string = blah.Config;blah.identityChallenge#2b2e0aab data:bytes expires_at:int = blah.IdentityChallenge;blah.getConfig#76372b1a = blah.Config;blah.requestIdentityChallenge#dceefd53 domain:string = blah.IdentityChallenge;blah.invokeWithIdentityProof#61b4c668 {X:Type} proof:bytes query:!X = X;blah.getConfig gives the supported Blah layer and DC identity. A client requests a
challenge for a domain, then signs BlahDiem’s canonical CBOR InvocationStatement
with a device listed in that domain’s current Diem profile. The statement carries the
challenge and SHA-512 of the exact query, inside a Diem proof. query bytes are the exact serialized bytes
inside blah.invokeWithIdentityProof, including any inner wrappers. The challenge
is single use and binds the domain, DC identity, encryption key and session.
The DC rejects malformed or private domains at challenge issuance.
On a device-authenticated DC, account creation and deletion; bot and sticker-set
creation; sticker-set deletion; and bot token rotation require the relevant identity’s
proof. Deleting a channel, group or community, or transferring its ownership, requires the
chat identity’s proof once the chat has one.
Bot deletion and transfer are disabled until they have proof-capable RPCs. The
@bot manager’s create and token commands are disabled there too.
Identity proof errors
Clients should map these 400 RPC errors from identity invocation and login completion:
| Code | Meaning |
|---|---|
IDENTITY_PROFILE_UNAVAILABLE | The challenged domain’s profile could not be fetched, including HTTP, content-type or size failures. |
IDENTITY_PROFILE_INVALID | The fetched profile is malformed, fails verification, is outside its validity or drops an unexpired device without a newer identity-certified generation. |
IDENTITY_PROFILE_STALE | Its revision or device generation is older than, or conflicts with, this DC’s accepted state. |
IDENTITY_HOME_INVALID | The home delegation is missing, expired, unknown, for another DC or an older hosting. |
IDENTITY_DEVICE_INVALID | The served profile does not certify the signing device, its signature fails, or login tries to replace a live authorization’s bound device. Publish new enrollment before login; an existing authorization can retry only with its original device. |
IDENTITY_CHALLENGE_INVALID | The statement/query, DC or connection binding does not match, or the challenge expired or was consumed. |
IDENTITY_ACCOUNT_MISMATCH | The signed home names an account where none is allowed, or disagrees with the identity’s account at this DC. |
SESSION_ALREADY_AUTHORIZED | The key already has an authorization that this login cannot reuse, including one for another account. Log out first or start with a fresh auth key; a fresh proof for the same live account and device can retry sign-in, but not signup or device linking. |
IDENTITY_PROOF_INVALID | The proof is malformed or verification failed for another reason. |
Publisher admission pressure returns 420 FLOOD_WAIT_5. These codes identify a failed
check without exposing proof bytes, key material or another account’s details.
Session devices
blah.sessionDevice#71d54a81 hash:long device:bytes = blah.SessionDevice;blah.sessionDevices#bc65209e sessions:Vector<blah.SessionDevice> retired:Vector<bytes> = blah.SessionDevices;blah.getSessionDevices#0ea8e1b2 = blah.SessionDevices;blah.getSessionDevices requires an ordinary signed-in user session, including a device
without the seed. sessions contains this account’s live local Diem bindings. Each hash
matches account.getAuthorizations (zero means the caller), and device is the raw 32-byte
Diem device ID, not its public key. Unbound and imported-only authorizations are omitted.
retired contains the identity’s 32-byte device IDs still recorded as retired at this DC.
Retirement is removed when an accepted profile no longer lists the device.
One device backs one local authorization for its entire life. Ending that session through logout, reset, idle expiry or profile removal retires the device; a new sign-in enrolls a fresh one. Accepting a profile that drops a device durably terminates its session and push registrations and queues cross-DC revocation. Removing an unexpired device requires a newer identity-certified generation; an ordinary same-generation revision cannot revoke it.
Seed holders terminate other sessions by mapping their hashes, publishing a profile without
those devices, then calling the ordinary account.resetAuthorization or
auth.resetAuthorizations. Reset fetches and accepts the current published profile. If an
original selected Diem session remains, reset fails without itself deleting listed victims.
Diem victims have no fresh-reset delay; other authorization kinds retain Telegram reset
policy. Self-logout always needs no roster publication and retires the caller’s device.
An already absent noncurrent reset target succeeds for a locally Diem-bound caller; zero
and the caller’s own hash remain invalid.
400 error | Client action |
|---|---|
DEVICE_KEY_IN_USE | This device already backs another live authorization for the identity at this DC. Enroll a fresh device for the new session. |
DEVICE_KEY_RETIRED | This device’s session has ended. Enroll a fresh device; a new proof cannot revive it. |
DEVICE_STILL_LISTED | Publish the victim-device removal first. Wait briefly and retry the reset RPC once for publisher propagation; if it still fails, show the error. |
After sign-in, holders prune exactly those retired IDs that their current profile still
lists. Never infer retirement from absence in sessions: enrollment or QR approval may be
in progress. Non-holders can log themselves out and enter matching recovery words to gain
roster-management authority; they have no other-session termination controls.
Device linking
blah.deviceLink#0e8e43c5 flags:# request:bytes response:flags.0?bytes = blah.DeviceLink;blah.getDeviceLink#0df49ce9 token:bytes = blah.DeviceLink;blah.invokeWithDeviceLink#f7e9bfa5 {X:Type} link:bytes query:!X = X;An unauthorized device wraps auth.exportLoginToken with its BlahDiem
DeviceLinkRequest. An existing user session reads the request using blah.getDeviceLink,
certifies the new device and publishes its updated profile through the identity’s publisher.
Approval requires seed custody. Allow this device to approve sign-ins defaults to off;
when enabled, an identity-bound sealed seed travels inside the encrypted grant. Device
signing keys remain local.
It approves with blah.invokeWithIdentityProof(proof, blah.invokeWithDeviceLink(sealedResponse, auth.acceptLoginToken(token))). The fetched
profile must certify the new device, or approval returns DEVICE_LINK_PROFILE_STALE.
The requesting key receives updateShort(updateLoginToken) and can poll for the opaque
response. After opening it and saving custody, it completes with a proof-wrapped
auth.exportLoginToken, returning auth.loginTokenSuccess or SESSION_PASSWORD_NEEDED
for the usual SRP continuation. Approval satisfies the per-sign-in email code. No signup
or token migration is supported. Bare export, accept and import return DEVICE_LINK_REQUIRED;
a link wrapper on other methods returns DEVICE_LINK_INVALID.
Resource identity
Channels, supergroups, communities, bots, sticker sets and custom emoji sets have their own Diem identities. The creator publishes the proposed profile on its advertised domain before proof-wrapped creation, then publishes the allocated local number. The resource identity authorizes deletion, ownership transfer or token rotation where the operation is available.
A channel, supergroup or community may instead be created without a proof. It then stays on
its home DC: only accounts homed there can join, other DCs cannot operate on it, and it
cannot take a username. Its creator binds an identity later by wrapping
channels.editTitle, channels.editPhoto, messages.editChatTitle,
messages.editChatPhoto or messages.editChatAbout in blah.invokeWithIdentityProof with
a proposed (unnumbered) channel identity, then publishes the allocated number as after
creation. The binding is permanent. Refused operations fail with CHAT_IDENTITY_REQUIRED.
Profiles
blah.resourceProfile#c0d31b67 id:long profile:bytes = blah.ResourceProfile;blah.getUserProfile#517800c0 id:Vector<InputUser> = Vector<blah.ResourceProfile>;blah.getChannelProfile#8344006d id:Vector<InputChannel> = Vector<blah.ResourceProfile>;blah.getStickerSetProfile#ff5c6967 id:Vector<InputStickerSet> = Vector<blah.ResourceProfile>;| Declaration | Description |
|---|---|
blah.resourceProfile | A DC-local resource ID and the exact canonical CBOR of its stored signed Diem profile. |
blah.getUserProfile | Profiles known for users, bots and the DC service account, including inputUserSelf. |
blah.getChannelProfile | Profiles known for broadcasts, supergroups and communities; monoforums are omitted. |
blah.getStickerSetProfile | Profiles known for sticker and custom-emoji sets, addressed by ID and access hash. Sets hosted on this DC also accept their short name. Built-in set constructors are refused. |
Each call accepts at most 100 inputs (400 LIMIT_INVALID otherwise), validates the usual
input credentials, and returns profiles in request order with duplicate IDs collapsed.
Empty input returns an empty vector. Resolved resources without a known identity, including
managed or ended accounts, are omitted; unknown IDs and invalid inputs fail the call.
A channel absent from the result has no identity this DC has verified, so this DC treats it as
limited to its home DC. A monoforum follows its broadcast.
The answer uses only this DC’s durable knowledge, including verified foreign identities,
and performs no publisher fetch or cross-DC call. It returns the latest stored profile even
if its validity has lapsed; clients verify signatures and validity themselves.
Managed accounts
An application creates and runs managed accounts. It calls these RPCs signed in as its own bot, and the Application API offers the same operations over HTTP. Accounts are named by their plain user id. An account that isn’t the calling application’s reads as though it doesn’t exist.
blah.managedAccount#1751086a flags:# user_id:long first_name:string last_name:flags.0?string about:flags.1?string username:flags.2?string account_ttl_days:int app_id:int = blah.ManagedAccount;blah.managedAccounts#f6f508e0 count:int accounts:Vector<blah.ManagedAccount> = blah.ManagedAccounts;blah.managedAccountTokens#8627a9eb flags:# user_id:long token:flags.0?string super_token:flags.1?string = blah.ManagedAccountTokens;blah.createdManagedAccount#994e6834 account:blah.ManagedAccount tokens:blah.ManagedAccountTokens = blah.CreatedManagedAccount;
blah.createManagedAccount#aaeecc53 flags:# first_name:string last_name:flags.0?string about:flags.1?string username:flags.2?string account_ttl_days:flags.3?int = blah.CreatedManagedAccount;blah.getManagedAccounts#f3de69b1 offset_id:long limit:int = blah.ManagedAccounts;blah.getManagedAccount#6b0d7dcb user_id:long = blah.ManagedAccount;blah.editManagedAccount#01c93b66 flags:# user_id:long first_name:flags.0?string last_name:flags.1?string about:flags.2?string username:flags.3?string account_ttl_days:flags.4?int = blah.ManagedAccount;blah.rotateManagedAccountTokens#5feee2af flags:# user_id:long token:flags.0?true super_token:flags.1?true = blah.ManagedAccountTokens;blah.deleteManagedAccount#4e12ac8d user_id:long = Bool;| Declaration | Description |
|---|---|
blah.managedAccount | An account’s profile, its idle auto-delete period in days and the application that owns it. |
blah.managedAccounts | One page of accounts, plus the application’s total count. |
blah.managedAccountTokens | Newly issued tokens. They are shown only once. The ordinary token talks, and the super_token changes the profile, handle, privacy and chat membership. |
blah.createdManagedAccount | A new account and both of its tokens. |
blah.createManagedAccount | Creates an account. |
blah.getManagedAccounts | Lists accounts in pages of up to 100, starting after offset_id (0 for the first page). |
blah.getManagedAccount | Returns one account. |
blah.editManagedAccount | Edits an account. An omitted field is left unchanged, and an empty string clears the field. |
blah.rotateManagedAccountTokens | Issues new tokens and ends the sessions the old ones started. Naming neither token rotates both. |
blah.deleteManagedAccount | Deletes an account. |
History import and bridges
A bot with the connect trait, such as the Telegram bridge’s, uses these RPCs to mirror
another service’s chat into a channel or supergroup hosted on the same DC. Blah records which
source ids the copies came from, so replies, edits and deletions keep pointing at the right
copies across batches and restarts.
blah.importSourceTelegram#582d59c2 chat_id:long = blah.ImportSource;blah.importedMedia#7af7f679 src_msg_id:int media:InputMedia = blah.ImportedMedia;blah.importedRichMedia#99ca67b6 flags:# src_msg_id:int files:Vector<blah.ImportedRichFile> quote_rich:flags.0?Vector<RichText> = blah.ImportedMedia;blah.importedRichFile#79a1f09e src_id:long media:InputMedia = blah.ImportedRichFile;blah.importedDocument#da716d20 src_document_id:long document:InputDocument = blah.ImportedDocument;blah.importedStickerSet#b10752b9 flags:# src_set_id:long stickerset:InputStickerSet src_document_ids:flags.0?Vector<long> = blah.ImportedStickerSet;blah.importedStickerSets#1edd7c9f sets:Vector<blah.ImportedStickerSet> = blah.ImportedStickerSets;blah.importedMessage#cb8245eb src_msg_id:int id:int = blah.ImportedMessage;blah.importedReply#6024f939 flags:# src_msg_id:int peer:InputPeer msg_id:int quote_text:flags.0?string quote_entities:flags.1?Vector<MessageEntity> quote_offset:flags.2?int = blah.ImportedReply;blah.importedTopic#fb238df3 flags:# closed:flags.2?true hidden:flags.3?true pinned:flags.4?true deleted:flags.5?true src_top_msg_id:int title:flags.0?string icon_color:flags.1?int icon_emoji_id:flags.6?long top_msg_id:flags.7?int = blah.ImportedTopic;blah.importedUpdates#eaa0d8dc imported:int edited:int deleted:int skipped:int pinned:int topics:int messages:Vector<blah.ImportedMessage> = blah.ImportedUpdates;blah.connectedTelegramUser#054605fe flags:# user_id:long tg_id:long username:flags.0?string = blah.ConnectedTelegramUser;blah.connectedTelegramUsers#f3c4b1cf users:Vector<blah.ConnectedTelegramUser> = blah.ConnectedTelegramUsers;
blah.importUpdatesToChannel#1c3fc544 flags:# source:blah.ImportSource peer:InputPeer updates:Vector<Update> users:Vector<User> chats:Vector<Chat> top_msg_id:flags.0?int media:flags.1?Vector<blah.ImportedMedia> documents:flags.2?Vector<blah.ImportedDocument> sets:flags.3?Vector<blah.ImportedStickerSet> topics:flags.4?Vector<blah.ImportedTopic> replies:flags.5?Vector<blah.ImportedReply> = blah.ImportedUpdates;blah.getImportedStickerSets#eeacf353 source:blah.ImportSource src_set_ids:Vector<long> = blah.ImportedStickerSets;blah.getImportedMessages#ab87ce0a source:blah.ImportSource peer:InputPeer ids:Vector<int> = Vector<blah.ImportedMessage>;blah.getImportedIds#b4347938 source:blah.ImportSource peer:InputPeer src_ids:Vector<int> = Vector<blah.ImportedMessage>;blah.getConnectedTelegramUsers#5e3204c5 users:Vector<InputUser> = blah.ConnectedTelegramUsers;| Declaration | Description |
|---|---|
blah.importSourceTelegram | Names a Telegram chat as the source. |
blah.importedMedia | Media already uploaded to Blah for a source message. |
blah.importedRichMedia | The source message’s rich files and optional rich quote, carried in the existing media vector. |
blah.importedRichFile | A source photo/document ID paired with media uploaded to Blah. |
blah.importedDocument | A document already uploaded to Blah for a source document, such as a sticker. |
blah.importedStickerSet | The Blah set that mirrors a source sticker set, and the source documents copied into it. |
blah.importedStickerSets | A list of mirrored sets. |
blah.importedMessage | A source message id paired with the id of its Blah copy. |
blah.importedReply | Makes a source message’s copy reply to a message in another Blah chat, optionally with a quote. |
blah.importedTopic | A source forum topic’s whole current state. top_msg_id pairs it with a topic that already exists in the destination. |
blah.importedUpdates | How many updates each kind of change applied, and the copies made. |
blah.connectedTelegramUser | The Telegram account a Blah user connected. |
blah.connectedTelegramUsers | A list of connected accounts. |
blah.importUpdatesToChannel | Applies a batch of source updates (new, edited, deleted and pinned messages, topics and more) to the destination. It takes the users, chats, media, documents, sticker sets, topics and cross-chat replies those updates refer to. |
blah.getImportedStickerSets | The mirrors this DC already holds for the given source sets. |
blah.getImportedMessages | Maps Blah copies back to the source messages they came from. |
blah.getImportedIds | Maps source messages to the Blah copies they became. |
blah.getConnectedTelegramUsers | Which Telegram account each given Blah user connected. A user who connected none is left out. |
For a rich import, carry the source rich_message in updates and supply at most one
blah.importedRichMedia per source message, with up to 50 unique src_id mappings. Each
media names an uploaded photo or document using Blah credentials; source credentials and
unmapped source IDs grant no access. Repeated versions of one source message use separate
batches. quote_rich, when present, overrides the update’s rich quote, including for a target
resolved through blah.importedReply; its references remain in source ID space. The target’s
persisted file mapping resolves inline images. Blah validates normalized content and checks
quotes against the live target. An invalid quote is dropped while its reply is kept.
Channels
blah.getChannelTags#b7c4ac9e channel:InputChannel offset:string limit:int = Vector<string>;| Declaration | Description |
|---|---|
blah.getChannelTags | The hashtags and cashtags used in a channel’s posts, in pages that start after offset. The caller must be a member. Monoforums aren’t supported. |