Data shapes
This page describes the values you get back from operations (Call protocol) and the building blocks that event payloads reuse. The Python SDK returns these maps and lists unchanged, with a few exceptions noted below.
Results and errors
Section titled “Results and errors”A call answers {ok: true, v: <result>} or {ok: false, e: <code>, m: <text>}. The Python SDK returns v and raises an exception for an error (Denied, Invalid, NotFound, QuotaExceeded, LimitExceeded, Unavailable, Failed). An awaited call answers the same through call.result. Every key described as “optional” is missing when it has no value.
All ids are integers.
| What | Convention |
|---|---|
| User, bot | The plain Telegram user id (positive). Your own id is account.info()["id"]. |
| Basic group | (1 << 48) + group_id, that is 281474976710656 + group_id. |
| Channel, supergroup, forum | (2 << 48) + channel_id, that is 562949953421312 + channel_id. |
| Message | The id inside its chat (positive, per chat). Not unique across chats. |
| Topic of a forum | The id of the topic’s first message; the General topic is 1. |
| Telegram service chat | 777000; it is never exposed and every operation on it answers denied. |
| Secret chats, Saved Messages | Never exposed to plugins: not listed, no events, operations answer not_found or denied. |
So there are no negative chat ids and no -100... prefix: the type is encoded in the high bits. The same value is used for chat, from, user, id of users.info, channels.info and so on. To take an id apart:
def peer_kind(peer_id): return ("user", "group", "channel")[peer_id >> 48]
def bare_id(peer_id): return peer_id & ((1 << 48) - 1)chat_type / type tells the same without arithmetic: user, bot, self, group, supergroup, forum, channel (a channel id has peer_id >> 48 == 2 for the last three).
The sender id from of a message is a peer id too: a user for ordinary messages, a channel for channel posts, the chat itself for anonymous admins. from_kind has the same values as chat_type.
Message ids passed to messages.edit, messages.react and similar may be a server id or a local_id returned by messages.send (valid until the app restarts). The Python Sent object can be passed directly.
messages.history(..., before=N) returns messages with ids smaller than N; from_user takes a user id or the text "me".
Entities
Section titled “Entities”entities is a list of maps describing formatting inside a text (the “text entities” of Telegram). It appears in message events, in the message object, in the message.outgoing / message.incoming hooks and is accepted by messages.send, messages.edit, messages.set_outgoing, messages.set_incoming and similar calls.
| Key | Type | Presence | Meaning |
|---|---|---|---|
type |
text | always | See the table below. |
offset |
int | always | Start, in UTF-16 code units from the start of text. |
length |
int | always | Length in UTF-16 code units (at least 1 when sending). |
url |
text | text_url |
The link target. |
language |
text | pre, optional |
The language of a code block; missing when none. |
id |
int | custom_emoji, mention_name |
The custom emoji document id, or the mentioned user id. |
collapsed |
bool | blockquote, optional |
Only true: the quote is collapsed until expanded. |
type |
Notes |
|---|---|
bold, italic, underline, strike, spoiler, code |
No extra keys. |
pre |
A code block; may have language. |
blockquote |
May have collapsed. |
text_url |
A link on custom text; url is required. |
mention_name |
A mention of a user without a username; id is the user id. When sending, the user must be known to the app. |
custom_emoji |
A custom emoji drawn over the placeholder character(s) it covers; id is required. |
url, mention, hashtag, cashtag, bot_command, email, phone |
Found by Telegram itself in the text. They are reported when reading, but ignored when sending. |
Limits when sending: at most 256 entities, url up to 2048 characters and starting with http://, https:// or tg://, language up to 64 characters. A malformed list makes the call fail with invalid. Entity types that are not in the table (for example bank card numbers) are skipped when reading, and a text carries at most 256 entities, so the list can be shorter than what is on screen. Entities cannot be combined with markdown=....
UTF-16 matters as soon as the text contains emoji or other characters outside the basic plane: such a character counts as 2 units. Convert before slicing:
def entity_text(text, entity): raw = text.encode("utf-16-le") start = entity["offset"] * 2 return raw[start:start + entity["length"] * 2].decode("utf-16-le")
def to_utf16_offset(text, index): """Python character index -> UTF-16 offset.""" return len(text[:index].encode("utf-16-le")) // 2Message object
Section titled “Message object”messages.history, messages.get, messages.search, messages.pinned, messages.scheduled, messages.reply_chain, media.list and the message.incoming hook return messages. They come in two forms.
Full form (messages that the app holds in memory, and every messages.get, messages.reply_chain and message.incoming):
| Key | Type | Presence | Meaning |
|---|---|---|---|
id |
int | always | Message id. |
chat |
int | always | Chat id. |
from |
int | always | Sender peer id. |
from_kind |
text | always | Kind of the sender. |
date |
int | always | Unix seconds (for a scheduled message: the planned time). |
text |
text | always | Text or caption; empty if none. |
out |
bool | always | Your own message. |
entities |
list | optional | Entities. |
reply_to |
int | optional | Replied message id. |
topic |
int | optional | Topic root id; 1 outside topics. |
media |
text | optional | Media kind. |
sticker |
map | optional | {id, emoji, set, animated, video}; set is the short name of the pack. |
emoji_only |
bool | optional | Only true. |
reactions |
list | optional | [{emoji | custom_emoji | paid, count, my}]. |
buttons |
list | optional | Inline keyboard. |
deleted |
bool | optional | Only true, and only if you have messages.read_deleted (otherwise such messages are filtered out). |
Short form (older messages that messages.history had to load from the server, and the results of messages.search, messages.pinned, messages.scheduled and of media.list for messages not in memory): only id, chat, from, date, text, out and optionally reply_to, topic and media. There are no entities, reactions, sticker, buttons or from_kind; from equals chat when the server gave no sender; topic is the reply thread top (missing if none, not 1); media has only the coarse values photo, file, poll, contact, location, webpage, other.
The message events (message.new and so on) are a different map described in Event payloads: it additionally has chat_title, chat_type, sender_name, forwarded, edit_date, grouped, via_bot, but topic and reply_to follow the same rules.
Two things the object does not carry: the origin of a forward (events only tell forwarded: true; history items do not even have that) and the file details of media (name, size, duration). Use media.get for the bytes, MIME type and file name.
Media kinds
Section titled “Media kinds”media |
Meaning |
|---|---|
photo |
A photo. |
video |
A video file. |
round |
A round video message. |
gif |
An animation. |
voice |
A voice message. |
audio |
An audio file. |
sticker |
A sticker (sticker map is set). |
file |
Any other document. |
poll, contact, location, webpage |
A poll, a shared contact, a geo point, a link preview. |
other |
Anything else. |
Buttons
Section titled “Buttons”buttons is a list of rows; a row is a list of buttons. Only the inline keyboard under a bot message is described (not reply keyboards).
| Key | Type | Presence | Meaning |
|---|---|---|---|
text |
text | always | The label. |
type |
text | always | callback, url or other (login, payment, switch inline, web app, …). |
url |
text | type == "url" |
The link. |
Press a button by position with bots.press_button(chat, message, row, column) (zero-based). A callback button answers {text, alert, url}: the bot’s pop-up text, whether it is an alert, and a URL to open (empty strings when absent). A url button answers its url without any request.
msg = freshgram.messages.get(chat, message_id)for r, row in enumerate(msg.get("buttons", [])): for c, button in enumerate(row): if button["type"] == "callback" and button["text"] == "Accept": answer = freshgram.bots.press_button(chat, message_id, r, c) freshgram.log(answer["text"])Chats and people
Section titled “Chats and people”chats.list, chat.info
Section titled “chats.list, chat.info”chats.list(limit) (default 100, at most 500) returns chats from the main list and the archive in list order. chat.info(chat) returns one of them. Secret chats, Saved Messages and the service chat are not included.
| Key | Type | Meaning |
|---|---|---|
id |
int | Chat id. |
title |
text | Title or user’s name. |
type |
text | user, bot, group, supergroup, forum, channel. |
unread |
int | Unread messages count. |
pinned |
bool | Pinned in the main list. |
archived |
bool | In the archive. |
users.info, users.resolve
Section titled “users.info, users.resolve”users.info(id) and users.resolve(username) (a username with or without @, 4 to 32 characters). NotFound if the app does not know the user.
| Key | Type | Meaning |
|---|---|---|
id |
int | User id. |
name |
text | Display name. |
username |
text | Without @, empty if none. |
bot, premium, contact, self |
bool | Flags. |
status |
text | online, recently, week, month, long_ago, offline, hidden, bot. |
last_seen |
int | Unix time of the last visit (or the end of the online state); 0 when unknown. |
has_photo |
bool | A profile photo exists; fetch it with users.photo. |
channels.info, channels.resolve
Section titled “channels.info, channels.resolve”| Key | Type | Presence | Meaning |
|---|---|---|---|
id |
int | always | Channel id (with the high bits, see Ids). |
title, username, about |
text | always | Empty when none. |
type |
text | always | channel, supergroup, forum. |
members |
int | optional | Missing when the count is unknown. |
verified, scam, fake, has_photo |
bool | always | Flags. |
users.photo, channels.photo
Section titled “users.photo, channels.photo”{mime, data}: data is bytes, mime e.g. image/jpeg. NotFound if there is no photo.
chats.members, chats.admins
Section titled “chats.members, chats.admins”A list of {id, name, username, role, bot}; role is member, admin or creator. Up to 200 per call; offset pages through. Needs chat.info and users.info.
contacts.list, folders.list
Section titled “contacts.list, folders.list”contacts.list returns [{id, name, username}]. folders.list returns [{id, title, chats: [chat id...]}], only chats you may see.
account.info, stars.balance, settings.read, ghost.get
Section titled “account.info, stars.balance, settings.read, ghost.get”| Operation | Result |
|---|---|
account.info |
{id, name, username, premium} |
stars.balance |
{balance: int} (the Python SDK returns just the number) |
settings.read |
{language, theme, streamerMode} (note the camel case streamerMode) |
ghost.get |
{active, send_read_messages, send_read_stories, send_online, send_typing}, all bool |
meta.info |
{api, plugin, granted: [text], chats: "all" | [int]} |
time.now |
The server unix time as an int. |
Sending and changing
Section titled “Sending and changing”| Operation | Result |
|---|---|
messages.send, messages.send_media |
{local_id, id?}. id is present only with the messages.ids permission and is None if the server did not confirm in time; local_id may be None if no local message was made. The Python SDK wraps it in Sent. |
messages.send_poll, stickers.send, bots.send_inline_result |
{local_id} (the Python SDK discards it for the inline result). |
messages.edit, messages.edit_media, messages.delete, messages.react, messages.forward, messages.pin, messages.unpin, chats.mark_read, chats.action, chats.ban, chats.unban, chats.mute, chats.archive, chats.pin, contacts.add, users.block, composer.set_draft, composer.insert, ui.toast, notify.show, storage.set |
None |
composer.draft |
The draft text of the chat (text, empty if none). |
messages.reactions |
[{user, name, date, emoji | custom_emoji | paid}]: who reacted and when. |
stickers.search, stickers.recent |
[{id, emoji, set, animated, video}], at most 100. |
bots.inline_query |
[{query_id, id, title, description, text}], at most 50. Send one with bots.send_inline_result. |
Media, files, images
Section titled “Media, files, images”| Operation | Result |
|---|---|
media.get |
{kind, mime, name, data}; data is bytes (up to 100 MiB). kind is the media kind, or "thumbnail" when thumbnail=True. name is empty for photos. NotFound if the file is not on the device (use download=True to fetch it). |
media.list |
A list of messages that have media, newest first. |
image.info |
{format, width, height} |
image.resize, image.convert, image.crop |
{format, width, height, data}; format is png, jpeg or webp. |
files.pick |
{name, data}, or None if the user cancelled. |
files.save |
True if saved, False if the user cancelled. |
Storage, timers, files in the chosen folder, network
Section titled “Storage, timers, files in the chosen folder, network”| Operation | Result |
|---|---|
storage.get |
The stored value, None if absent. |
storage.delete |
True if the key existed. |
storage.list |
[key], at most 1000. |
storage.usage |
{used, quota, keys} (bytes, bytes, count). |
timers.set |
None. |
timers.clear |
True if the timer existed. |
timers.list |
[timer id] |
plugins.peers |
[plugin id] |
fs.list |
{entries: [{name, dir, size, modified}], more} |
fs.stat |
{name, dir, size, modified} |
fs.read |
bytes |
fs.write, fs.mkdir, fs.remove, fs.rename |
True |
net.fetch |
{status, headers: {lowercase name: text}, header_list: [[name, value]], body: bytes}; set-cookie headers are removed; repeated headers are joined with , in headers. |
net.tcp.connect, net.udp.connect |
{id} |
net.socket.send |
{sent} |
net.socket.recv |
{data: bytes, closed: bool} |
net.ws.connect |
{id, protocol} |
net.ws.send |
{sent} |
net.ws.recv |
{kind: "text" | "binary" | "close" | "none", data?, code?, reason?, closed}; none means nothing arrived in time; on close, code and reason are set. |
