Skip to content

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 as InputPeer, User and Update. Those are encoded at the Telegram layer the connection announced.
  • Profiles, device proofs and challenges are canonical CBOR carried in bytes fields. 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. invokeWithLayer governs Telegram’s declarations, and the Telegram types that blah.* declarations carry. blah.invokeWithLayer governs only the blah.* 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_INVALID until 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:

CodeMeaning
IDENTITY_PROFILE_UNAVAILABLEThe challenged domain’s profile could not be fetched, including HTTP, content-type or size failures.
IDENTITY_PROFILE_INVALIDThe fetched profile is malformed, fails verification, is outside its validity or drops an unexpired device without a newer identity-certified generation.
IDENTITY_PROFILE_STALEIts revision or device generation is older than, or conflicts with, this DC’s accepted state.
IDENTITY_HOME_INVALIDThe home delegation is missing, expired, unknown, for another DC or an older hosting.
IDENTITY_DEVICE_INVALIDThe 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_INVALIDThe statement/query, DC or connection binding does not match, or the challenge expired or was consumed.
IDENTITY_ACCOUNT_MISMATCHThe signed home names an account where none is allowed, or disagrees with the identity’s account at this DC.
SESSION_ALREADY_AUTHORIZEDThe 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_INVALIDThe 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 errorClient action
DEVICE_KEY_IN_USEThis device already backs another live authorization for the identity at this DC. Enroll a fresh device for the new session.
DEVICE_KEY_RETIREDThis device’s session has ended. Enroll a fresh device; a new proof cannot revive it.
DEVICE_STILL_LISTEDPublish 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>;
DeclarationDescription
blah.resourceProfileA DC-local resource ID and the exact canonical CBOR of its stored signed Diem profile.
blah.getUserProfileProfiles known for users, bots and the DC service account, including inputUserSelf.
blah.getChannelProfileProfiles known for broadcasts, supergroups and communities; monoforums are omitted.
blah.getStickerSetProfileProfiles 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;
DeclarationDescription
blah.managedAccountAn account’s profile, its idle auto-delete period in days and the application that owns it.
blah.managedAccountsOne page of accounts, plus the application’s total count.
blah.managedAccountTokensNewly issued tokens. They are shown only once. The ordinary token talks, and the super_token changes the profile, handle, privacy and chat membership.
blah.createdManagedAccountA new account and both of its tokens.
blah.createManagedAccountCreates an account.
blah.getManagedAccountsLists accounts in pages of up to 100, starting after offset_id (0 for the first page).
blah.getManagedAccountReturns one account.
blah.editManagedAccountEdits an account. An omitted field is left unchanged, and an empty string clears the field.
blah.rotateManagedAccountTokensIssues new tokens and ends the sessions the old ones started. Naming neither token rotates both.
blah.deleteManagedAccountDeletes 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;
DeclarationDescription
blah.importSourceTelegramNames a Telegram chat as the source.
blah.importedMediaMedia already uploaded to Blah for a source message.
blah.importedRichMediaThe source message’s rich files and optional rich quote, carried in the existing media vector.
blah.importedRichFileA source photo/document ID paired with media uploaded to Blah.
blah.importedDocumentA document already uploaded to Blah for a source document, such as a sticker.
blah.importedStickerSetThe Blah set that mirrors a source sticker set, and the source documents copied into it.
blah.importedStickerSetsA list of mirrored sets.
blah.importedMessageA source message id paired with the id of its Blah copy.
blah.importedReplyMakes a source message’s copy reply to a message in another Blah chat, optionally with a quote.
blah.importedTopicA source forum topic’s whole current state. top_msg_id pairs it with a topic that already exists in the destination.
blah.importedUpdatesHow many updates each kind of change applied, and the copies made.
blah.connectedTelegramUserThe Telegram account a Blah user connected.
blah.connectedTelegramUsersA list of connected accounts.
blah.importUpdatesToChannelApplies 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.getImportedStickerSetsThe mirrors this DC already holds for the given source sets.
blah.getImportedMessagesMaps Blah copies back to the source messages they came from.
blah.getImportedIdsMaps source messages to the Blah copies they became.
blah.getConnectedTelegramUsersWhich 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>;
DeclarationDescription
blah.getChannelTagsThe 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.