Appearance
Types
Every object below is JSON. Fields described as optional are absent when they have no value rather than present and null, so test for presence — a truthiness check on a field that was never sent is the difference between a working bot and one that crashes on the first message without a caption. The shapes mirror the Telegram Bot API field for field, deliberately: pointing an existing Telegram bot at GROM's base URL should not require touching your models. Treat the surface as additive — parse leniently and ignore fields you do not recognise, because new ones appear without a version bump.
Index
Update · User · Chat · Message · MessageId · MessageEntity · LinkPreviewOptions · ReplyParameters · PhotoSize · Animation · Audio · Document · Video · VideoNote · Voice · Sticker · Contact · Location · Venue · Poll · PollOption · PollAnswer · Dice · File · UserProfilePhotos · InputFile · InputMedia · InlineKeyboardMarkup · InlineKeyboardButton · ReplyKeyboardMarkup · KeyboardButton · ReplyKeyboardRemove · ForceReply · CallbackQuery · InlineQuery · InlineQueryResult · ChosenInlineResult · WebAppInfo · WebAppData · MenuButton · ChatMember · ChatPermissions · ChatAdministratorRights · ChatInviteLink · ChatJoinRequest · ForumTopic · BotCommand · BotCommandScope · ReactionType · MessageReactionUpdated · LabeledPrice · Invoice · SuccessfulPayment · ShippingQuery · PreCheckoutQuery
Update
One event delivered to your bot, by long polling or webhook. It is the outermost object your handler ever receives.
An Update carries exactly one of its optional event fields. Never write a handler that reads update.message unconditionally — dispatch on which field is present, and make the fallback branch a no-op rather than an error, because new update kinds are added over time and an old bot must survive meeting one.
update_id increases monotonically per bot but not contiguously: ids are consumed by events your bot did not subscribe to, so gaps are normal and are not lost messages. When polling, pass offset as the highest update_id you handled plus one — that acknowledges everything below it permanently.
Some kinds are never delivered unless you name them in allowed_updates, notably message_reaction, message_reaction_count and chat_member. A bot that seems deaf to reactions or join events is almost always missing this list rather than broken.
Trimmed: the rarer event fields (business-account, boost, paid-media, giveaway) are omitted here. See the Getting updates page for the full allowed_updates vocabulary.
| Field | Type | Description |
|---|---|---|
update_id | Integer | Monotonically increasing per bot, with gaps. Used as the polling acknowledgement cursor and as the natural idempotency key for webhooks, which are delivered at-least-once. |
message | Message | Optional. A new incoming message in a private chat, group or supergroup the bot belongs to. |
edited_message | Message | Optional. A known message was edited. Arrives with the full new content, not a diff — compare against your stored copy if you need to know what changed. |
channel_post | Message | Optional. A new post in a channel the bot administers. Channels have no per-user sender, so from is typically absent and sender_chat carries the channel. |
edited_channel_post | Message | Optional. A channel post was edited. |
message_reaction | MessageReactionUpdated | Optional. A specific user changed their reaction on a message. Requires explicit subscription in allowed_updates. |
callback_query | CallbackQuery | Optional. An inline keyboard button was pressed. You must answer it even when there is nothing to say, or the user's client spins. |
inline_query | InlineQuery | Optional. Someone typed the bot's username in any composer. Only delivered if inline mode is enabled for the bot. |
chosen_inline_result | ChosenInlineResult | Optional. An offered inline result was actually picked. Requires inline feedback to be switched on, and is sampled rather than guaranteed for every pick. |
poll | Poll | Optional. The tallies or closed state of a poll the bot sent changed. Bots only receive updates for polls they created themselves. |
poll_answer | PollAnswer | Optional. A user voted in a non-anonymous poll the bot sent. Anonymous polls never produce this. |
my_chat_member | ChatMember | Optional. The bot's own membership or rights changed. This is how you learn you were added, removed, promoted or demoted — always subscribed, no opt-in needed. |
chat_member | ChatMember | Optional. Some other member's status changed. Requires explicit subscription and administrator rights in the chat. |
chat_join_request | ChatJoinRequest | Optional. Someone asked to join a chat where the bot can approve requests. |
shipping_query | ShippingQuery | Optional. A shipping address was submitted for an invoice priced flexibly. |
pre_checkout_query | PreCheckoutQuery | Optional. A payment is about to be charged and is waiting on the bot's confirmation, with a hard answer deadline. |
User
A person or a bot. The same object represents both, distinguished by is_bot.
id is the one identifier worth storing. It survives the account changing username, display name, phone number and language; nothing a user can do from the app will change it. Usernames are the opposite — they are rentable handles that can be released and claimed by somebody else, so keying your database on username eventually attributes one person's data to another.
can_join_groups, can_read_all_group_messages and supports_inline_queries describe the bot's own configuration and are returned only by getMe. They are absent from every User that appears inside a Message.
| Field | Type | Description |
|---|---|---|
id | Integer | Stable for the lifetime of the account and never reused after deletion. Store this, not the username. Exceeds 32 bits, so use a 64-bit signed column. |
is_bot | Boolean | True for bot accounts. Check it before treating a sender as a human — bots can be members of groups and will produce messages. |
first_name | String | Display name chosen by the user. Always present but not unique, not stable, and freely settable to anything including another person's name — never use it for identity or access control. |
last_name | String | Optional. Many accounts have none, so build display strings that read correctly when it is missing rather than concatenating a stray space. |
username | String | Optional, without the leading @. A user may have none at all, which is why @-mentioning is not a reliable way to reach someone; mention by id through a text_mention entity instead. |
language_code | String | Optional IETF tag from the client, e.g. ru or uz. A hint for choosing your reply language, not a verified preference, and absent often enough that you need a default. |
is_premium | Boolean | Optional. Present and true only for premium accounts; absent otherwise rather than false. |
added_to_attachment_menu | Boolean | Optional. The user added this bot to their attachment menu. |
can_join_groups | Boolean | getMe only. False when group privacy was configured to keep the bot out of groups entirely. |
can_read_all_group_messages | Boolean | getMe only. When false the bot has privacy mode on and sees only commands, replies to itself and service events — the usual reason a group bot appears to ignore ordinary chatter. |
supports_inline_queries | Boolean | getMe only. Whether inline mode is enabled, which gates delivery of inline_query updates. |
Chat
A conversation: a private chat, a group, a supergroup or a channel. Every send method addresses one by chat_id.
chat_id encodes the chat type in its sign. This is the single most common source of chat not found:
- Positive — a private chat. The value equals the user's own
User.id. - Negative — a group.
-100-prefixed — a channel or supergroup, e.g.-1001234567890.
Store ids as 64-bit signed integers. They routinely exceed 32 bits, so a legacy INT column silently truncates and starts addressing the wrong chat. Methods also accept @username as a string in place of the number, but only for public chats and channels.
A group that is upgraded to a supergroup changes its id. The old id stops working and you receive a migration marker on the final message in the old chat. If you cache chat ids, handle that transition or the bot goes quiet in exactly the groups that grew large enough to matter.
Trimmed: the object embedded inside a Message carries only identity fields — id, type, title, username, names and is_forum. Everything below photo in this table is populated only by getChat. Reading permissions off message.chat returns nothing; fetch the chat explicitly.
| Field | Type | Description |
|---|---|---|
id | Integer | Signed 64-bit. The sign and the -100 prefix carry the chat type — see the note above before storing or constructing one. |
type | String | One of private, group, supergroup, channel. Branch on this rather than inferring from the sign of the id; the two agree, but only one of them is documented behaviour you control. |
title | String | Optional. Present for groups, supergroups and channels; absent for private chats, where you build a display name from the user's first and last name instead. |
username | String | Optional. Public chats and channels only. Private chats have one only if the user does. |
first_name | String | Optional. Private chats only — the other party's first name. |
last_name | String | Optional. Private chats only. |
is_forum | Boolean | Optional. Topics are enabled. When true, messages carry message_thread_id and sending without one drops the message into the General topic. |
photo | ChatPhoto | Optional, getChat only. File ids for the chat avatar, which still have to be fetched through getFile to obtain bytes. |
description | String | Optional, getChat only. The group or channel description text. |
invite_link | String | Optional, getChat only. The primary invite link, and only when the bot is an administrator. Revoking it here invalidates it for everyone. |
pinned_message | Message | Optional, getChat only. The most recently pinned message; earlier pins are not exposed as a list. |
permissions | ChatPermissions | Optional, getChat only. Default rights for ordinary members. Per-member restrictions are layered on top and are read from getChatMember, not here. |
slow_mode_delay | Integer | Optional, getChat only. Seconds a non-administrator must wait between messages. A bot that is not an admin is subject to it and will hit 429 — read this before sizing your send rate. |
linked_chat_id | Integer | Optional, getChat only. The discussion group of a channel, or the channel of a discussion group. This is the link that turns channel posts into commentable threads. |
message_auto_delete_time | Integer | Optional, getChat only. Seconds after which messages self-destruct. Anything the bot sends here is also subject to it, which will quietly remove messages your bot expects to edit later. |
Message
A message. The single richest object in the API — it represents ordinary text, every media kind, and dozens of service events such as members joining or a chat being renamed.
Trimmed on purpose. Message has well over a hundred optional fields. This table lists the ones a bot actually reads; the long tail — passport data, giveaway records, video-chat lifecycle, most forum service events — is omitted so the useful fields stay visible. If a field exists in the Telegram Bot API and is not listed here, treat the Telegram name as authoritative rather than inventing one.
Exactly one content field is populated on a normal message: text or one of the media fields, never both. Media messages carry caption instead of text, which is why a bot that only reads text appears to ignore every photo it is sent. The two also have different length limits — 4096 for text, 1024 for a caption.
message_id is unique within its chat only, not globally. Two different chats will hand you the same number. Always persist the (chat_id, message_id) pair together; storing the id alone is the reason editMessageText later fails with message to edit not found.
A service event message — someone joined, the title changed — has no text and no media. Guard on that before formatting, or you will emit empty replies into busy groups.
| Field | Type | Description |
|---|---|---|
message_id | Integer | Unique inside this chat and increasing, but not globally unique and not a timestamp. Meaningless without the chat_id it belongs to. |
message_thread_id | Integer | Optional. The forum topic, or the discussion thread of a channel post. Echo it back when replying in a forum or your answer lands in General instead of the conversation it belongs to. |
from | User | Optional. The sender. Absent for channel posts and for messages sent anonymously by an admin — in those cases read sender_chat, and never assume from exists. |
sender_chat | Chat | Optional. The chat acting as sender: the channel for a channel post, or the group itself when an admin posts anonymously. |
date | Integer | Unix time the message was sent, in seconds, server-assigned. Use it to discard stale updates after downtime — a webhook backlog can deliver hours-old messages the instant you come back up. |
chat | Chat | Where the message lives. Only the identity fields are populated here; call getChat for settings and permissions. |
forward_origin | MessageOrigin | Optional. Provenance of a forwarded message. Absent means it originated here — this is the check for whether content is original. |
is_topic_message | Boolean | Optional. Sent inside a forum topic, so message_thread_id is meaningful. |
is_automatic_forward | Boolean | Optional. This is a channel post auto-forwarded into the linked discussion group. Ignore these unless you specifically handle comments, or your bot will answer every channel post twice. |
reply_to_message | Message | Optional. The message replied to, embedded whole but not recursively — its own reply_to_message is stripped, so you cannot walk a reply chain from a single update. |
via_bot | User | Optional. Sent through an inline bot. Useful for ignoring your own inline output and avoiding a feedback loop. |
edit_date | Integer | Optional. Unix time of the last edit. Its presence is the reliable way to tell an edit from an original when you handle both update kinds through one code path. |
media_group_id | String | Optional. Groups an album into one unit. An album arrives as several separate updates sharing this id — buffer briefly on it if you must treat the album as a whole, since there is no marker for the last item. |
author_signature | String | Optional. Signature of a channel post author, or the custom title of an anonymous group admin. Display only; it is not an identifier. |
text | String | Optional. Plain UTF-8 text with no markup, up to 4096 UTF-16 code units. Formatting lives separately in entities; the markup you sent is not echoed back here. |
entities | Array of MessageEntity | Optional. Formatting and special runs — bold, links, mentions, code — as offsets into text. Offsets are UTF-16 code units; see MessageEntity. |
link_preview_options | LinkPreviewOptions | Optional. How the link preview was rendered for this message. |
animation | Animation | Optional. A GIF or soundless looping video. When set, document is also set for backward compatibility — check animation first or you will misclassify every GIF as a file. |
audio | Audio | Optional. A music file, shown with performer and title. Distinct from voice, which is a recorded note. |
document | Document | Optional. A generic file. Also populated alongside animation. |
photo | Array of PhotoSize | Optional. The same image at several sizes, ascending. The last element is the largest available — take that one rather than indexing [0], which gives you a thumbnail. |
sticker | Sticker | Optional. A sticker, which may be static, animated or video. |
video | Video | Optional. A regular video. |
video_note | VideoNote | Optional. A round video note, always square and short. A separate type from video and not interchangeable with it. |
voice | Voice | Optional. A recorded voice message with a waveform, as opposed to an uploaded audio file. |
caption | String | Optional. Text accompanying media, up to 1024 UTF-16 code units. Media messages populate this instead of text. |
caption_entities | Array of MessageEntity | Optional. Formatting for caption, with offsets into the caption rather than into text. |
has_media_spoiler | Boolean | Optional. Media is covered by a spoiler animation until tapped. |
contact | Contact | Optional. A shared phone contact. |
dice | Dice | Optional. An animated emoji whose landed value is decided by the server. |
poll | Poll | Optional. A poll or quiz. The message carries the poll's current state at send time; later tallies arrive as poll updates. |
venue | Venue | Optional. A named place. Also sets location for clients that do not render venues. |
location | Location | Optional. A point on the map. A live location keeps arriving as edits to this same message rather than as new ones. |
new_chat_members | Array of User | Optional. Service event: members joined. Includes the bot itself when it is added — though my_chat_member is the more reliable signal for that. |
left_chat_member | User | Optional. Service event: one member left or was removed. Always a single user, never an array. |
new_chat_title | String | Optional. Service event: the chat was renamed. |
new_chat_photo | Array of PhotoSize | Optional. Service event: the chat avatar changed. |
pinned_message | Message | Optional. Service event: a message was pinned, embedded here in full. |
invoice | Invoice | Optional. A payment invoice message. |
successful_payment | SuccessfulPayment | Optional. Service event confirming a charge went through. This is the only trustworthy signal that money moved — never fulfil on pre_checkout_query alone. |
web_app_data | WebAppData | Optional. Data returned by a Mini App opened from a keyboard button. |
reply_markup | InlineKeyboardMarkup | Optional. The inline keyboard currently attached. Only inline keyboards come back on the message; reply keyboards belong to the user's input area, not to any message. |
MessageId
A bare message identifier, returned by methods that create a message without returning the whole thing — copyMessage most notably.
Only ever meaningful together with the chat_id you sent to. Nothing in this object records which chat it belongs to, so pair them yourself at the call site or you will not be able to edit or delete the result later.
| Field | Type | Description |
|---|---|---|
message_id | Integer | Identifier of the newly created message, unique within the destination chat. |
MessageEntity
One formatted or special run inside a message's text or caption — a bold span, a link, a mention, a code block.
offset and length are counted in UTF-16 code units. Not bytes, not Unicode code points, not visible characters. This is the single most common cause of broken formatting, and it hides completely during development because every ASCII character is 1 unit in all three schemes.
It breaks the first time a user types an emoji, which is 2 units. A flag or a skin-toned emoji can be 4 or more.
If you compute offsets over UTF-8 bytes (Go string, Rust &str, Python bytes) or over code points (Python str, Swift Character), every entity after the first emoji is misaligned — bold starts mid-word, or the API rejects the message with can't parse entities. Convert to UTF-16 before measuring, or use your library's helper. JavaScript, Java and C# store strings as UTF-16 already and get this right for free, which is exactly why the bug survives code review in a polyglot team and only shows up in the one service written in Go.
Entities may nest but must not partially overlap: bold containing italic is fine, bold that starts inside italic and ends outside it is rejected. Sending entities alongside parse_mode is not additive — supply one or the other.
| Field | Type | Description |
|---|---|---|
type | String | The kind of run: bold, italic, underline, strikethrough, spoiler, code, pre, blockquote, text_link, text_mention, mention, hashtag, url, email, phone_number, custom_emoji. Ignore unknown values rather than failing — the list grows. |
offset | Integer | Start of the run in UTF-16 code units from the beginning of the text. See the note — measuring in bytes or code points is the classic formatting bug. |
length | Integer | Length of the run, also in UTF-16 code units. offset + length must not exceed the text length or the message is rejected outright. |
url | String | Optional, text_link only. The destination the visible text points to. The text itself may say anything, so display it with care when it came from a user. |
user | User | Optional, text_mention only. Mentions a specific account by id rather than by handle — the only way to reliably mention a user who has no username. |
language | String | Optional, pre only. Language hint for syntax highlighting of the code block. |
custom_emoji_id | String | Optional, custom_emoji only. Resolve it with getCustomEmojiStickers; the run's text holds a plain fallback emoji for clients that cannot render it. |
LinkPreviewOptions
Controls whether and how a link in a message is expanded into a preview card.
Preview generation is a server-side fetch of the target page. It is best-effort: a slow, private or unreachable URL simply yields no card, and the message still sends. Do not treat a missing preview as an error.
| Field | Type | Description |
|---|---|---|
is_disabled | Boolean | Optional. Suppress the preview entirely. The usual choice for bots that post many links and do not want each one to occupy half a screen. |
url | String | Optional. Preview this URL instead of the first link found in the text — useful when the message text links somewhere you would rather not showcase. |
prefer_small_media | Boolean | Optional. Request the compact card. Ignored when the preview has no media to shrink. |
prefer_large_media | Boolean | Optional. Request the expanded card. Ignored when the target offers no suitable image. |
show_above_text | Boolean | Optional. Place the card above the message text rather than below it. |
ReplyParameters
Describes the message being replied to, including replies that quote part of it or point into another chat.
Set allow_sending_without_reply on anything automated. Without it, a reply to a message the user deleted in the meantime fails the whole send with message to reply not found — so your bot goes silent precisely when a conversation is moving fast.
quote must match the target message's text exactly, including formatting, or the send is rejected. It is not a free-text annotation; it selects a literal substring of the original.
| Field | Type | Description |
|---|---|---|
message_id | Integer | The message being replied to, in the chat named by chat_id if given, otherwise in the chat being sent to. |
chat_id | Integer or String | Optional. Reply to a message in a different chat. The bot must be able to see that chat, and cross-chat replies are not accepted everywhere. |
allow_sending_without_reply | Boolean | Optional. Send anyway if the target is gone, dropping the reply link instead of failing. Almost always what an automated sender wants. |
quote | String | Optional. The exact substring of the original to quote, up to 1024 UTF-16 code units. Must match character for character. |
quote_parse_mode | String | Optional. Parse mode for the quote's own markup. Mutually exclusive with quote_entities. |
quote_entities | Array of MessageEntity | Optional. Entities for the quote, with offsets relative to the quote string — not to the original message. |
quote_position | Integer | Optional. Where the quote starts in the original, in UTF-16 code units. Disambiguates a phrase that occurs more than once. |
PhotoSize
One rendition of an image or a file thumbnail. Images are always delivered as a set of these at ascending sizes.
file_id and file_unique_id are not interchangeable, and choosing wrong is a durable bug.
file_id— the handle you re-send with. Pass it anywhere anInputFileis accepted and the file is attached with no upload at all. It is not portable between bots: afile_idobtained by one bot is meaningless to another, and it may change form over time even for the same file. Never treat it as a database key and never publish it.file_unique_id— stable, identical across bots, and the right key for deduplicating. It is not downloadable and cannot be sent: you cannot turn one back into a file.
The practical pattern is to store both — file_unique_id as the primary key for 'have I seen this file', file_id as the cached handle for re-sending, refreshed whenever you see the file again.
In a Message, photo is an array sorted smallest to largest. Take the last element for the full image; [0] is a thumbnail and will look broken if you present it as the original.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle. Usable by this bot only, and not stable enough to be a database key — see the note. |
file_unique_id | String | Stable identity of the underlying file, the same for every bot that sees it. Use it to deduplicate; you cannot download or send it. |
width | Integer | Width in pixels of this particular rendition, not of the original upload. |
height | Integer | Height in pixels of this rendition. |
file_size | Integer | Optional. Size in bytes. Absent often enough that you cannot rely on it for pre-download budgeting. |
Animation
A GIF or a soundless looping video (H.264/MP4). What users call a GIF is almost always the latter.
A message carrying an Animation also carries a Document describing the same file, for clients too old to know the type. Check animation before document when classifying, or every GIF is filed as an ordinary attachment.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. See PhotoSize for how it differs from file_unique_id. |
file_unique_id | String | Stable cross-bot identity. Not downloadable. |
width | Integer | Width in pixels as reported by the sender's client. |
height | Integer | Height in pixels. |
duration | Integer | Loop length in seconds, rounded, so short animations frequently report 0. |
thumbnail | PhotoSize | Optional. Poster frame. Absent when none could be generated — render a placeholder rather than assuming it exists. |
file_name | String | Optional. Original name from the sender. Attacker-controlled: never use it as a filesystem path without sanitising. |
mime_type | String | Optional. Declared by the sender, not verified on arrival. Sniff the bytes if a decision depends on it. |
file_size | Integer | Optional. Size in bytes. |
Audio
A music or audio file presented with performer and title. Distinct from Voice, which is a recorded note.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. |
file_unique_id | String | Stable cross-bot identity. Not downloadable. |
duration | Integer | Length in seconds as declared by the sender's client, not measured server-side. |
performer | String | Optional. Read from the file's tags when present; absent for untagged uploads. |
title | String | Optional. Track title from the tags, which is not the same as file_name. |
file_name | String | Optional. Original filename. Untrusted input — sanitise before using as a path. |
mime_type | String | Optional. Sender-declared type, not verified. |
file_size | Integer | Optional. Size in bytes. |
thumbnail | PhotoSize | Optional. Embedded album art, when the file had any. |
Document
A generic file with no special presentation. The fallback type for anything that is not recognised as photo, audio, video or voice.
sendDocument is the escape hatch when a specialised method rejects your file: it applies the loosest content checks and the highest size limit. It is also the right choice when you need the bytes to survive intact, since the specialised kinds may be re-encoded.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. |
file_unique_id | String | Stable cross-bot identity. Not downloadable. |
thumbnail | PhotoSize | Optional. Generated preview, present mainly for document types the server can render. |
file_name | String | Optional. Original name as supplied by the sender. Fully attacker-controlled — it may contain path separators, null bytes or a misleading double extension. |
mime_type | String | Optional. Declared, not verified. Sniffing the magic bytes is the only reliable check. |
file_size | Integer | Optional. Size in bytes. Check it before downloading, since getFile has its own download ceiling. |
Video
A video file played inline.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. |
file_unique_id | String | Stable cross-bot identity. Not downloadable. |
width | Integer | Width in pixels. |
height | Integer | Height in pixels. Compare with width to detect portrait video before laying anything out around it. |
duration | Integer | Length in seconds, rounded. |
thumbnail | PhotoSize | Optional. Poster frame. |
file_name | String | Optional. Original name. Untrusted. |
mime_type | String | Optional. Sender-declared. |
file_size | Integer | Optional. Size in bytes. |
VideoNote
A round video message — the short square clip recorded in-app. A separate type from Video and not interchangeable with it.
Always square, so there is a single length rather than width and height. Video notes cannot carry a caption; sending one with caption set silently drops the text.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. |
file_unique_id | String | Stable cross-bot identity. Not downloadable. |
length | Integer | Diameter of the square in pixels — width and height are equal by definition for this type. |
duration | Integer | Length in seconds. Video notes are short by design and clients cap the recording. |
thumbnail | PhotoSize | Optional. Circular poster frame. |
file_size | Integer | Optional. Size in bytes. |
Voice
A recorded voice message, shown with a waveform and playable at variable speed. Distinct from Audio.
Recorded in an Opus-in-OGG container by most clients, but Android in particular produces an MP4-container file that sniffs as video/mp4. Accept that when validating uploads rather than rejecting on container alone.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. |
file_unique_id | String | Stable cross-bot identity. Not downloadable. |
duration | Integer | Length in seconds as reported by the recording client. |
mime_type | String | Optional. Varies by platform — do not hardcode a single expected value. |
file_size | Integer | Optional. Size in bytes. |
Sticker
A sticker: static WebP, animated, or video. Custom emoji are also stickers and arrive through this type.
| Field | Type | Description |
|---|---|---|
file_id | String | Re-send handle for this bot. |
file_unique_id | String | Stable cross-bot identity. The right key for caching a sticker you recognise. |
type | String | regular, mask or custom_emoji. Independent of the format fields below — this describes the sticker's role, not its encoding. |
width | Integer | Width in pixels. |
height | Integer | Height in pixels. |
is_animated | Boolean | Lottie/TGS vector animation. Rendering it requires a Lottie player, not an image decoder. |
is_video | Boolean | WebM video sticker. Mutually exclusive with is_animated; both false means a static image. |
thumbnail | PhotoSize | Optional. Static preview, which for animated and video stickers is the only frame you can render without a specialised player. |
emoji | String | Optional. The emoji this sticker corresponds to — a reasonable text fallback when you cannot display the image. |
set_name | String | Optional. The sticker set it belongs to. Absent for stickers not in a set. |
custom_emoji_id | String | Optional, custom_emoji type only. The id referenced by custom_emoji message entities. |
needs_repainting | Boolean | Optional. The custom emoji should be recoloured to the surrounding text colour rather than drawn as-is. |
file_size | Integer | Optional. Size in bytes. |
Contact
A phone contact shared into a chat.
user_id is present only when the phone number belongs to a known account. A contact card for someone not on the platform carries a number and a name and nothing else — so treat user_id as the test for 'can I message this person', not as a field that is merely sometimes omitted.
| Field | Type | Description |
|---|---|---|
phone_number | String | As supplied by the sender. Formatting is not normalised and the leading + may be missing — canonicalise before comparing against your own records. |
first_name | String | Contact's first name from the sender's address book, which need not match that account's own profile name. |
last_name | String | Optional. Often absent. |
user_id | Integer | Optional. Present only if the number maps to a registered account. Its absence means the person cannot be reached by the bot at all. |
vcard | String | Optional. Raw vCard text with any additional fields. Untrusted input and can be sizeable — parse defensively. |
Location
A geographic point. Also the payload of a live location, which updates over time.
A live location does not generate a new message per update — the original message is edited repeatedly, so you receive edited_message updates. A bot that only handles message sees the starting point and never learns the user moved.
| Field | Type | Description |
|---|---|---|
latitude | Float | Degrees north, as a float. Storing it as a float32 loses roughly a metre of precision — use double. |
longitude | Float | Degrees east. Note the order: most geo libraries take latitude first, but GeoJSON takes longitude first, which is a reliable source of transposed coordinates. |
horizontal_accuracy | Float | Optional. Radius of uncertainty in metres, 0–1500. A large value means the fix came from the network rather than GPS. |
live_period | Integer | Optional. Seconds this location stays live from the time it was sent. Its presence is what distinguishes a live location from a static pin. |
heading | Integer | Optional. Direction of travel, 1–360 degrees. Live locations only, and only when the device reported one. |
proximity_alert_radius | Integer | Optional. Metres at which the sender asked to be alerted about approaching another party. |
Venue
A named place — a point plus a title and address, rendered as a card rather than a bare pin.
A venue message also populates location on the same Message, so clients that cannot render a venue still show the pin. Read venue first when both are present.
| Field | Type | Description |
|---|---|---|
location | Location | Coordinates of the venue. |
title | String | Name of the place as shown on the card. |
address | String | Street address. Free text in whatever form the sender's client produced — not a structured address. |
foursquare_id | String | Optional. Foursquare identifier, when the place came from that directory. |
foursquare_type | String | Optional. Foursquare category string, e.g. food/icecream. |
Poll
A poll or quiz, carrying its options and current tallies.
A bot receives poll updates only for polls it sent itself. There is no way to observe a poll created by a user.
The Poll embedded in the original Message is a snapshot from send time and does not change. Live tallies arrive as separate poll updates — reading counts off your stored copy of the message will show zeroes forever.
| Field | Type | Description |
|---|---|---|
id | String | Poll identifier as a string, not an integer, and distinct from the message_id of the message carrying it. Use it to correlate poll_answer updates. |
question | String | The question text, up to 300 characters. |
options | Array of PollOption | Between 2 and 10 answer options, in display order. Positions are the indices referenced by PollAnswer.option_ids. |
total_voter_count | Integer | Number of people who voted, not the number of votes — one voter choosing several options in a multi-answer poll still counts once. |
is_closed | Boolean | Voting has ended. Closing is irreversible; a closed poll cannot be reopened, only replaced. |
is_anonymous | Boolean | When true, no poll_answer updates are produced at all — the tallies are the only information you will ever get. |
type | String | regular or quiz. A quiz has exactly one correct option and shows an explanation after answering. |
allows_multiple_answers | Boolean | Voters may pick more than one option. Never true for quizzes. |
correct_option_id | Integer | Optional, quizzes only. Zero-based index of the right answer. Withheld until the poll closes or the recipient has answered — so its absence is a privacy rule, not missing data. |
explanation | String | Optional, quizzes only. Text shown after answering, up to 200 characters. |
explanation_entities | Array of MessageEntity | Optional. Formatting for the explanation, with offsets into the explanation string. |
open_period | Integer | Optional. Seconds the poll stays open after creation, 5–600. Mutually exclusive with close_date. |
close_date | Integer | Optional. Unix time at which voting closes automatically. Mutually exclusive with open_period. |
PollOption
One answer option of a poll together with its vote count.
Options carry no id of their own. They are referenced by zero-based position in the Poll.options array, so order is load-bearing — never sort or filter the array before matching PollAnswer.option_ids against it.
| Field | Type | Description |
|---|---|---|
text | String | Option text, 1–100 characters. |
voter_count | Integer | Votes for this option. Reported as 0 for a quiz until the recipient has answered, rather than being withheld. |
PollAnswer
One user's vote in a non-anonymous poll the bot sent, including the act of retracting a vote.
An empty option_ids array means the vote was retracted, not that the update is malformed. Handle it explicitly or your tallies drift upward and never come back down.
| Field | Type | Description |
|---|---|---|
poll_id | String | The poll this vote belongs to, matching Poll.id. You must have stored the poll id at send time to make sense of this update. |
voter_chat | Chat | Optional. The channel that voted on behalf of itself. Present instead of user when a channel casts the vote. |
user | User | Optional. The voter. Absent when voter_chat is set, so check both rather than assuming a user. |
option_ids | Array of Integer | Zero-based indices into Poll.options. Empty means the vote was withdrawn. |
Dice
An animated emoji with a random outcome decided by the server — dice, darts, basketball, football, bowling, slot machine.
The value is chosen server-side and cannot be influenced by the sender, which is what makes it usable for games. Note that the client animation takes a few seconds to settle, so the recipient sees the result after your bot already knows it — announcing the outcome immediately spoils it.
| Field | Type | Description |
|---|---|---|
emoji | String | Which animation was rolled. The emoji determines the range of value, so you cannot interpret the number without it. |
value | Integer | The outcome. 1–6 for dice and bowling, 1–5 for darts and basketball and football, 1–64 for the slot machine. |
File
A file prepared for download, returned by getFile. It is a temporary download ticket, not the bytes themselves.
Downloading is two steps. Call getFile with a file_id to get a file_path, then fetch:
GET https://api.casinoplaneta.info/file/bot<TOKEN>/<file_path>That URL embeds your bot token, so it is a credential — never log it, never put it in a redirect, never hand it to a browser. Anyone holding it can act as your bot.
file_path expires after about an hour. Do not persist it. Store the file_id and call getFile again when you next need bytes; storing the path and retrying it tomorrow yields a 404 that looks like the file was deleted.
getFile has its own size ceiling, well below the limit for files a user can send. A large video can arrive in a message and still be undownloadable by the bot — check file_size before committing to a download path.
| Field | Type | Description |
|---|---|---|
file_id | String | The same re-send handle carried by the media object. Echoed back so the response is self-contained. |
file_unique_id | String | Stable cross-bot identity. Not usable for downloading — that is what file_path is for. |
file_size | Integer | Optional. Size in bytes. Use it to enforce your own limits before opening the stream, since the response is not chunk-negotiated. |
file_path | String | Optional. Relative path to append to the download URL. Short-lived — treat it as valid for this request only. |
UserProfilePhotos
A user's profile pictures, returned by getUserProfilePhotos.
Doubly nested: an array of arrays. The outer array is one entry per photo, newest first; each inner array is that photo's size renditions ascending. So the current avatar at full size is photos[0][last], and photos[0][0] is its thumbnail.
total_count counts the photos that exist, while photos holds only the page you requested — do not derive pagination from the array length.
| Field | Type | Description |
|---|---|---|
total_count | Integer | How many profile photos the user has in total, independent of how many this response returned. 0 means none are visible to the bot, which can also be a privacy setting rather than an empty profile. |
photos | Array of Array of PhotoSize | Requested page, newest photo first, each as its set of size renditions. |
InputFile
Not a returned object — the contents of a file you are sending. Every method that accepts media takes one of these.
There are three ways to supply a file, and they cost very different amounts:
- Existing
file_id(string) — a file already on the server. Nothing is transferred; the send is instant and free. Always prefer this when re-sending something the server has seen before. - HTTP URL (string) — a public
https://address the server fetches for you. No upload from your side, but the fetch can fail or time out, the server will not chase redirects indefinitely, and size limits are lower than for direct uploads. - Multipart upload —
multipart/form-datawith the bytes in the request body. The only option for a file the server has never seen, and by far the slowest.
Bots that re-send the same asset repeatedly should upload once, keep the returned file_id, and use method 1 forever after. This is the single largest performance difference between a fast bot and a slow one.
Inside sendMediaGroup and the InputMedia types you cannot inline bytes, so you reference an uploaded part by name using the attach:// scheme: post the part as photo_1 and set "media": "attach://photo_1" in the JSON.
InputMedia
Abstract description of a piece of media to send or to replace an existing one with. Used by sendMediaGroup and editMessageMedia.
A union discriminated by type, with variants InputMediaPhoto, InputMediaVideo, InputMediaAnimation, InputMediaAudio and InputMediaDocument. The fields below are common to all; the video and animation variants add width, height and duration, and audio adds performer and title.
Albums are homogeneous in practice: photos and videos may be mixed together, but audio and documents each have to be sent in an album of their own. Mixing across those groups is rejected.
Only the first item's caption is displayed as the album caption in most clients. Putting a caption on every item is not an error but generally does not render the way you expect.
| Field | Type | Description |
|---|---|---|
type | String | The discriminator: photo, video, animation, audio or document. Determines which additional fields are accepted. |
media | String | A file_id, an HTTP URL, or attach://<name> referring to a multipart part in the same request. Raw bytes cannot appear here — see InputFile. |
caption | String | Optional. Up to 1024 UTF-16 code units. In an album, generally only the first item's caption is shown. |
parse_mode | String | Optional. MarkdownV2 or HTML. Mutually exclusive with caption_entities. |
caption_entities | Array of MessageEntity | Optional. Explicit formatting, avoiding the escaping hazards of parse modes. Offsets are UTF-16 code units into the caption. |
InlineKeyboardMarkup
Buttons attached to a message itself, rendered directly beneath it.
Inline keyboards belong to the message and travel with it, which means they can be changed later with editMessageReplyMarkup — the basis for any stateful UI such as pagination or a wizard. Reply keyboards, by contrast, belong to the user's input area and cannot be edited in place.
Send an empty inline_keyboard array to strip the buttons from a message without touching its text.
| Field | Type | Description |
|---|---|---|
inline_keyboard | Array of Array of InlineKeyboardButton | Rows of buttons — the outer array is rows, the inner is columns within a row. Wide buttons come from short rows, so layout is controlled entirely by how you nest. |
InlineKeyboardButton
One button on an inline keyboard. What it does depends on which single optional field you set.
You must set exactly one of the action fields. A button with none does nothing and is rejected; a button with two is rejected as ambiguous.
callback_data is limited to 64 bytes — bytes, not characters, so non-ASCII eats the budget several times faster than you expect. It is far too small for real state. Store the state server-side and put a short opaque key in the button; packing JSON in here works in testing and truncates in production.
Treat callback_data as untrusted on the way back in. It is echoed by the client, so it can be tampered with — always re-check that the pressing user is entitled to the action rather than trusting a user id you encoded into the button.
| Field | Type | Description |
|---|---|---|
text | String | Visible label. The only required field, and the only one the user actually reads. |
url | String | Optional. Opens a link. Produces no callback_query, so the bot never learns the button was pressed. |
callback_data | String | Optional. Sent back as a callback_query when pressed. Hard 64-byte cap, and client-supplied on return — validate it. |
web_app | WebAppInfo | Optional. Launches a Mini App. Results come back through web_app_data or through answerWebAppQuery, not as a callback. |
login_url | LoginUrl | Optional. Opens a URL with a signed identity assertion so your site can authenticate the user without a separate login. |
switch_inline_query | String | Optional. Prompts the user to pick a chat and prefills the composer with the bot's username plus this text. An empty string is valid and means prefill nothing. |
switch_inline_query_current_chat | String | Optional. Same, but inserts into the current chat's composer without a chat picker. |
pay | Boolean | Optional. Marks this as the Pay button of an invoice. It must be the first button of the first row, and only one keyboard may carry it. |
ReplyKeyboardMarkup
A custom keyboard that replaces the user's normal input area. Pressing a button sends its label as an ordinary text message.
Reply keyboard presses arrive as plain text messages and are indistinguishable from the user typing the same words by hand. That makes them unsuitable for anything sensitive or destructive, and it means your text handler must be able to receive button labels. For anything that needs an unambiguous, non-forgeable signal, use an inline keyboard and callback_data.
The keyboard persists across messages until you send a ReplyKeyboardRemove or a replacement. Bots regularly leave stale keyboards attached to users for weeks because nothing in the protocol expires them.
| Field | Type | Description |
|---|---|---|
keyboard | Array of Array of KeyboardButton | Rows of buttons, outer array rows and inner array columns. |
is_persistent | Boolean | Optional. Keep the keyboard open rather than letting the user collapse it to the normal input field. |
resize_keyboard | Boolean | Optional. Shrink the keyboard to fit its rows instead of occupying the default full height. Almost always worth setting — the default wastes most of the screen on a two-button keyboard. |
one_time_keyboard | Boolean | Optional. Hide the keyboard after one press. It only collapses it — the keyboard remains available to reopen, so this is not a substitute for removing it. |
input_field_placeholder | String | Optional. Grey hint text shown in the input field, 1–64 characters. |
selective | Boolean | Optional. Show only to the users mentioned in the message text and, if it is a reply, to the author of the replied-to message. The way to give one person a keyboard in a busy group. |
KeyboardButton
One button of a reply keyboard. With no optional fields set it simply sends its own text.
The request variants — contact, location, poll, users, chat — prompt the user for consent and then deliver the result as a service message. The user can always decline, so treat a missing follow-up as the normal outcome rather than an error state to retry.
| Field | Type | Description |
|---|---|---|
text | String | Label, and also the message text sent when pressed if no request field is set. |
request_contact | Boolean | Optional. Asks the user to share their phone number, returned as a Contact. Private chats only. |
request_location | Boolean | Optional. Asks the user to share their current location. Private chats only. |
request_poll | KeyboardButtonPollType | Optional. Opens the poll composer, optionally restricted to quiz or regular. |
web_app | WebAppInfo | Optional. Launches a Mini App whose result arrives as web_app_data. Private chats only. |
request_users | KeyboardButtonRequestUsers | Optional. Opens a user picker and returns the chosen users' ids to the bot. |
request_chat | KeyboardButtonRequestChat | Optional. Opens a chat picker with filters, returning the chosen chat's id. |
ReplyKeyboardRemove
Instruction to dismiss a custom keyboard and restore the normal input field.
This is not a standalone call — it is passed as reply_markup on a message you are sending, so removing a keyboard always costs one message. Bots that want to clean up silently usually send a throwaway message and delete it immediately afterwards.
| Field | Type | Description |
|---|---|---|
remove_keyboard | True | Must be true. The field exists solely to discriminate this object from the other reply_markup shapes. |
selective | Boolean | Optional. Remove the keyboard only for the users targeted by the message, leaving everyone else's intact. |
ForceReply
Instruction to open the reply interface for the user, as if they had tapped reply on the bot's message.
The cheapest way to collect one field of input without building state: the user's answer arrives with reply_to_message pointing at your prompt, so you can correlate question and answer without storing anything.
The user can dismiss the reply interface and say nothing at all. Never block a flow on the assumption that a forced reply will arrive.
| Field | Type | Description |
|---|---|---|
force_reply | True | Must be true. Present only to discriminate this object from other reply_markup shapes. |
input_field_placeholder | String | Optional. Hint text in the reply field, 1–64 characters. Use it to say what you are asking for. |
selective | Boolean | Optional. Force a reply only from the users the message targets — essential in groups, where forcing everyone to reply is hostile. |
CallbackQuery
An inline keyboard button press.
Always call answerCallbackQuery, even with an empty response. Until you do, the user's client shows a spinner on the button. This is the most common reason a working bot feels broken.
message is absent when the button belongs to a message sent via inline mode — in that case only inline_message_id identifies it, and the two are mutually exclusive. Handle both or your inline results become dead buttons.
data is echoed from what you put in the button and is therefore client-controlled. Re-derive authorisation from from.id and your own state; never trust a user id or a permission flag that you encoded into callback_data.
| Field | Type | Description |
|---|---|---|
id | String | The token you pass to answerCallbackQuery. Short-lived — answer promptly, since a stale query id is rejected. |
from | User | Who pressed the button. In a group this is frequently not the person the message was addressed to, so check it before acting on someone else's behalf. |
message | Message | Optional. The message carrying the keyboard, when the bot can still see it. Absent for inline-mode messages and for very old messages. |
inline_message_id | String | Optional. Identifies a message sent through inline mode. Mutually exclusive with message, and the only handle you get for editing such a message. |
chat_instance | String | Opaque identifier for the chat the keyboard lives in, present even when message is not. The only chat-scoped grouping key available for inline-mode callbacks. |
data | String | Optional. The button's callback_data, verbatim and untrusted. Capped at 64 bytes on the way out. |
game_short_name | String | Optional. Present when the press was on a game launch button rather than a data button. |
InlineQuery
A user typed your bot's username followed by a query in some chat's composer, anywhere on the platform.
Inline queries arrive from chats your bot is not a member of, including private conversations between strangers. You learn the query text and the sender, never the chat contents — do not expect chat context you have not been given.
Answer within a few seconds or the result is discarded. Since users type incrementally, you will receive a burst of queries per search; debounce, and set a sensible cache_time in answerInlineQuery rather than recomputing on each keystroke.
| Field | Type | Description |
|---|---|---|
id | String | Token passed to answerInlineQuery. Valid briefly; a late answer is dropped silently. |
from | User | The person typing. Your only basis for personalising or rate-limiting results. |
query | String | Text after the bot's username, up to 256 characters. Empty when the user has typed only the username — the natural cue to show defaults. |
offset | String | Pagination cursor you defined in the previous answerInlineQuery. Opaque to the server; empty string on the first page. |
chat_type | String | Optional. sender, private, group, supergroup or channel — where the query was typed. Absent for queries from a secret chat. |
location | Location | Optional. Present only if the bot requests location and the user granted it. |
InlineQueryResult
One item offered in response to an inline query. Abstract — you always send a concrete variant.
A union discriminated by type, with around twenty variants: article, photo, gif, mpeg4_gif, video, audio, voice, document, location, venue, contact, game, sticker, and cached_* forms of most media kinds that reference an existing file_id instead of a URL.
Every variant carries type and id. id must be unique within one answer and is limited to 64 bytes; duplicated ids cause the client to drop results silently, which looks like your bot returning fewer matches than it computed.
Media variants send the media itself when picked. article and the other non-media variants require input_message_content to say what is actually sent — omitting it produces a result that appears in the list and sends nothing.
| Field | Type | Description |
|---|---|---|
type | String | The discriminator, e.g. article or photo. Determines which further fields are required. |
id | String | Your own identifier for this result, unique within the answer, at most 64 bytes. Echoed back in chosen_inline_result if feedback is enabled. |
title | String | Display title. Required for article and most non-media variants; ignored where the media supplies its own presentation. |
input_message_content | InputMessageContent | Optional for media variants, required for article. What gets sent when the result is chosen, as opposed to what is shown in the list. |
reply_markup | InlineKeyboardMarkup | Optional. Inline keyboard attached to the sent message. Editing it later needs inline_message_id, since there is no chat_id for an inline-sent message. |
ChosenInlineResult
Feedback that a result your bot offered was actually chosen and sent.
Delivered only when inline feedback is enabled for the bot, and even then it is sampled rather than guaranteed for every pick. Never use it for accounting or billing — treat it as analytics.
inline_message_id is present only if the result carried an inline keyboard. Without it you cannot edit the message afterwards, so decide at answer time whether you will ever need to update the result.
| Field | Type | Description |
|---|---|---|
result_id | String | The id you assigned to the chosen result, matching what you sent in answerInlineQuery. |
from | User | The user who picked it. |
location | Location | Optional. Present only for location-enabled bots with a granted permission. |
inline_message_id | String | Optional. Handle for editing the sent message later. Present only when the result had an inline keyboard attached. |
query | String | The query text that produced this result — useful for measuring which searches convert. |
WebAppInfo
The Mini App to open from a button. A single-field wrapper around its URL.
Must be https://. The page receives an initData string from the platform describing the user; validate its HMAC signature server-side with your bot token before trusting a single field of it. initData is fully attacker-controlled otherwise — anyone can open your page and claim to be anyone.
| Field | Type | Description |
|---|---|---|
url | String | HTTPS address of the Mini App. Query parameters you add are preserved, which is the ordinary way to pass non-secret context into the page. |
WebAppData
Data a Mini App sent back to the bot, arriving as a field on a Message.
Only produced by Mini Apps opened from a reply-keyboard button. Apps launched from an inline keyboard button, the menu button, or an inline result return data through answerWebAppQuery instead. Wiring up the wrong launch surface and waiting for a web_app_data that never comes is a standard first-day mistake.
data reaches you through the client, so it is not authenticated by transport. If the payload matters, sign it inside the Mini App with a secret your backend holds, or have the Mini App call your own backend directly and use the bot message only as a notification.
| Field | Type | Description |
|---|---|---|
data | String | Whatever the app passed to sendData, up to 4096 bytes. An opaque string to the platform — JSON is a convention, not a requirement, and it is not validated. |
button_text | String | Label of the keyboard button that launched the app. Lets one handler distinguish between several entry points into the same Mini App. |
MenuButton
The button shown next to the input field in a private chat with the bot.
A union discriminated by type:
default— inherit whatever is configured globally.commands— show the command menu built fromsetMyCommands.web_app— open a Mini App; requires bothtextandweb_app.
Set per chat or globally with setChatMenuButton. A per-chat setting overrides the global one, which is how a bot shows a personalised entry point to a signed-in user and the plain command list to everyone else.
| Field | Type | Description |
|---|---|---|
type | String | default, commands or web_app. Determines whether the other fields are read at all. |
text | String | web_app type only. Label on the button. Short — it sits in a cramped part of the input bar. |
web_app | WebAppInfo | web_app type only. The Mini App to launch. |
ChatMember
A user's membership in a chat, including their role and rights. Returned by getChatMember and carried by chat_member and my_chat_member updates.
A union discriminated by status. Switch on it before reading anything else — the rights fields exist only on the variants that can have them, so reading can_restrict_members off a plain member yields absent, not false, and a truthiness check on a missing field is how bots accidentally grant privilege.
creator— the owner. Holds every right implicitly regardless of what the individual flags say, and cannot be demoted by anyone.administrator— carries the full set ofcan_*rights pluscustom_title.member— ordinary participant, no rights fields.restricted— still in the chat but limited; carriesChatPermissionsflags anduntil_date.left— not in the chat, not banned, may return freely.kicked— banned; carriesuntil_date, where 0 means permanent.
left and kicked are still valid responses, not errors. getChatMember succeeding does not mean the user is present — check the status, not merely the absence of an exception.
Comparing rights is not the whole story: a bot can never grant or exercise a right it does not itself hold, so an operation can fail with not enough rights even when the target's status suggests it should work.
| Field | Type | Description |
|---|---|---|
status | String | The discriminator: creator, administrator, member, restricted, left or kicked. Everything else on the object depends on this value. |
user | User | The member. Present on every variant, and the only field you can safely read before branching. |
custom_title | String | Optional, creator and administrator only. Display label replacing the default role name. Cosmetic — it confers nothing. |
is_anonymous | Boolean | Optional, creator and administrator only. Posts appear as the chat rather than as the person, which is why some group messages have no from. |
until_date | Integer | Optional, restricted and kicked only. Unix time the restriction lifts. 0 means forever — do not treat it as the epoch and conclude the ban has expired. |
is_member | Boolean | Optional, restricted only. Distinguishes a restricted user still in the chat from one who is restricted and has left. |
ChatPermissions
What ordinary members are allowed to do — either the chat's defaults or one member's restrictions.
Used in two directions: read from getChat as the chat-wide default, and written by restrictChatMember to limit one person. In both cases an omitted field means unchanged, not denied — sending a partial object to tighten one permission leaves the rest as they were.
Some permissions imply others. Granting can_send_polls or can_send_other_messages without can_send_messages produces a member who can do neither, because the base right gates the specific ones.
| Field | Type | Description |
|---|---|---|
can_send_messages | Boolean | Optional. Send text, contacts, locations and venues. The base right the media permissions depend on. |
can_send_audios | Boolean | Optional. Send audio files. |
can_send_documents | Boolean | Optional. Send documents. |
can_send_photos | Boolean | Optional. Send photos. |
can_send_videos | Boolean | Optional. Send videos. |
can_send_video_notes | Boolean | Optional. Send round video notes, controlled separately from ordinary video. |
can_send_voice_notes | Boolean | Optional. Send voice messages, controlled separately from audio files. |
can_send_polls | Boolean | Optional. Create polls. Requires can_send_messages to have any effect. |
can_send_other_messages | Boolean | Optional. Send stickers, GIFs, games and inline-bot results, all as one flag. |
can_add_web_page_previews | Boolean | Optional. Allow link previews on their messages. A common thing to disable in spam-prone groups. |
can_change_info | Boolean | Optional. Edit title, photo and description. Ignored in channels, where this is an admin-only right. |
can_invite_users | Boolean | Optional. Add members or share invite links. |
can_pin_messages | Boolean | Optional. Pin messages. Groups and supergroups only. |
can_manage_topics | Boolean | Optional. Create and edit forum topics. Meaningful only when the chat has is_forum set. |
ChatAdministratorRights
The full set of administrator rights, used when promoting a member and when declaring what rights your bot wants on being added.
A bot cannot grant a right it does not hold itself. Promoting someone to more than the bot has silently drops the excess rather than failing loudly, so read the result back with getChatMember if the outcome matters.
Rights split by chat type: can_post_messages, can_edit_messages and the story rights apply to channels, while can_pin_messages and can_manage_topics apply to groups. Setting the wrong family for the chat type is ignored, not rejected — which is why a promotion can appear to succeed and change nothing.
| Field | Type | Description |
|---|---|---|
is_anonymous | Boolean | The admin's messages appear as the chat itself, with no from on their messages. |
can_manage_chat | Boolean | Implied by every other right. Grants access to the admin log, member list and statistics — the baseline any admin holds. |
can_delete_messages | Boolean | Delete messages sent by anyone. Without it a bot can only delete its own. |
can_manage_video_chats | Boolean | Start and manage video chats. Groups only. |
can_restrict_members | Boolean | Restrict, ban and unban members. The right any moderation bot needs first. |
can_promote_members | Boolean | Promote and demote others, but only within the rights the promoter already holds. |
can_change_info | Boolean | Edit title, photo and description. |
can_invite_users | Boolean | Add members and create invite links. |
can_post_messages | Boolean | Optional. Channels only. Without it a bot administering a channel cannot publish at all. |
can_edit_messages | Boolean | Optional. Channels only. Edit posts by others, including pinning them. |
can_pin_messages | Boolean | Optional. Groups only. |
can_post_stories | Boolean | Optional. Channels only. Publish stories to the channel. |
can_edit_stories | Boolean | Optional. Channels only. Edit others' stories. |
can_delete_stories | Boolean | Optional. Channels only. |
can_manage_topics | Boolean | Optional. Forum-enabled supergroups only. Create, edit, close and reopen topics. |
ChatInviteLink
An invite link to a chat, with its restrictions and usage state.
A bot sees and manages only the links it created itself, plus the primary link when it is an administrator. It cannot enumerate links made by human admins.
Revoking is permanent and cannot be undone — revokeChatInviteLink returns the revoked object rather than deleting it, so you keep the audit trail. Revoking the primary link regenerates it, invalidating the address everyone already has, which is rarely what you want.
creates_join_request and member_limit are mutually exclusive: a link that queues requests cannot also cap seats, since nobody is admitted automatically.
| Field | Type | Description |
|---|---|---|
invite_link | String | The URL. Abbreviated with an ellipsis in the middle when the link was created by another administrator — so the string you receive is not always usable. |
creator | User | Who created it. For links your bot made, this is the bot. |
creates_join_request | Boolean | Joining raises a chat_join_request for approval instead of admitting immediately. Incompatible with member_limit. |
is_primary | Boolean | This is the chat's main link. There is exactly one, it cannot be deleted, and revoking it replaces the address rather than removing it. |
is_revoked | Boolean | No longer usable. Revoked links are retained rather than erased, so expect to see them in listings. |
name | String | Optional. Label of up to 32 characters, visible only to administrators. The practical way to attribute joins to a campaign. |
expire_date | Integer | Optional. Unix time the link stops working. Absent means it never expires. |
member_limit | Integer | Optional. Seats, 1–99999, after which the link stops admitting. Incompatible with creates_join_request. |
pending_join_request_count | Integer | Optional. Requests awaiting approval through this link. Present only for links that create join requests. |
ChatJoinRequest
Someone asked to join a chat where the bot can approve or decline.
user_chat_id is the field that makes vetting flows possible: it lets the bot message the applicant privately before they are in the chat. That window is limited — you may send messages until the request is resolved, and only while it is pending.
Answer with approveChatJoinRequest or declineChatJoinRequest. Leaving requests unanswered accumulates a queue an admin has to clear by hand, so make sure your handler resolves every request even on the error paths.
| Field | Type | Description |
|---|---|---|
chat | Chat | The chat being joined. |
from | User | The applicant. |
user_chat_id | Integer | Private-chat id for messaging the applicant directly. Usable while the request is pending even though the user has never written to the bot — the one documented exception to that rule. |
date | Integer | Unix time of the request. Use it to expire stale requests rather than letting the queue grow indefinitely. |
bio | String | Optional. The applicant's bio, if their privacy settings expose it. Frequently absent, and untrusted text when present — a common place to hide spam links. |
invite_link | ChatInviteLink | Optional. Which link they came through, when the bot can see it. The basis for attributing joins to a source. |
ForumTopic
A topic inside a forum-enabled supergroup.
A topic's message_thread_id is the message_id of the message that created it, which is why the two are interchangeable in the API and why topic ids are not sequential.
The General topic is special: it always exists, cannot be deleted, and has no creation message — some topic methods reject it, so branch on it rather than treating it as topic zero.
| Field | Type | Description |
|---|---|---|
message_thread_id | Integer | Identifies the topic and is passed as message_thread_id when sending into it. Omitting it on send drops the message into General instead of erroring. |
name | String | Topic title, up to 128 characters. |
icon_color | Integer | RGB colour of the icon as an integer, chosen from a fixed palette. Arbitrary values are not accepted at creation time. |
icon_custom_emoji_id | String | Optional. Custom emoji used as the icon, overriding the colour swatch. |
BotCommand
One command in the bot's menu — the list users see when they type /.
Registering commands is presentation only. It populates the menu and enables autocomplete; it does not cause the platform to route or validate anything. Your bot still receives /anything as ordinary text and must parse it, including commands you never registered.
In groups, commands arrive suffixed with the bot's username — /start@my_bot. Strip it before dispatching, or every command breaks the moment a second bot joins the group.
| Field | Type | Description |
|---|---|---|
command | String | Name without the leading slash, 1–32 characters, lowercase letters, digits and underscores only. Uppercase is rejected at registration but users can still type it. |
description | String | Shown beside the command in the menu, 1–256 characters. The main place users learn what your bot can do, so write it for them rather than for you. |
BotCommandScope
Which users, in which chats, see a given command list. Abstract — you send a concrete variant.
A union discriminated by type, with seven variants: default, all_private_chats, all_group_chats, all_chat_administrators, chat, chat_administrators and chat_member.
Scopes are resolved most specific first, and the lists do not merge — the first scope that matches supplies the entire menu. So setting a chat scope replaces the default list for that chat rather than adding to it, which is the usual reason commands disappear after a per-chat customisation.
Each scope also holds a separate list per language_code. An empty-language list is the fallback, so a user whose client reports an unlisted language sees the fallback and not your carefully written localisation.
| Field | Type | Description |
|---|---|---|
type | String | The discriminator, determining whether chat_id and user_id are required. |
chat_id | Integer or String | Required by the chat, chat_administrators and chat_member scopes. Ignored by the broad scopes. |
user_id | Integer | Required by the chat_member scope only — the narrowest scope, giving one person in one chat their own menu. |
ReactionType
A reaction on a message — either a standard emoji or a custom one. Abstract, with two concrete variants.
A union discriminated by type: emoji carries emoji, custom_emoji carries custom_emoji_id. Exactly one of the two payload fields is present.
Only emoji from the platform's supported set may be used, and a chat can narrow that further to a whitelist or disable reactions entirely. Read the allowed set from getChat before reacting, or expect reaction is not allowed.
| Field | Type | Description |
|---|---|---|
type | String | emoji or custom_emoji. Determines which of the fields below is populated. |
emoji | String | emoji type only. The reaction character itself, which must come from the supported set — arbitrary emoji are rejected. |
custom_emoji_id | String | custom_emoji type only. Identifier of a custom emoji, resolvable with getCustomEmojiStickers. |
MessageReactionUpdated
One user changed their reactions on a message, delivered as the message_reaction update.
Not delivered unless message_reaction appears in allowed_updates — it is excluded from the default set. This is almost always why reaction handling appears not to work.
The update reports whole before-and-after lists, not a delta. Diff old_reaction against new_reaction yourself to find what was added or removed; both arrays being non-empty is a change, not two separate events.
An empty new_reaction means every reaction was removed. Handle it, or your counters only ever increase.
| Field | Type | Description |
|---|---|---|
chat | Chat | Where the reacted-to message lives. |
message_id | Integer | The message reacted to, unique only within chat. |
user | User | Optional. Who changed their reaction. Absent when a chat reacted on its own behalf — check actor_chat then. |
actor_chat | Chat | Optional. The chat that reacted anonymously, present instead of user. |
date | Integer | Unix time of the change. |
old_reaction | Array of ReactionType | Complete set of reactions this actor had before. Empty means they had none. |
new_reaction | Array of ReactionType | Complete set after the change. Empty means all were removed. |
LabeledPrice
One line item of an invoice — a label and an amount.
amount is in the smallest unit of the currency, so 1.45 USD is 145. Currencies with a different exponent do not use two decimals, and some have none at all — a bare multiply by 100 is wrong for those. Never send a float.
GROM's payment story is the least settled part of this reference: the relationship between platform Stars and the internal Take balance is not finalised. Treat the payment types as provisional and check the Payments section before building against them.
| Field | Type | Description |
|---|---|---|
label | String | Line description shown in the checkout breakdown. Purely cosmetic, but visible to the buyer, so it is where refund disputes start. |
amount | Integer | Price in the smallest currency unit, as an integer. Line items may be negative to express a discount, but the invoice total must end up positive. |
Invoice
Summary of an invoice, as carried by a message containing one.
This is the summary shown on the message, not the full invoice you constructed. The line items and payload you sent are not echoed back — keep your own record keyed by start_parameter or by the payload you will see again on SuccessfulPayment.
| Field | Type | Description |
|---|---|---|
title | String | Product name shown at the top of the invoice. |
description | String | Product description, up to 255 characters. |
start_parameter | String | Deep-link parameter that regenerates this invoice. Empty for invoices that cannot be reopened. |
currency | String | Three-letter ISO 4217 code. Determines how total_amount is scaled — you cannot interpret the amount without it. |
total_amount | Integer | Total in the smallest currency unit, e.g. 145 for 1.45 USD. |
SuccessfulPayment
Confirmation that a payment was charged, arriving as a service message in the chat.
This is the only trustworthy signal that money moved. Answering pre_checkout_query with ok: true authorises the charge; it does not complete it. Fulfilling on the pre-checkout query means shipping goods for payments that later failed.
Store telegram_payment_charge_id — it is the handle for refunds and the key for reconciling against your provider's records. The field name is retained verbatim from the Telegram surface so that existing payment code keeps working unmodified; it is not evidence of a Telegram dependency.
Payment service messages can repeat if your handler is retried. Make fulfilment idempotent on invoice_payload or on the charge id, exactly as you would for webhook deliveries generally.
| Field | Type | Description |
|---|---|---|
currency | String | Three-letter ISO 4217 code of the currency actually charged. |
total_amount | Integer | Amount charged in the smallest currency unit. Verify it against what you invoiced before fulfilling — do not assume it matches. |
invoice_payload | String | The opaque payload you set when creating the invoice, returned verbatim. Your primary key for matching a payment to an order. |
shipping_option_id | String | Optional. Which shipping option the buyer chose, for flexible invoices. |
order_info | OrderInfo | Optional. Name, phone, email and shipping address, present only for the fields you requested. Personal data — subject to your retention policy, and never to be logged. |
telegram_payment_charge_id | String | Platform-side charge identifier. Required for issuing a refund, so persist it before you fulfil the order. |
provider_payment_charge_id | String | The payment provider's own identifier, for reconciliation against their dashboard. |
ShippingQuery
A buyer submitted a shipping address for an invoice priced flexibly.
Only produced when the invoice was created with flexible pricing and requests a shipping address. Answer with answerShippingQuery, either with delivery options or with an error message explaining why you cannot ship there — the error text is shown to the buyer, so write it for them.
The address is not validated by the platform. Treat every field as free text supplied by the user.
| Field | Type | Description |
|---|---|---|
id | String | Token for answerShippingQuery. Short-lived; a late answer leaves the buyer stuck at checkout. |
from | User | The buyer. |
invoice_payload | String | Your opaque payload from the invoice, for looking up what is being bought. |
shipping_address | ShippingAddress | Country code, state, city, two street lines and post code. User-entered and unvalidated — normalise before computing rates. |
PreCheckoutQuery
Final confirmation checkpoint before a charge is made. The bot must approve or reject it.
You have roughly 10 seconds to answer. Miss it and the payment fails for the buyer. Do your stock and price checks fast, or pre-reserve at invoice time so this step is a lookup rather than a computation.
This is the last point at which you can refuse — verify total_amount and currency against the order you recorded, since a stale invoice can be paid long after your prices changed. Rejecting with a clear error_message is far better than accepting a charge you will have to refund.
Approving here does not mean you have been paid. Wait for SuccessfulPayment before fulfilling.
| Field | Type | Description |
|---|---|---|
id | String | Token for answerPreCheckoutQuery, which must be called within the timeout. |
from | User | The buyer about to be charged. |
currency | String | Three-letter ISO 4217 code of the pending charge. |
total_amount | Integer | Amount about to be charged, in the smallest currency unit. Re-verify it against your own record rather than trusting it. |
invoice_payload | String | Your opaque payload, for resolving which order this is. |
shipping_option_id | String | Optional. The chosen shipping option, when the invoice was flexible. |
order_info | OrderInfo | Optional. Buyer-supplied name, phone, email and address, limited to the fields the invoice requested. Personal data — do not log it. |