API profiles and devices
Every Telegram client announces itself in initConnection: an api_id, and the device model,
system version, app version and language fields of the app. An API profile is that set of
values for one official client. A session that uses an official profile looks like the official
app; one that mixes an official api_id with a library’s own fields does not.
| Field | Python (APIData) | Node (ApiProfile) | Rust (api::ApiProfile) |
|---|---|---|---|
| API id and hash | api_id, api_hash | apiId, apiHash | api_id, api_hash |
| Device | device_model, system_version | deviceModel, systemVersion | device_model, system_version |
| App | app_version | appVersion | app_version |
| Languages | lang_code, system_lang_code, lang_pack | langCode, systemLangCode, langPack | lang_code, system_lang_code, lang_pack |
The eight clients
| Client | Python | Node and the core | Rust | api_id | lang_pack |
|---|---|---|---|---|---|
| Telegram Desktop | API.TelegramDesktop | 'TelegramDesktop' | Client::Desktop | 2040 | tdesktop |
| Telegram Android | API.TelegramAndroid | 'TelegramAndroid' | Client::Android | 6 | android |
| Telegram X | API.TelegramAndroidX | 'TelegramAndroidX' | Client::AndroidX | 21724 | android |
| Telegram iOS | API.TelegramIOS | 'TelegramIOS' | Client::Ios | 10840 | ios |
| Telegram for macOS | API.TelegramMacOS | 'TelegramMacOS' | Client::MacOS | 2834 | macos |
| Telegram Web A | API.TelegramWeb_A (alias TelegramWeb_Z) | 'TelegramWeb_A' | Client::WebA | 2496 | empty |
| Telegram Web K | API.TelegramWeb_K | 'TelegramWeb_K' | Client::WebK | 2496 | macos |
| Webogram | API.Webogram | 'Webogram' | Client::Webogram | 2496 | empty |
Each client has a default device and its app version from the current
client versions. The default profile is fine for one account; for
many accounts, give each its own device with Generate.
from opentele2.api import API
print(API.TelegramDesktop.api_id, API.TelegramDesktop.device_model) # the class holds the profile
api = API.TelegramAndroid() # an instance to edit or pass around
print(api.app_version, api.lang_pack)import { profile } from 'opentele2'
const desktop = profile('TelegramDesktop')
console.log(desktop.apiId, desktop.deviceModel)
console.log(profile('TelegramAndroid').appVersion)use opentele2_core::api::{self, Client, Versions};
fn main() {
let versions = Versions::offline();
for client in Client::ALL {
let profile = api::profile(client, &versions);
println!("{} {} {}", client.name(), profile.api_id, profile.device_model);
}
}Generate a device
Generate keeps the client’s API id, app version and languages and replaces the device with one
picked from the client’s catalog. The pick is seeded by a unique id of your choosing, such as
your own account id: the same id always gives the same device.
from opentele2.api import API
android = API.TelegramAndroid.Generate("my-id")
desktop = API.TelegramDesktop.Generate("windows", "my-id") # platform first, then the unique id
any_desktop = API.TelegramDesktop.Generate("my-id") # platform picked from the unique id
print(desktop.device_model, desktop.system_version, desktop.app_version)Every client class has Generate(unique_id); TelegramDesktop.Generate also takes the platform,
"windows", "macos" or "linux", positionally or as system=.
import { generate } from 'opentele2'
const android = generate('TelegramAndroid', 'my-id')
const desktop = generate('TelegramDesktop', 'my-id', 'windows') // seed, then the platform
const anyDesktop = generate('TelegramDesktop', 'my-id') // platform picked from the seed
console.log(desktop.deviceModel, desktop.systemVersion, desktop.appVersion)use opentele2_core::api::{self, Client, Versions};
fn main() -> opentele2_core::Result<()> {
let versions = Versions::offline();
let android = api::generate(Client::Android, Some("my-id"), &versions)?;
let desktop = api::generate_desktop(Some("windows"), Some("my-id"), &versions)?;
println!("{} {} {}", desktop.device_model, desktop.system_version, desktop.app_version);
println!("{}", android.device_model);
Ok(())
}All three print Vostro 3520 Windows 11 x64 7.3.1 x64.
- Desktop picks the platform from the unique id when none is given (one of Windows, macOS and
Linux), and adds the build architecture to the app version:
x64on Windows,arm64on macOS, none on Linux. - macOS: Telegram Desktop on a Mac reports
macOS 26.7.1, the Swift client (TelegramMacOS) the bare version; both pick the same Mac for the same id. - Without a unique id the device is drawn from the operating system’s random source, so each call gives another device.
The same unique id gives the same device in Python, Node and Rust. Store the id with the account and regenerate the profile whenever you need it, instead of storing the device.
How the pick works
A catalog is a list of (model, system version) pairs, each with a weight: a heavier pair is picked
more often. The pick hashes the unique id (SHA-1 of its UTF-8 text, as a big-endian number, modulo
10^12) and indexes the cumulative weights with the hash. An integer id is hashed as its decimal
text, so 12345 and "12345" give the same device. The catalogs are embedded in the core and are
the only copy; every binding reads them from there.
Device catalogs
| Platform | Python | Node and the core |
|---|---|---|
| Windows | WindowsDevice | 'windows' |
| Linux | LinuxDevice | 'linux' |
| macOS | macOSDevice | 'macos' |
| Android (Telegram Android and Telegram X) | AndroidDevice | 'android' |
| iOS | IOSDevice (alias iOSDevice) | 'ios' |
| Web A and Web K | WebBrowserDevice | 'web_a', 'web_k' |
from opentele2.devices import AndroidDevice, WindowsDevice
print(WindowsDevice.device_models[:3])
print(len(WindowsDevice.deviceList)) # every pair repeated by its weight
print(AndroidDevice.RandomDevice("my-id")) # the device Generate picks for this idThe catalog lists are read-only: every read is a fresh copy, so editing one changes nothing that
RandomDevice picks.
import { catalogEntries, deviceModels, systemVersions } from 'opentele2'
console.log(catalogEntries('windows')[0]) // { model, systemVersion, weight }
console.log(deviceModels('ios').length, systemVersions('android').slice(0, 3))use opentele2_core::devices::{self, Platform};
fn main() -> opentele2_core::Result<()> {
let device = devices::pick(Platform::Android, Some("my-id"))?;
println!("{} {}", device.model, device.system_version);
println!("{}", devices::catalog_entries(Platform::Windows)?.len());
Ok(())
}Web devices
A web client’s device is a browser: the model is a desktop browser user agent, and the system
version is what the client sends for the platform, the OS name (Windows) for Web A and
navigator.platform (Win32) for Web K and Webogram.
The web catalog is a pool drawn with opentele2-browser: up to
60 Chrome, 30 Edge and 20 Firefox desktop user agents, weighted 19 : 2 : 1, around the current
Chrome and Firefox majors. When the browser dataset trails a live major, the version window slides
down by at most 24 and the drawn majors are shifted back up. The pool is a pure function of the
two majors, so a unique id gives the same web device until Chrome or Firefox moves to a new major.
from opentele2.api import API
web = API.TelegramWeb_A.Generate("my-id")
print(web.device_model) # a Chrome, Edge or Firefox user agentUsing a profile
- Python: pass
api=toTDesktop,ToTelethon,ToTDesktop,QRLoginToNewClientor the opentele2TelegramClient; a class (API.TelegramDesktop) or an instance (Generate(...)) both work. - Node: pass
profile.apiIdandprofile.apiHashto mtcute’sTelegramClientandinitConnectionOptions(profile)fromopentele2/mtcuteas itsinitConnectionOptions. - Rust: pass
api_idto grammers andopentele2_grammers::connection_params(&profile)as its connection params. grammers 0.11 sends an emptylang_packand noparams, so the wire is not identical to Telegram Desktop’s; see initConnection fingerprints.
A profile is stored in the .json companion of a Telethon session: APIData.to_json and
APIData.from_json in Python, profileToSessionJson, profileFromSessionJson and profilesEqual
in Node, ApiProfile::to_session_json and ApiProfile::from_session_json in Rust.