Developer docs
English

Pabal API

Pabal API (MTProto)

For building clients that connect to the server the same way the app does: connection details, the server public key, the sign-in flow, updates and what's supported.

The Pabal app talks to the server over MTProto 2.0. Read this page when you're building a program that connects the same way โ€” another app, automation running on a user account, a research client. If you're building a bot, the simpler Bot API is enough.

Read Telegram's documentation alongside this page

Pabal follows Telegram's published protocol, so the detailed definitions of the protocol and methods are in the MTProto and API methods documentation. This page describes what's different when connecting to a Pabal server, and what's supported.

Connection details

ItemValue
Address122.34.175.215
Port8443 (TCP)
DCDCs 1โ€“5 all share the same address. You can connect to any DC; 2 is the usual choice
ProtocolMTProto 2.0, API layer 216
TransportsAbridged, Intermediate, Padded Intermediate, Full โ€” each also with obfuscation (obfuscated2)
Server public keyserver-key.pem ยท fingerprint 8724853375441383205
api_id ยท api_hashNot checked. Use any values

There's no HTTP transport, WebSocket transport or MTProxy yet. The client device's clock must be right โ€” MTProto message IDs are derived from the time, so if the clock is far off, the server drops the messages.

Server public key

When an MTProto client first connects, it encrypts the auth key exchange with the server's RSA public key. Telegram clients have Telegram's public key built in, so to connect to Pabal you have to add this key in its place (or alongside it). This key stops other servers from pretending to be the Pabal server.

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA4hH74xPQsUwr/pyXPdF4tVicYr6QbfeDrKC7mUOVrPLSL4FtmgGn
w+O4u6lVvOf3Udd1KY6+OL4fUZdMBlLzwsoGLoniiVR09dnvyXHE8LhQSS+i1LmI
oJbhQwXplLnUJf272fLXkD23e7ppKLkjYk+jeYObueCy5HYMSThklVeXEzbZVGZv
47o/mjU2vyFoRpa6wCIE4rpj1UIPtOMpekMI/TocIlGVJ+ch6cAVNxIDro53a1eG
/1oZRLQH4oViEGxeMMjBMY5gk5HPkZvjbqy4h8TjXEz7O5o0BRsSqG/OPtP/dRQV
X4ewPhj2WCU4e6l5X59ZKIcv4sQLOwClqQIDAQAB
-----END RSA PUBLIC KEY-----
curl -s -o server-key.pem https://pabal.me/docs/server-key.pem

This key is created once, when the server first starts, and never changes. Check that the Loaded RSA key โ€ฆ (fingerprint โ€ฆ) value in the server log matches the fingerprint above.

Connecting with Telethon

This example uses Python's Telethon to sign in to a user account and send and receive messages. Sign up in the app first to create the account (see below).

# pabal_client.py โ€” pip install telethon==1.42.0
# server-key.pem (the server public key downloaded above) in the same folder
import asyncio

from telethon import TelegramClient, events
from telethon.crypto import rsa

rsa.add_key(open("server-key.pem").read(), old=False)          # the Pabal server's public key

client = TelegramClient("pabal", api_id=1, api_hash="0" * 32)  # sign-in is saved in pabal.session
client.session.set_dc(2, "122.34.175.215", 8443)


@client.on(events.NewMessage(incoming=True))
async def show(event):
    sender = await event.get_sender()
    print(f"{sender.first_name}: {event.raw_text}")


async def main():
    await client.start(phone=lambda: input("Phone number (+8210โ€ฆ): "))   # enter the code once, the first time
    me = await client.get_me()
    print(f"Signed in: {me.first_name} (id {me.id})")
    await client.send_message("BotFather", "/help")
    await client.run_until_disconnected()


asyncio.run(main())
  • Use Telethon 1.42. It's the version that speaks layer 216. Newer versions try to read responses with a higher layer and fail with TypeNotFoundError.
  • Telethon blocks signing up new accounts (sign_up()). Sign up in the app, or call auth.signUp directly.
  • The sign-in is saved in the pabal.session file, so you won't be asked for a code again. This file is the key to the account, so keep it safe.

Sign-in flow

1 ยท Auth key (once) req_pq_multi โ†’ req_DH_params set_client_DH_params 2048-bit auth keyProtected by server key 2 ยท Sign-in (once per auth key) auth.sendCodePhone number sentCodeTypeSmsSMS or via the admin SetUpEmailRequiredEmail โ†’ asks for address account.sendVerifyEmailCodepurpose: loginSetup auth.signInphone_code or email code auth.authorizationSigned in authorizationSignUpRequiredNew number โ†’ auth.signUp(name) Code received
Create an auth key, then sign in with a code

