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.
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
| Item | Value |
|---|---|
| Address | 122.34.175.215 |
| Port | 8443 (TCP) |
| DC | DCs 1โ5 all share the same address. You can connect to any DC; 2 is the usual choice |
| Protocol | MTProto 2.0, API layer 216 |
| Transports | Abridged, Intermediate, Padded Intermediate, Full โ each also with obfuscation (obfuscated2) |
| Server public key | server-key.pem ยท fingerprint 8724853375441383205 |
| api_id ยท api_hash | Not 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 callauth.signUpdirectly. - The sign-in is saved in the
pabal.sessionfile, 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
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 getsentCodeTypeSetUpEmailRequired, and the client asks for an email address and callsaccount.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,
authorizationSignUpRequiredcomes back; callauth.signUpwith 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.importBotAuthorizationcall (with the bot token).
| Error | When |
|---|---|
PHONE_NUMBER_INVALID | The number is wrong, or it's a new number while the server has closed new sign-ups |
PHONE_NUMBER_BANNED | A 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_INVALID | The code is wrong (after the set number of tries, it's locked) |
PHONE_CODE_EXPIRED | The code has expired or is locked, or the phone_code_hash is unknown โ start again from sendCode |
EMAIL_INVALID, EMAIL_NOT_ALLOWED | The 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 |
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
updateShortMessageandupdates. 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, callupdates.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 getdifferenceTooLong; 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_type2 = 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 withupdates.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_type1 (an APNs alert token) and 9 (a PushKit VoIP token); for both,secretis 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 inp, 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 othertoken_typeis answered withboolTrue, but nothing is sent. - Libraries such as Telethon take care of all this for you.
IDs and peers
| What | ID | Notes |
|---|---|---|
| People | From 100001 | peerUser. @BotFather is 100000 |
| Bots | Same numbering as people | user.bot = true. The number at the front of the token is the bot ID |
| Basic groups | From 1000001 | peerChat. In the Bot API they appear as negative numbers (-chat_id) |
| Messages | One sequence across the server | Both 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 withinputFileUploadedโฆ. Files over 10 MB go up throughupload.saveBigFilePartโ you say in advance how many parts there are, so if the size is one the server won't take, it refuses withFILE_TOO_BIGbefore 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.sendMediawithinputMediaUploadedPhoto(a new photo),inputMediaPhoto(a photo already on the server),inputMediaUploadedDocument(a new file) orinputMediaDocument(a file already on the server). Other media giveMEDIA_INVALID. You can also upload something without sending it, withmessages.uploadMedia(send the media it returns as is later), andmessages.sendMultiMediais accepted, but the items go one by one, not grouped (no grouped_id). - Downloading:
upload.getFilewithinputPhotoFileLocation(message photos),inputDocumentFileLocation(message files) orinputPeerPhotoFileLocation(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.
| Area | Verified methods |
|---|---|
| Connection | initConnection, invokeWithLayer, help.getConfig, auth.bindTempAuthKey (PFS temporary keys), auth.exportAuthorization/importAuthorization |
| Sign-in | auth.sendCode, auth.signIn, auth.signUp, auth.logOut, auth.importBotAuthorization, account.sendVerifyEmailCode |
| Users and contacts | users.getUsers, users.getFullUser, contacts.resolveUsername, contacts.importContacts, contacts.search |
| Messages | messages.sendMessage, messages.sendMedia (photos and files), messages.getHistory, messages.getDialogs, messages.getMessages, messages.editMessage, messages.deleteMessages |
| Groups | messages.createChat, messages.deleteChatUser, messages.editChatTitle (messages.addChatUser verified with the official app only) |
| Bots | messages.getBotCallbackAnswer, messages.setBotCallbackAnswer, messages with buttons (reply_markup) |
| Updates | updates.getState, updates.getDifference, real-time push |
| Files and photos | upload.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.
| Code | Meaning |
|---|---|
400 | Bad request โ PEER_ID_INVALID, MESSAGE_ID_INVALID, MEDIA_INVALID, USERNAME_NOT_OCCUPIED โฆ |
401 | Sign-in required โ AUTH_KEY_UNREGISTERED |
403 | Not allowed โ for example, a group you're not a member of |
406 | Not allowed right now โ a device signed in less than 24 hours ago tries to end another device: FRESH_RESET_AUTHORISATION_FORBIDDEN |
420 | FLOOD_WAIT_n โ wait n seconds |
500 | Internal server error |
Transport error -404 | The 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 withbad_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.