Skip to content

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.

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 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")) // 2

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 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 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.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(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.
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.

{mime, data}: data is bytes, mime e.g. image/jpeg. NotFound if there is no photo.

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 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.
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.
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.