About this diagram

  • Two stages: the top one creates the auth key used for encryption (the protocol), and the bottom one attaches an account to that key by signing in (the API). Libraries handle the top stage for you.
  • A sign-in is attached to the auth key: once a key is signed in, every session you open with it is signed in too. If you lose the key (by deleting the session file), you have to sign in again.
  • The server operator decides how codes are delivered: for SMS or delivery by an administrator you get sentCodeTypeSms; for email you get sentCodeTypeSetUpEmailRequired, and the client asks for an email address and calls account.sendVerifyEmailCode (the middle row).
  • The red dashed line is the path for a new number: if the code is right but there's no account, authorizationSignUpRequired comes back; call auth.signUp with a name and the sign-up is complete. Signing up is only possible after entering the right code.
  • Bots skip the bottom stage and sign in with a single auth.importBotAuthorization call (with the bot token).
ErrorWhen
PHONE_NUMBER_INVALIDThe number is wrong, or it's a new number while the server has closed new sign-ups
PHONE_NUMBER_BANNEDA number the operator has blocked, or the number of a suspended account (if it was deleted while suspended, for a year by default โ€” the operator can lengthen that or make it permanent)
FLOOD_WAIT_n (420)Codes were requested too often. Try again after n seconds
PHONE_CODE_INVALIDThe code is wrong (after the set number of tries, it's locked)
PHONE_CODE_EXPIREDThe code has expired or is locked, or the phone_code_hash is unknown โ€” start again from sendCode
EMAIL_INVALID, EMAIL_NOT_ALLOWEDThe email address is invalid / the account already exists and this isn't its registered sign-in email
AUTH_KEY_UNREGISTERED (401)A method that needs sign-in was called with a key that isn't signed in
Test numbers

On a server where the operator has turned on test numbers, numbers of the form +99966XYYYY sign in with the code XXXXX (X five times), without any code actually being delivered. This is meant for development servers only, and it's off on production servers.

Receiving updates

  • Real time: while the connection is open, the server immediately sends new messages, edits and deletions as updateShortMessage and updates. The session that sent a request gets the result as the RPC response, so the same thing isn't pushed to it a second time.
  • pts: each user's changes get a sequence number (pts). If there's a gap in the pts a client receives, it has missed something.
  • Catching up: remember the current pts with updates.getState, and when you reconnect, call updates.getDifference(pts, date, qts) to get the new messages and deletions in between, along with the related users and groups. If the client's pts is ahead of the server's (for example, when the server's data has been reset), you get differenceTooLong; reload the chat list in that case.
  • Push notifications โ€” Android: an Android app with no connection is woken up by a Firebase push. Once the app registers with account.registerDevice (token_type 2 = FCM token, secret = a 256-byte device key), the server encrypts new messages and calls with that device key and sends them through FCM, and the app, once awake, catches up with updates.getDifference. Push can reach only apps built with the server operator's Firebase project.
  • Push notifications โ€” iPhone: the iPhone app registers two things with the same request โ€” token_type 1 (an APNs alert token) and 9 (a PushKit VoIP token); for both, secret is the account's 256-byte device key. New messages go as APNs alerts: the only outer text Apple can see is the app name and a line with no content, such as "You have a new notification", while the message itself is encrypted with the device key and carried in p, where the app's Notification Service Extension decrypts it and shows it. Calls come as VoIP pushes that bring up the CallKit call screen (from the builds the operator turns VoIP on for; before that, as alerts), and what was read or deleted on another device is announced with background pushes. Push can reach only apps signed by the same team as the server operator's APNs key and with the same bundle ID. Any other token_type is answered with boolTrue, but nothing is sent.
  • Libraries such as Telethon take care of all this for you.

IDs and peers

WhatIDNotes
PeopleFrom 100001peerUser. @BotFather is 100000
BotsSame numbering as peopleuser.bot = true. The number at the front of the token is the bot ID
Basic groupsFrom 1000001peerChat. In the Bot API they appear as negative numbers (-chat_id)
MessagesOne sequence across the serverBoth copies of a one-to-one message carry the same number, so requests that take only message IDs, without a peer (messages.getMessages, messages.deleteMessages), point at the same message for both people. Only channels count from 1 on their own

Store access_hash exactly as the server gives it to you, and reuse it (it comes with username lookups, chat lists and updates).

Files

  • Uploading: upload parts with upload.saveFilePart (up to 512 KB each, up to 4,000 of them) and reference them with inputFileUploadedโ€ฆ. Files over 10 MB go up through upload.saveBigFilePart โ€” you say in advance how many parts there are, so if the size is one the server won't take, it refuses with FILE_TOO_BIG before you have sent it all. Parts are written to disk as they arrive and joined into one file when a message points at it.
  • Sending: messages.sendMedia with inputMediaUploadedPhoto (a new photo), inputMediaPhoto (a photo already on the server), inputMediaUploadedDocument (a new file) or inputMediaDocument (a file already on the server). Other media give MEDIA_INVALID. You can also upload something without sending it, with messages.uploadMedia (send the media it returns as is later), and messages.sendMultiMedia is accepted, but the items go one by one, not grouped (no grouped_id).
  • Downloading: upload.getFile with inputPhotoFileLocation (message photos), inputDocumentFileLocation (message files) or inputPeerPhotoFileLocation (profile photos). Up to 1 MB at a time. Downloads don't load the whole file into memory either โ€” only the range you asked for is read.
  • Profile photos: photos.uploadProfilePhoto, photos.updateProfilePhoto, photos.getUserPhotos, photos.deletePhotos.
  • Photos are stored in one size only, the original (no separate thumbnails are generated). Files are left exactly as they are, and the documentAttribute* values the sender attached are passed on to the recipient byte for byte โ€” so the server doesn't need to understand a video's duration or a voice recording's waveform to hand them along.

What's supported

The server has handlers for 469 layer 216 methods and can read requests for all of them, but the methods below are the ones verified end to end with real clients. The rest answer in the right format, but their contents may be empty or may not be recorded.

AreaVerified methods
ConnectioninitConnection, invokeWithLayer, help.getConfig, auth.bindTempAuthKey (PFS temporary keys), auth.exportAuthorization/importAuthorization
Sign-inauth.sendCode, auth.signIn, auth.signUp, auth.logOut, auth.importBotAuthorization, account.sendVerifyEmailCode
Users and contactsusers.getUsers, users.getFullUser, contacts.resolveUsername, contacts.importContacts, contacts.search
Messagesmessages.sendMessage, messages.sendMedia (photos and files), messages.getHistory, messages.getDialogs, messages.getMessages, messages.editMessage, messages.deleteMessages
Groupsmessages.createChat, messages.deleteChatUser, messages.editChatTitle (messages.addChatUser verified with the official app only)
Botsmessages.getBotCallbackAnswer, messages.setBotCallbackAnswer, messages with buttons (reply_markup)
Updatesupdates.getState, updates.getDifference, real-time push
Files and photosupload.saveFilePart, upload.saveBigFilePart (over 10 MB), upload.getFile, photos.* (above)

We have also confirmed that the official Telegram Desktop 6.2.6 works without modification for signing up, signing in, chats, photos, groups and reconnecting. The roughly 60 methods the app calls at startup have their response formats checked separately.

Added since then โ€” the device list and ending sessions (account.getAuthorizations, account.resetAuthorization, auth.resetAuthorizations), the desktop app's data export (account.initTakeoutSession, invokeWithTakeout, invokeWithMessagesRange, account.finishTakeoutSession โ€” three starts a day per account, beyond that 420 TAKEOUT_INIT_DELAY_n) and changing the number (account.sendChangePhoneCode, account.changePhone โ€” 400 PHONE_CHANGE_DISABLED until the operator turns it on, BOT_METHOD_INVALID for bots) โ€” have been verified with server tests; the end-to-end check with the real apps is still to come.

Errors

Errors arrive as a standard rpc_error (error_code + error_message). error_message is always made of uppercase letters, digits and underscores (PEER_ID_INVALID), followed by : description when needed.

CodeMeaning
400Bad request โ€” PEER_ID_INVALID, MESSAGE_ID_INVALID, MEDIA_INVALID, USERNAME_NOT_OCCUPIED โ€ฆ
401Sign-in required โ€” AUTH_KEY_UNREGISTERED
403Not allowed โ€” for example, a group you're not a member of
406Not allowed right now โ€” a device signed in less than 24 hours ago tries to end another device: FRESH_RESET_AUTHORISATION_FORBIDDEN
420FLOOD_WAIT_n โ€” wait n seconds
500Internal server error
Transport error -404The server doesn't know this auth key โ€” create a new key and sign in again (permanent key) or bind again (temporary key)

Watch API

The server's /api/watch/v1/ is an HTTPS JSON entry point just for the Pabal Apple Watch app. It exists separately because the watch can't use MTProto; it is not a public specification and is still in the works, so it changes without notice. To build a program that connects to Pabal, use the MTProto described on this page. Server operators, see Server installation โ€” Apple Watch entry points.

Not available yet

  • Channels and supergroups (channels.* methods respond, but channels don't display properly in the app), secret chats, group calls
  • Video and voice messages that play inside the app, albums shown as a group (grouped_id), stickers, thumbnails (files are kept as the original only)
  • Two-step verification (SRP) โ€” it can't be set up, and actions that require two-step verification are refused
  • HTTP and WebSocket transports, MTProxy, sending msgs_ack, periodic server-salt rotation (a wrong salt is answered with bad_server_salt)
  • Splitting across several servers โ€” a single server handles all of DCs 1โ€“5

Connecting the official Telegram app to Pabal

The Telegram app builds the server address and public key into itself at build time. So instead of changing a setting, you have to modify the source and rebuild it. That's how the Pabal app (Pabal.app) was made. If you're a server operator, see Server installation โ€” connecting the app.