SMeet Bot API
API reference
The reference below the first sections is generated from the machine-readable contract, which you can also download as openapi.json. Method names, JSON fields and error codes are the same in every language of this documentation.
Requests#
- Base URL:
https://messenger.scrile.com/bot-api/v1. A method is called asPOST https://messenger.scrile.com/bot-api/v1/<method>with a JSON body in UTF-8 andContent-Type: application/json. - Read-only methods (getMe, getMyCommands, getFile, getDeliveryInfo, getWebhookInfo) also accept
GETwith query parameters. - uploadFile takes
multipart/form-data. File bytes are downloaded withGET /files/<file_id>/content. - A request body may be at most 21 MiB; a larger one is refused with
413 PAYLOAD_TOO_LARGEandlimitin bytes before it is read. - A path that is not a method answers
404 METHOD_NOT_FOUND.
Authentication#
Every call carries the bot token in a header:
POST /bot-api/v1/getUpdates HTTP/1.1
Host: messenger.scrile.com
Authorization: Bearer sbt1_EXAMPLE_REPLACE_WITH_YOUR_TOKEN
Content-Type: application/jsonA missing, malformed, unknown or revoked token, or the token of a deleted bot, gets the same answer: 401 INVALID_TOKEN with the header WWW-Authenticate: Bearer. A request that was already on its way when the bot was deleted may get 404 BOT_NOT_FOUND instead; the token does not work after that either. The token is never accepted in the URL, and SMeet never sends it anywhere, including your webhook.
Answers and errors#
Every answer is JSON. A success:
{"ok": true, "result": {"message_id": "7301", "chat": {"id": "550", "type": "private"}}}A failure has a matching HTTP status and this shape:
{
"ok": false,
"error": {"code": "RATE_LIMITED", "message": "Too many requests; wait retry_after seconds", "retry_after": 2},
"request_id": "3f1c2a7e-8b1d-4c52-9a55-0c7e1f7a1b90"
}error.codeis stable and is what your program checks.error.messageis English text for people; do not parse it.retry_after(seconds) is present only when a retry makes sense, and then theRetry-Afterheader carries the same value. SMeet sends it with every429and503, and with every409 CONSUMER_CONFLICTand409 FILE_NOT_READY.- Some codes add fields to
error:fieldnames the parameter at fault withINVALID_REQUEST,confirmed_offsetandmax_issued_update_idcome with the offset errors,reasonwithFILE_REJECTED,limit(the largest accepted size in bytes) withPAYLOAD_TOO_LARGE. request_id, also sent as theX-Request-Idheader, identifies the request. Quote it when you contact support.- The proxy in front of the API answers its own limits in the same shape:
429 RATE_LIMITEDwithretry_after1, and413 PAYLOAD_TOO_LARGE, both with an emptyrequest_id. The proxy's413may come withoutlimit.
Every code with its HTTP status is in the table of error codes.
Identifiers#
All identifiers (update_id, chat_id, message_id, epoch, offsets) are decimal strings of up to 20 digits. They can exceed 2^53, so never convert them to floating point numbers; you never need arithmetic on them either, because the server returns next_offset with every batch. file_id and the id of a callback query are opaque strings, and event_id is a UUID.
Idempotency-Key#
Methods that change something take an Idempotency-Key header: 1 to 128 characters of A-Z a-z 0-9 . _ : -.
| Method | Idempotency-Key |
|---|---|
| sendMessage, editMessageText, sendPhoto, sendDocument, uploadFile, rejectUpdate | Required. Without it: 400 IDEMPOTENCY_KEY_REQUIRED. |
| answerCallbackQuery, setMyCommands, setMyConditionsMode | Accepted, but not needed: they are idempotent by themselves (a press is answered once, and a command list or a mode replaces the previous one), so the key is not stored. |
- Same key, same parameters: the result of the first call comes back, and nothing is done twice. Before answering, SMeet checks the token and the chat again, so a repeat after the user pressed Stop fails with
BOT_STOPPED_OR_BLOCKED. - Same key, other parameters:
409 IDEMPOTENCY_CONFLICT. Parameters are compared as JSON, so key order and spacing do not matter. - A failure before the action (
FILE_NOT_READY,429,503, a validation error) does not use up the key. Repeat the same request with the same key. - A repeat that arrives while the first call is still running waits for it, at most 2 seconds, and then returns its result; after 2 seconds it gets
503 TEMPORARILY_UNAVAILABLEwithretry_after1. - A conditional request repeated with the same key and body returns the result of the first success without checking the chat and the conditions again: they were checked together with the first call. So an outbox learns the outcome within the 7 days even after a Stop. A conditional repeat with another value of a condition is another body:
409 IDEMPOTENCY_CONFLICT. A refusal, a conflict included, uses up no key. - Keys belong to one bot and one method: the same key in sendMessage and in editMessageText are two different keys. uploadFile keys are unique per bot. SMeet keeps keys for 7 days.
Build keys from the update you are answering and the action, for example <event_id>:reply: a repeated or replayed update then produces the same key, and SMeet returns the reply it already sent. Your own side effects (a database row, a payment) need the same protection on your side, keyed by event_id (Deduplication).
Capabilities#
getMe lists in capabilities what this server promises beyond version 1.1.0. Skip values you do not know.
| Value | The server |
|---|---|
chat_context | Has getChatContext. |
access_revisions | Puts access_revision in every Chat, and installation_generation in group and channel chats, and checks expected_access_revision and expected_installation_generation. |
message_revisions | Puts message_revision in message, edited_message and the results of sending and editing, and checks expected_message_revision. |
initiator_conditions | Checks initiator. |
strict_conditions | Has setMyConditionsMode and getMe.conditions_mode. |
mini_apps | Accepts the mini_app button and delivers mini_app_submission (Mini Apps). |
A server that promises none of them answers getMe without capabilities, as version 1.1.0 did. Rules for your program:
- Send a condition only when its value is in
capabilitiesof the last getMe. A server without it does not check the condition: an older server skips the parameter silently and performs the action without the condition, and a 1.2.0 server with conditions switched off refuses the call with400 INVALID_REQUESTand the parameter infield, without performing it. - Call getMe when you start and at least every 5 minutes; at once after
404 METHOD_NOT_FOUNDfrom getChatContext or setMyConditionsMode, after an answer of rule 3 and after an update of rule 5. - Every success of a 1.2.0 server to sendMessage, sendPhoto, sendDocument, editMessageText, deleteMessage, pinChatMessage, unpinChatMessage and restrictChatMember carries
result.chat.access_revision, also the answer to a repeat by key. Without it the conditions were not checked: stop the actions that depend on them and tell an operator. - After any time without the capability (the server rolled back, the platform switched conditions off), treat every stored revision as invalid: the older code may have changed access and messages without new revisions.
- A fresh update with a
chatbut withoutchat.access_revisionwhileaccess_revisionsis listed means the server rolled back: call getMe at once and apply rule 4. Fresh means adatelater than the moment you first saw the capability, with a margin for clock drift. Replays from FAILED and updates without achatare no such sign.
Conditional requests#
A changing call can carry conditions. The server checks them and performs the action in one transaction: a Stop, a new installation or an edit at the same moment either waits for the call or has happened already and is seen by it. Checking with getChatContext first does not do that, because its answer is out of date at once.
| Parameter | Holds while |
|---|---|
expected_access_revision | the chat's access_revision is this one: the conversation's in a private chat, the installation's in a group, topic or channel |
expected_installation_generation | the bot's installation in the group of chat_id is still this one |
expected_message_revision | the message message_id has this message_revision |
initiator | a person holds a role in a group now (Groups) |
| Method | expected_access_revision | expected_installation_generation | expected_message_revision | initiator |
|---|---|---|---|---|
| sendMessage, sendPhoto, sendDocument | yes | groups, topics, channels | no | yes |
| editMessageText, deleteMessage | yes | groups, topics, channels | yes | yes |
| pinChatMessage, unpinChatMessage | yes | yes | yes | yes |
| restrictChatMember | yes | yes | no | yes |
- A condition a method does not take,
expected_installation_generationin a private chat, an unknown field whose name starts withexpected_and an unknown field insideinitiatorget400 INVALID_REQUESTwithfield: a misspelt condition never turns into an unconditional call. answerCallbackQuery, rejectUpdate and setMyCommands take no conditions. - A value is a string of 1 to 64 characters, compared with the stored one as a string;
nullis the same as no parameter. A value that is not a string gets400 INVALID_REQUEST. - Take every value from the update the decision was made on, and pass it on unchanged. Never refresh it from a later update, a result or getChatContext: that would revive an action after Stop and Start or after the bot was installed again. A button press carries the revisions of the moment of the press; when the press carries out a decision taken when the card was made, keep the revisions of that event with the card (Revisions in updates).
| Answer | When | field | What to do |
|---|---|---|---|
409 ACCESS_REVISION_CONFLICT | The access changed | expected_access_revision or initiator.expected_access_revision | Do not retry. Cancel the task or bring it back to a person. |
409 INSTALLATION_GENERATION_CONFLICT | The bot was installed again | expected_installation_generation or initiator.expected_installation_generation | The same; do not carry settings of the old installation over by themselves. |
409 MESSAGE_REVISION_CONFLICT | The message changed | expected_message_revision | Wait for message_edited with the new revision and decide again. |
403 INITIATOR_NOT_AUTHORIZED | The person does not hold the role now | initiator.user_id | Do not retry; the action needs a new decision of a person who may take it. |
The errors never carry the current values, so that no library slips them into an old task. After a Stop a conditional send gets 403 BOT_STOPPED_OR_BLOCKED, after Stop and a new Start 409 ACCESS_REVISION_CONFLICT; both are final for the task. The rate limit is checked before the locks, so a 429 may come first and the retry after retry_after gets the conflict. A changing call waits for a revocation running at the same moment for at most 2 seconds, then answers 503 TEMPORARILY_UNAVAILABLE with retry_after 1; nothing was done, and the same key is safe to repeat.
POST /bot-api/v1/deleteMessage HTTP/1.1
Host: messenger.scrile.com
Authorization: Bearer sbt1_EXAMPLE_REPLACE_WITH_YOUR_TOKEN
Idempotency-Key: 0b7c9a52-51f4-4c55-9a55-7e1f0c7a1b90:delete
Content-Type: application/json
{"chat_id": "90020", "message_id": "880123", "expected_message_revision": "2",
"expected_installation_generation": "184505", "expected_access_revision": "184510"}If the person edited the message in between:
{
"ok": false,
"error": {"code": "MESSAGE_REVISION_CONFLICT", "message": "The message changed after expected_message_revision",
"field": "expected_message_revision"},
"request_id": "5d0e7c1a-2f8b-4d6e-9a31-1c2b3d4e5f60"
}Strict mode#
setMyConditionsMode with {"mode": "strict"} makes the server refuse a new changing call that lacks the conditions it needs, with 400 INVALID_REQUEST naming the missing parameter in field; {"mode": "optional"} switches it off. The mode is shown in getMe.conditions_mode and is off by default.
| Chat of the call | Required |
|---|---|
| Private | expected_access_revision |
| Group, topic, channel | expected_access_revision and expected_installation_generation; for deleteMessage also expected_message_revision |
initiator is never required. A repeat by key of a call made before the mode was switched on returns its result. The mode works only while strict_conditions is listed: on a server without it nothing is refused, so keep checking capabilities (rule 1 above).
Limits#
Requests over a limit get 429 RATE_LIMITED with retry_after. A short burst above the rate is allowed up to the number in the last column.
| What is counted | Rate | Burst |
|---|---|---|
| Messages to one chat: sendMessage, editMessageText, sendPhoto, sendDocument | 1 per second | 3 |
| The same methods, per bot | 10 per second | 20 |
| The same methods, all bots of one owner together | 20 per second | 40 |
| getUpdates, per bot | 5 per second | 10 |
| getChatContext, per bot | 5 per second | 10 |
| uploadFile, per bot | 2 per second | 5 |
| File downloads, per bot | 5 per second | 10 |
| All requests from one IP address | 20 per second | 40 |
Other limits: text up to 4096 characters and buttons as in Messages and buttons; photos up to 10 MiB, documents up to 20 MiB and 200 MiB of uploads per bot per UTC day (Photos and documents); a queue of up to 10,000 updates or 50 MiB per bot (Receiving updates). The platform may set other limits for a single bot, and another total rate for all bots of one owner.
Versions and compatibility#
- The major version is part of the path:
/bot-api/v1. A change that could break a working program gets a new path;v1keeps working. - Within
v1SMeet adds things: new fields in objects, new update types, new values ofreasonandstatus, new error codes. Your program must ignore fields and update types it does not know and treat unknown values as "other". - Every change of the contract is listed in the API changelog. The current version is 1.2.0; what it adds is listed by capabilities, so a program can tell a server that has it from an older one.
- The contract itself is published as openapi.json (OpenAPI 3.1); generate a client from it or use it in your tests.
Generated from the contract SMeet Bot API 1.2.0 (OpenAPI 3.1.0). Base URL: https://messenger.scrile.com/bot-api/v1. Machine-readable: openapi.json.
HTTP API for programs that act as SMeet bots.
Methods#
Every method is called as POST https://messenger.scrile.com/bot-api/v1/<method> with a JSON body; read-only methods also accept GET with query parameters. Authentication: The bot token shown once in "My bots" / SMeet BotFather. Format sbt1_<key id>_<secret>; treat it as opaque. SMeet stores only a hash. A revoked token fails with 401 INVALID_TOKEN on every node immediately.
| Method | HTTP | Idempotency-Key | Description |
|---|---|---|---|
| BotThe bot's own profile and commands. | |||
getMe | GET POST | Check the token and return the bot's public profile. | |
getMyCommands | GET POST | Read the command menu. | |
setMyCommands | POST | optional | Replace the command menu shown when the user types "/". |
setMyConditionsMode | POST | optional | Switch the strict conditions mode on or off. |
| UpdatesReceiving updates with long polling. | |||
getUpdates | POST | Receive pending updates with long polling. | |
rejectUpdate | POST | required | Move one issued, unconfirmed update to FAILED. |
| MessagesSending and editing messages, answering button presses. | |||
sendMessage | POST | required | Send a text message to an allowed private, group, topic or channel chat. |
editMessageText | POST | required | Edit the text and buttons of a message this bot sent. |
deleteMessage | POST | required | Delete a message. |
pinChatMessage | POST | required | Pin a message in a group, topic or channel. |
unpinChatMessage | POST | required | Unpin a message in a group, topic or channel. |
restrictChatMember | POST | required | Remove a member from a group or channel. |
getChatContext | GET POST | Read the current state of one chat of this bot, with its revisions. | |
answerCallbackQuery | POST | optional | Confirm that a button press was handled. |
| FilesPhotos and documents. | |||
uploadFile | POST | required | Upload a photo or document for later sending. |
sendPhoto | POST | required | Send a ready photo file to a chat. |
sendDocument | POST | required | Send a ready document file to a chat. |
getFile | GET POST | Read a file's metadata and processing status. | |
downloadFile | GET | Download the bytes of a ready file. | |
| DeliveryRead-only delivery diagnostics. | |||
getDeliveryInfo | GET POST | Read the delivery mode, queue sizes and the last error. | |
getWebhookInfo | GET POST | Read the webhook state (a subset of getDeliveryInfo). | |
getMe#
GET POST /getMeBot
Check the token and return the bot's public profile.
Result#
The result field holds Bot. The bot.
On failure the answer is an error object with a code from the table of error codes.
getMyCommands#
GET POST /getMyCommandsBot
Read the command menu.
Parameters#
| Name | Type | Required |
|---|---|---|
language_code | LanguageCode | No |
With GET pass them as query parameters, with POST as fields of a JSON body.
Result#
The result field holds array of BotCommand. The commands.
On failure the answer is an error object with a code from the table of error codes.
setMyCommands#
POST /setMyCommandsBot
Replace the command menu shown when the user types "/".
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | No | Accepted and not needed: the method is idempotent by itself (a repeated identical call gives the same result), so the key is not stored. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
commands | array of BotCommand | Yes | Up to 100 items. |
language_code | LanguageCode | No | Omit for the default list shown to every language. |
Result#
The result field holds true. Saved.
On failure the answer is an error object with a code from the table of error codes.
setMyConditionsMode#
POST /setMyConditionsModeBot
Switch the strict conditions mode on or off.
In the strict mode a new call of a changing method without the conditions it needs fails with 400 INVALID_REQUEST naming the missing parameter: expected_access_revision in a private chat; expected_access_revision and expected_installation_generation in a group, topic or channel, and expected_message_revision too for deleteMessage. A repeat by key of a call made before the switch still returns its result. The mode is shown in getMe.conditions_mode. Available when capabilities lists strict_conditions; otherwise 404 METHOD_NOT_FOUND.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | No | Accepted and not needed: the method is idempotent by itself (a repeated identical call gives the same result), so the key is not stored. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | One of: optional, strict. |
Result#
The result field holds true. Saved.
On failure the answer is an error object with a code from the table of error codes.
getUpdates#
POST /getUpdatesUpdates
Receive pending updates with long polling.
Returns pending updates starting at the confirmed position. Passing offset confirms every update with update_id < offset; confirmed updates are never returned again. Unconfirmed updates are returned again on the next call (after a lost response or a restart), so the program must tolerate repeats.
The request waits up to timeout seconds and returns as soon as an update arrives, not at the end of the timeout. An empty result after a timeout changes nothing.
One active consumer per bot. The first call names a consumer_id and receives a lease with an epoch; later calls pass the same consumer_id and epoch and extend the lease (TTL 60 seconds). A second concurrent call, even with the same consumer_id, is rejected with CONSUMER_CONFLICT. A call carrying a foreign epoch (another consumer, the owner's lease reset or a mode switch took the lease since) gets a new lease but confirms nothing: its offset is ignored and unconfirmed updates come again. If your own lease expired and nobody took it in between, your next call gets a new lease and its offset still confirms.
Offset rules (confirmed_offset = position after the confirmed prefix, max_issued_update_id = highest update_id ever returned):
offset = confirmed_offsetis always accepted (a no-op confirmation).confirmed_offset < offset <= max_issued_update_id + 1confirms the prefix.offset < confirmed_offsetfails with409 CURSOR_BEHIND; the error carries the currentconfirmed_offset. Nothing is re-sent.offset > max_issued_update_id + 1fails with400 OFFSET_NOT_ISSUEDunless it equals thenext_offsetthe server returned for the current lease.- Omitting
offsetcontinues from the position stored by the server.
Example: after update 42 was returned, offset=43 confirms it; offset=44 is rejected; once 43 is confirmed, offset=42 gets CURSOR_BEHIND.
Updates that were cancelled, expired or moved to FAILED before being confirmed are reported in skipped; next_offset then jumps over them. Always confirm with the returned next_offset after durably storing the whole batch.
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
offset | Id | No | |
limit | integer | No | From 1 to 100. Default: 100. |
timeout | integer | No | Seconds to wait when nothing is pending. 0 returns immediately. From 0 to 25. Default: 25. |
consumer_id | string | Yes | A stable name of this receiving process, e.g. its host name. 1-64 characters. Pattern: ^[A-Za-z0-9._-]+$. |
epoch | Id | No | The epoch of the lease received earlier. Keep it with your offset and pass it after a restart too; omit it only when you have none. Without it, a lease this consumer still holds answers 409 CONSUMER_CONFLICT until it expires (retry_after tells how long); then call again. |
Result#
The result field holds GetUpdatesResult. A batch of updates (possibly empty).
On failure the answer is an error object with a code from the table of error codes.
rejectUpdate#
POST /rejectUpdateUpdates
Move one issued, unconfirmed update to FAILED.
Use when the program cannot process one specific update and does not want it to block the rest of the batch. The update leaves the active sequence, keeps its payload for the original retention window, and appears in the owner's FAILED list where it can be replayed or skipped. Only updates already returned to the current lease and not yet confirmed can be rejected.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
update_id | Id | Yes | |
reason | string | Yes | Shown to the owner. Must not contain secrets or personal data. 1-256 characters. |
consumer_id | string | Yes | |
epoch | Id | Yes |
Result#
The update is FAILED. The result field holds an object with these fields.
| Field | Type | Required | Description |
|---|---|---|---|
update_id | Id | Yes | |
status | string | Yes | Always failed. |
On failure the answer is an error object with a code from the table of error codes.
sendMessage#
POST /sendMessageMessages
Send a text message to an allowed private, group, topic or channel chat.
The sender is always the bot identified by the token; there is no sender_id parameter. A private chat must still be started. A group or topic needs can_send_messages; a channel needs can_post_messages. Takes the conditions expected_access_revision, expected_installation_generation (groups, topics and channels) and initiator. A mini_app button needs a private chat of a bot with a Mini App.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
text | string | Yes | Plain text. Strings starting with "__META__:" or "__SYSTEM__" are rejected. 1-4096 characters. |
reply_to_message_id | Id | No | A message in the same chat. |
reply_markup | InlineKeyboardMarkup | No | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
initiator | Initiator | No |
Result#
The result field holds Message. The sent message.
On failure the answer is an error object with a code from the table of error codes.
editMessageText#
POST /editMessageTextMessages
Edit the text and buttons of a message this bot sent.
Only messages sent by this bot can be edited. If reply_markup is omitted, the message has no buttons after the edit. Takes the conditions of sendMessage and expected_message_revision. A mini_app button needs a private chat of a bot with a Mini App, as in sendMessage.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
message_id | Id | Yes | |
text | string | Yes | 1-4096 characters. |
reply_markup | InlineKeyboardMarkup | No | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
expected_message_revision | Revision | No | Act only while the message's message_revision is this one; otherwise 409 MESSAGE_REVISION_CONFLICT. |
initiator | Initiator | No |
Result#
The result field holds Message. The edited message.
On failure the answer is an error object with a code from the table of error codes.
deleteMessage#
POST /deleteMessageMessages
Delete a message.
In a private chat a bot can delete only its own message. In a group, topic or channel the installation needs can_delete_messages. Takes the conditions expected_access_revision, expected_installation_generation (groups, topics and channels), expected_message_revision and initiator.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
message_id | Id | Yes | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
expected_message_revision | Revision | No | Act only while the message's message_revision is this one; otherwise 409 MESSAGE_REVISION_CONFLICT. |
initiator | Initiator | No |
Result#
The deleted message identity. The result field holds an object with these fields.
On failure the answer is an error object with a code from the table of error codes.
pinChatMessage#
POST /pinChatMessageMessages
Pin a message in a group, topic or channel.
The installation needs can_pin_messages. Takes the conditions expected_access_revision, expected_installation_generation, expected_message_revision and initiator.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
message_id | Id | Yes | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
expected_message_revision | Revision | No | Act only while the message's message_revision is this one; otherwise 409 MESSAGE_REVISION_CONFLICT. |
initiator | Initiator | No |
Result#
The message is pinned. The result field holds an object with these fields.
On failure the answer is an error object with a code from the table of error codes.
unpinChatMessage#
POST /unpinChatMessageMessages
Unpin a message in a group, topic or channel.
The installation needs can_pin_messages. Takes the conditions expected_access_revision, expected_installation_generation, expected_message_revision and initiator.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
message_id | Id | Yes | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
expected_message_revision | Revision | No | Act only while the message's message_revision is this one; otherwise 409 MESSAGE_REVISION_CONFLICT. |
initiator | Initiator | No |
Result#
The message is unpinned. The result field holds an object with these fields.
On failure the answer is an error object with a code from the table of error codes.
restrictChatMember#
POST /restrictChatMemberMessages
Remove a member from a group or channel.
The installation needs can_restrict_members. The group owner and the bot itself cannot be removed by this method. Takes the conditions expected_access_revision, expected_installation_generation and initiator.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
user_id | Id | Yes | |
expected_access_revision | Revision | No | Act only while the installation's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. |
initiator | Initiator | No |
Result#
The member is removed. The result field holds an object with these fields.
On failure the answer is an error object with a code from the table of error codes.
getChatContext#
GET POST /getChatContextMessages
Read the current state of one chat of this bot, with its revisions.
For a private chat: the person and the conversation's status, also when it is stopped, blocked or removed. For a group, topic or channel: the group's owner, the installation's permissions and when it was installed. With user_id, whether that person is the chat's person, or, in a group, known to the bot there, a member and the owner. The answer is a snapshot that is out of date at once: what makes an action safe is the condition of the changing call, not a read before it. Available when capabilities lists chat_context; otherwise 404 METHOD_NOT_FOUND. Limited to 5 calls a second per bot, bursts of 10.
Parameters#
| Name | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | chat.id from an update: a private chat, group, topic or channel of this bot. |
user_id | Id | No | A person to check; the answer is in user_check. |
With GET pass them as query parameters, with POST as fields of a JSON body.
Result#
The result field holds ChatContext. The chat as it is now.
On failure the answer is an error object with a code from the table of error codes.
answerCallbackQuery#
POST /answerCallbackQueryMessages
Confirm that a button press was handled.
Stops the user's pending indicator and optionally shows a short notice. A press that is not answered within 15 seconds shows "The bot did not respond" to the user; the bot can still answer later and edit its message. A callback query can be answered once, within 1 hour; a repeated identical answer returns the same result, and a different second answer fails with 409 IDEMPOTENCY_CONFLICT.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | No | Accepted and not needed: the method is idempotent by itself (a repeated identical call gives the same result), so the key is not stored. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
callback_query_id | string | Yes | |
text | string | No | A short notice shown to the user. Up to 200 characters. |
show_alert | boolean | No | Show the notice as a dialog instead of a toast. Default: false. |
Result#
The result field holds true. Answered.
On failure the answer is an error object with a code from the table of error codes.
uploadFile#
POST /uploadFileFiles
Upload a photo or document for later sending.
The file is stored in quarantine and scanned. The returned file_id has status scanning; it can be sent only after getFile reports ready. Limits: photos up to 10 MiB (JPEG, PNG, WebP, HEIC), documents up to 20 MiB, plus a daily volume quota per bot. The same Idempotency-Key never creates a second file.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (multipart/form-data)#
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | |
kind | string | No | One of: photo, document. Default: document. |
file_name | string | No | Up to 255 characters. |
Result#
The result field holds File. The stored file (usually scanning).
On failure the answer is an error object with a code from the table of error codes.
sendPhoto#
POST /sendPhotoFiles
Send a ready photo file to a chat.
The file must have kind photo (a JPEG, PNG, WebP or HEIC by its content). Any other ready file fails with 400 INVALID_REQUEST, field: file_id; send it with sendDocument instead. Takes the conditions of sendMessage. A mini_app button needs a private chat of a bot with a Mini App, as in sendMessage.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
file_id | FileId | Yes | A file this bot uploaded, or an attachment of a message this bot received in the same space. Must be ready: otherwise FILE_NOT_READY (retry later with the same Idempotency-Key) or FILE_REJECTED. |
caption | string | No | Up to 1024 characters. |
reply_to_message_id | Id | No | |
reply_markup | InlineKeyboardMarkup | No | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
initiator | Initiator | No |
Result#
The result field holds Message. The sent message.
On failure the answer is an error object with a code from the table of error codes.
sendDocument#
POST /sendDocumentFiles
Send a ready document file to a chat.
Takes the conditions of sendMessage. A mini_app button needs a private chat of a bot with a Mini App, as in sendMessage.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Yes | Unique per logical operation (a UUID is fine). Reuse the same key when retrying after a timeout, 429 or 503. Bind it to the event_id you are answering and the action, so a replayed update does not produce a second reply. |
Request body (JSON)#
| Field | Type | Required | Description |
|---|---|---|---|
chat_id | Id | Yes | |
file_id | FileId | Yes | A file this bot uploaded, or an attachment of a message this bot received in the same space. Must be ready: otherwise FILE_NOT_READY (retry later with the same Idempotency-Key) or FILE_REJECTED. |
caption | string | No | Up to 1024 characters. |
reply_to_message_id | Id | No | |
reply_markup | InlineKeyboardMarkup | No | |
expected_access_revision | Revision | No | Act only while the chat's access_revision is this one; otherwise 409 ACCESS_REVISION_CONFLICT. |
expected_installation_generation | Revision | No | Groups, topics and channels: act only while the installation is this one; otherwise 409 INSTALLATION_GENERATION_CONFLICT. In a private chat, 400 INVALID_REQUEST. |
initiator | Initiator | No |
Result#
The result field holds Message. The sent message.
On failure the answer is an error object with a code from the table of error codes.
getFile#
GET POST /getFileFiles
Read a file's metadata and processing status.
Works for files the bot uploaded and for attachments of messages it received. The download_path is present only while the status is ready and access is still allowed. For scanning, the response carries retry_after.
Parameters#
| Name | Type | Required |
|---|---|---|
file_id | FileId | Yes |
With GET pass them as query parameters, with POST as fields of a JSON body.
Result#
The result field holds File. The file.
On failure the answer is an error object with a code from the table of error codes.
downloadFile#
GET /files/{file_id}/contentFiles
Download the bytes of a ready file.
Requires the bot token and current access rights on every call. There is no public or permanent storage URL. Access ends when the source message is deleted, the user stops or blocks the bot, or the user leaves the space.
Parameters#
| Name | Type | Required |
|---|---|---|
file_id | FileId | Yes |
Path parameters are part of the address.
Result#
The answer is the file itself, not JSON. The file bytes. Response headers: Content-Disposition.
On failure the answer is an error object with a code from the table of error codes.
getDeliveryInfo#
GET POST /getDeliveryInfoDelivery
Read the delivery mode, queue sizes and the last error.
Never returns the webhook secret.
Result#
The result field holds DeliveryInfo. Delivery state.
On failure the answer is an error object with a code from the table of error codes.
getWebhookInfo#
GET POST /getWebhookInfoDelivery
Read the webhook state (a subset of getDeliveryInfo).
Result#
The result field holds WebhookInfo. Webhook state.
On failure the answer is an error object with a code from the table of error codes.
Webhook request#
POST your HTTPS address
SMeet delivers one update to the owner's HTTPS endpoint.
Sent only when the bot's delivery mode is webhook. One request in flight per bot; network timeout 10 seconds; redirects are not followed.
Verify X-SMeet-Signature before trusting the body: t is a Unix timestamp and v1 is the lowercase hex HMAC-SHA256 of "<t>.<raw request body>" keyed with the webhook secret shown once in "My bots". Reject timestamps more than 5 minutes away from your clock. The API token is never sent to the webhook.
Store the update durably (unique by event_id) and answer any 2xx quickly; run long business logic afterwards. A 2xx confirms receipt, not business success. Lost responses cause repeats with the same update_id and event_id.
Response handling: 2xx confirms. 400, 413 or 422 marks this update FAILED and moves on. 401, 403, 404, 410, a certificate that does not validate, an address webhooks may not reach, a redirect and any other unsupported answer pause the endpoint until the owner fixes it and resumes delivery in "My bots" (state paused, the cause in last_error); nothing is retried meanwhile, no update is charged and the queue is kept. 429 slows down and honours Retry-After. DNS and connection errors, and connect or TLS handshake timeouts before the request is sent, keep the update pending and back off (5 seconds, doubling up to 5 minutes). 5xx, 408 and failures after the request started share a budget of 5 attempts or 10 minutes per update, after which it becomes FAILED; three such updates in a row open a circuit breaker that probes with the current update before continuing.
Headers#
| Header | Type | Required | Description |
|---|---|---|---|
X-SMeet-Signature | string | Yes | Example: t=1790416800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd |
X-SMeet-Event-Id | string (uuid) | Yes | |
X-SMeet-Update-Id | Id | Yes | |
X-SMeet-Delivery-Attempt | integer | Yes | At least 1 |
Body#
An Update object in JSON.
Your answer#
Accepted. Any 2xx is treated the same way; the body is ignored.
Update types#
The type field of an Update names exactly one payload field that is present. Consumers must ignore unknown values.
type | Payload field | Payload type |
|---|---|---|
message | message | Message |
message_edited | edited_message | Message |
message_deleted | deleted_message | DeletedMessage |
callback_query | callback_query | CallbackQuery |
chat_access_changed | chat_access | ChatAccess |
my_chat_member | my_chat_member | MyChatMember |
delivery_gap | delivery_gap | DeliveryGap |
file_status_changed | file_status | FileStatusChange |
mini_app_submission | mini_app_submission | MiniAppSubmission |
Types#
Simple types#
Strings with a fixed format. Fields refer to them by name.
| Name | Type | Description |
|---|---|---|
Id | string | A decimal identifier passed as a string. Not a secret, not a permission. Pattern: ^[0-9]{1,20}$. Example: 1842. |
FileId | string | An opaque file identifier valid only for this bot. Pattern: ^[A-Za-z0-9_-]{16,64}$. Example: f_Qm9vdGZpbGVfMDAwMDAx. |
IdempotencyKey | string | 1-128 characters. Pattern: ^[A-Za-z0-9._:-]+$. |
LanguageCode | string | Pattern: ^[a-z]{2}$. Example: en. |
Timestamp | string (date-time) | RFC 3339, UTC. Example: 2026-09-26T10:00:00Z. |
Revision | string | An opaque version of an access, an installation or a message. Store it as a string and compare it only for equality; never compute with it. 1-64 characters. Example: 184502. |
UpdateType | string | Consumers must ignore unknown values. One of: message, message_edited, message_deleted, callback_query, chat_access_changed, my_chat_member, delivery_gap, file_status_changed, mini_app_submission. |
FileStatus | string | One of: scanning, ready, rejected, scan_failed. |
User#
Only what the chat needs. E-mail, phone, other chats, other accounts and sessions are never included.
| Field | Type | Required | Description |
|---|---|---|---|
id | Id | Yes | |
display_name | string | Yes | |
username | string | No | Public address without "@", when the user has one. |
is_bot | boolean | Yes |
Chat#
| Field | Type | Required | Description |
|---|---|---|---|
id | Id | Yes | |
type | string | Yes | Consumers must ignore unknown values. One of: private, group, channel. |
group_id | Id | No | Parent group or channel for group and channel chats. |
title | string | No | Current group or channel title. |
topic_id | Id | No | Present when this chat id identifies a topic inside the parent group or channel. |
access_revision | Revision | No | The version of the bot's access to this chat when the object was made: the conversation's for a private chat, the installation's in the parent group for a group, topic or channel. Pass it back as expected_access_revision. Present in every object of a 1.2.0 server. |
installation_generation | Revision | No | Groups and channels only: which installation of the bot this is. Installing the bot again gives a new one. Pass it back as expected_installation_generation. |
Space#
| Field | Type | Required | Description |
|---|---|---|---|
id | Id | Yes | |
kind | string | Yes | One of: public, organization. |
name | string | No |
Bot#
| Field | Type | Required | Description |
|---|---|---|---|
id | Id | Yes | The bot's account id, the same value as from.id of the messages the bot sends. |
username | string | Yes | |
display_name | string | Yes | |
description | string | No | |
is_bot | boolean | Yes | Always true. |
space | Space | Yes | |
status | string | Yes | One of: active, paused_by_owner, suspended_by_admin. |
delivery_mode | string | Yes | One of: polling, webhook. |
can_send_files | boolean | No | False while file support is turned off for this installation. |
capabilities | array of string | No | What this server promises beyond 1.1.0. chat_context: getChatContext. access_revisions: chats carry access_revision and installation_generation, and the changing methods check the expected values. message_revisions: messages carry message_revision, checked as expected_message_revision. initiator_conditions: the changing methods check initiator. strict_conditions: setMyConditionsMode and conditions_mode. mini_apps: the mini_app button and the mini_app_submission update. Skip values you do not know. Missing on a server that promises none of them. |
conditions_mode | string | No | Present with the conditions' capabilities; set with setMyConditionsMode. One of: optional, strict. |
mini_app | BotMiniApp | No | The bot's Mini App, while capabilities lists mini_apps and the bot has one. |
BotMiniApp#
The Mini App of a bot, as getMe describes it.
| Field | Type | Required | Description |
|---|---|---|---|
id | Id | Yes | The same value as app_id of mini_app_submission. |
status | string | Yes | Consumers must ignore unknown values. One of: active, paused_by_owner, blocked_by_admin. |
published | boolean | Yes | Whether a reviewed configuration is live. A mini_app button opens only the published one; until then a person sees that the Mini App is not available yet. |
MiniAppButton#
Opens the bot's Mini App in the person's SMeet app, the published version only.
| Field | Type | Required | Description |
|---|---|---|---|
start_param | string | No | Handed to the Mini App when it opens. Empty or missing: no parameter. The Mini App's own server sees it after login, and a submission does not repeat it. Up to 64 characters. Pattern: ^[A-Za-z0-9_-]*$. |
InlineKeyboardButton#
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | 1-64 characters. |
callback_data | string | No | Returned in callback_query.data when pressed. Exactly one of callback_data, url or mini_app. 1-64 characters. |
url | string (uri) | No | An https:// link opened by the client. Exactly one of callback_data, url or mini_app. Up to 2048 characters. |
mini_app | MiniAppButton | No | Opens the bot's Mini App (capability mini_apps). Only in a private chat of a bot that has a Mini App; otherwise INVALID_REQUEST with reason mini_app_not_configured. The bot is not told about a press. Exactly one of callback_data, url or mini_app. |
InlineKeyboardMarkup#
| Field | Type | Required | Description |
|---|---|---|---|
inline_keyboard | array of array of InlineKeyboardButton | Yes | Rows of buttons; at most 8 buttons per row and 40 in total. 1-10 items. |
Attachment#
| Field | Type | Required | Description |
|---|---|---|---|
file_id | FileId | Yes | |
kind | string | Yes | voice is a person's voice message: the recording alone, with duration and, when the sender's app recognised the speech, transcription. One of: photo, document, voice. |
file_name | string | Yes | Sanitised name, safe to display and to use as a download name. |
mime_type | string | No | As the sender's app declared it. The type detected from the content is in getFile once the check is done: markup, programs and other dangerous types are refused there (type_not_allowed), and any other mismatch is stored under the detected type (a "photo" that is not a picture becomes a document). |
size | integer | Yes | As the sender's app declared it; getFile has the size of the stored copy. At least 0. |
processing_status | FileStatus | Yes | |
reason | string | No | Only when the attachment is already rejected or scan_failed, for example too_large for an attachment above the size a bot may receive. Same values as File.reason. |
duration | integer | No | Voice only. Length of the recording in whole seconds, as the sender's app measured it. At least 0. |
transcription | string | No | Voice only, and only when the sender's app recognised the speech. A convenience, not a guarantee: download the audio for anything that must be exact. The sender may replace it, and neither message_revision nor message_edited follows that. |
Message#
| Field | Type | Required | Description |
|---|---|---|---|
message_id | Id | Yes | |
chat | Chat | Yes | |
from | User | Yes | |
date | Timestamp | Yes | |
edit_date | Timestamp | No | |
text | string | No | Up to 4096 characters. |
reply_to_message_id | Id | No | |
attachments | array of Attachment | No | |
reply_markup | InlineKeyboardMarkup | No | |
has_unsupported_content | boolean | No | True when the message carries content this API version does not expose (round video, location, poll and the like). text then holds the readable fallback, if any. A voice message is not among them: it arrives as a voice attachment. |
message_revision | Revision | No | The version of this message: it changes with every edit of its text or quote, not with a voice message's transcription. Present in message, edited_message and the results of sending and editing; pass it back as expected_message_revision. |
CallbackQuery#
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Pass to answerCallbackQuery. |
from | User | Yes | |
message | object | Yes | The bot's message whose button was pressed. |
message.message_id | Id | Yes | |
message.chat | Chat | Yes | |
data | string | Yes | |
date | Timestamp | Yes |
ChatAccess#
| Field | Type | Required | Description |
|---|---|---|---|
chat | Chat | Yes | With the access_revision after the change. |
user | User | Yes | |
status | string | Yes | started: the user pressed Start (again). stopped: the user pressed Stop. blocked: the user blocked the bot, or added the bot's account to their general block list; taking it off that list sends no update. removed: the user left the bot's space, so the platform withdrew the bot's access. After anything but started, sending to this chat fails with BOT_STOPPED_OR_BLOCKED. One of: started, stopped, blocked, removed. |
start_param | string | No | The deep-link parameter of a ?start= link, for started only. Never a command itself, but Start also puts a visible message from the user in the chat, /start or /start <param>, which arrives as its own message update right after this one. A bot that greets on Start should react to one of the two, not both. Up to 64 characters. Pattern: ^[A-Za-z0-9_-]*$. |
date | Timestamp | Yes |
GroupBotPermissions#
| Field | Type | Required | Description |
|---|---|---|---|
read_all_messages | boolean | Yes | Receive every new human message. Without it, only addressed messages are delivered. |
can_send_messages | boolean | Yes | Send to groups and their topics. |
can_edit_messages | boolean | Yes | |
can_delete_messages | boolean | Yes | |
can_pin_messages | boolean | Yes | |
can_restrict_members | boolean | Yes | |
can_post_messages | boolean | Yes | Publish in channels; independent from can_send_messages. |
MyChatMember#
| Field | Type | Required | Description |
|---|---|---|---|
chat | Chat | Yes | With the access_revision and installation_generation after the change. |
from | User | Yes | The person who installed, updated or removed the bot. |
status | string | Yes | One of: member, left. |
permissions | GroupBotPermissions | Yes | |
date | Timestamp | Yes |
DeliveryGap#
A summary emitted after group-message backpressure ends. It says that new messages were deliberately not queued; it never contains the dropped message bodies.
| Field | Type | Required | Description |
|---|---|---|---|
chat | Chat | Yes | |
dropped_count | integer | Yes | At least 1. |
first_dropped_at | Timestamp | Yes | |
last_dropped_at | Timestamp | Yes |
DeletedMessage#
FileStatusChange#
Sent when a check ends: for attachments the bot received (with message_id and chat), and for the bot's own uploads (without them). Never sent into a chat the user has stopped or left. scan_failed is not always final: a file the scanner could not check (scanner_unavailable, scanner_outdated, scan_timeout) is checked again about an hour later, up to three rounds, and another file_status_changed follows; meanwhile getFile reports scanning again.
| Field | Type | Required | Description |
|---|---|---|---|
file_id | FileId | Yes | |
status | FileStatus | Yes | |
message_id | Id | No | The message the file is attached to, for received attachments. |
chat | Chat | No | The chat of that message. For an attachment received in a group or topic it is the group chat, with group_id, title and topic_id. |
reason | string | No | For rejected and scan_failed. Values: malware_detected, type_not_allowed (markup, programs and other refused types), too_large (a file above the limit is rejected; one the scanner refuses for its size ends as scan_failed), empty (no content), scanner_unavailable, scanner_outdated (signatures older than 3 days), scan_timeout, source_missing (the attachment disappeared before the check). Consumers must accept unknown values. |
Update#
Exactly one payload field is present, chosen by type:
| type | payload field |
|---|---|
| message | message |
| message_edited | edited_message |
| message_deleted | deleted_message |
| callback_query | callback_query |
| chat_access_changed | chat_access |
| my_chat_member | my_chat_member |
| delivery_gap | delivery_gap |
| file_status_changed | file_status |
| mini_app_submission | mini_app_submission |
Editing an old message is not a new command: treat message_edited as a correction. The revisions in a payload are those of the moment the event happened, and a replay from FAILED hands them over unchanged; updates written before 1.2.0 have none.
| Field | Type | Required | Description |
|---|---|---|---|
update_id | Id | Yes | |
event_id | string (uuid) | Yes | |
type | UpdateType | Yes | |
replay_of_update_id | Id | No | Set when the owner replayed a FAILED update. |
date | Timestamp | Yes | |
message | Message | No | |
edited_message | Message | No | |
deleted_message | DeletedMessage | No | |
callback_query | CallbackQuery | No | |
chat_access | ChatAccess | No | |
my_chat_member | MyChatMember | No | |
delivery_gap | DeliveryGap | No | |
file_status | FileStatusChange | No | |
mini_app_submission | MiniAppSubmission | No |
MiniAppSubmission#
A person sent a form of the bot's Mini App to the bot and confirmed it in SMeet. SMeet fixes the application, the environment, the person and the chat; the page sends only data. One submission is one update, whatever the page retries.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | The submission, as the person's app and the Mini App know it. |
app_id | Id | Yes | |
environment | string | Yes | test comes only from the owner, accepted testers and reviewers. One of: production, test. |
client_request_id | string | Yes | The page's requestId of this form. A retry of the page with the same one is the same submission and gives no second update. 1-64 characters. Pattern: ^[A-Za-z0-9._:-]+$. |
from | User | Yes | |
chat | Chat | Yes | The private chat of the person with the bot, with the access_revision the Mini App was opened with. Answer there, with this revision as expected_access_revision. |
data | object | Yes | The form as the page sent it, a JSON object of up to 16 KiB. |
date | Timestamp | Yes |
Lease#
SkipRange#
| Field | Type | Required | Description |
|---|---|---|---|
from_update_id | Id | Yes | |
to_update_id | Id | Yes | Inclusive. |
reason | string | Yes | cancelled: access to the chat or its source ended before delivery, or a Mini App's submission lost its application. failed: moved to FAILED (rejectUpdate or the webhook budget). expired: the retention window ran out. skipped_by_owner: the owner skipped a FAILED update. delivered: already confirmed another way, for example by the webhook before the owner switched the bot back to long polling. Consumers must accept unknown values. One of: cancelled, failed, expired, skipped_by_owner, delivered. |
GetUpdatesResult#
| Field | Type | Required | Description |
|---|---|---|---|
updates | array of Update | Yes | |
confirmed_offset | Id | Yes | Position after the confirmed prefix, after applying this request's offset. |
next_offset | Id | Yes | Pass as offset after durably storing every update of this batch. |
skipped | array of SkipRange | Yes | |
lease | Lease | Yes | |
queue_gap | boolean | No | True when updates expired before delivery since the last confirmation. |
BotCommand#
| Field | Type | Required | Description |
|---|---|---|---|
command | string | Yes | Without the leading slash. Pattern: ^[a-z0-9_]{1,32}$. |
description | string | Yes | 1-256 characters. |
DeliveryError#
| Field | Type | Required | Description |
|---|---|---|---|
date | Timestamp | Yes | |
code | string | Yes | Before the request left: DNS_ERROR, CONNECT_ERROR, CONNECT_TIMEOUT, TLS_ERROR. After it left: TIMEOUT, NO_RESPONSE, IO_ERROR, HTTP_408, HTTP_5xx (the status itself, for example HTTP_503). Pausing until the owner resumes: HTTP_401, HTTP_403, HTTP_404, HTTP_410, TLS_CERTIFICATE_ERROR, ADDRESS_FORBIDDEN, REDIRECT_NOT_FOLLOWED and HTTP_<status> of any other unsupported answer. Also HTTP_400, HTTP_413, HTTP_422 (the update became FAILED), HTTP_429 (slowed down) and DISABLED_BY_PLATFORM. Consumers must accept unknown values. |
message | string | No | Sanitised; never contains secrets or message text. |
DeliveryInfo#
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | One of: polling, webhook. |
config_version | Id | Yes | |
pending_update_count | integer | Yes | |
failed_update_count | integer | Yes | |
oldest_pending_date | Timestamp | No | |
last_delivery_date | Timestamp | No | |
last_error | DeliveryError | No | |
confirmed_offset | Id | No | Polling mode. |
lease | Lease | No | Polling mode, when a consumer holds the lease. |
webhook | WebhookInfo | No | |
maintenance | boolean | Yes | True during a technical pause of the platform. |
retention_seconds | integer | No | How long unconfirmed updates are kept (604800 = 7 days). |
WebhookInfo#
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Empty string in polling mode. |
state | string | Yes | paused: waiting for the owner after an answer only the owner can fix (see last_error); the owner resumes delivery in "My bots". circuit_open: repeated failures, SMeet probes by itself. disabled_by_platform: an administrator turned the webhook off. One of: none, active, paused, circuit_open, disabled_by_platform. |
pending_update_count | integer | Yes | |
next_attempt_date | Timestamp | No | |
last_error | DeliveryError | No | |
max_connections | integer | No | Always 1. |
Initiator#
The person on whose decision the bot acts, and the role they must hold now. Checked in the parent group of chat_id together with the action; failing it gives 403 INITIATOR_NOT_AUTHORIZED, the same answer for "not a member", "not the owner" and "not known to the bot". An unknown field inside the object gives 400 INVALID_REQUEST.
| Field | Type | Required | Description |
|---|---|---|---|
user_id | Id | Yes | Usually callback_query.from.id or message.from.id. |
role | string | Yes | group_owner: the group's current owner. group_member: a member of the group known to the bot there (it received an update from them since the current installation). Both must be active members of the group's space. One of: group_member, group_owner. |
chat_id | Id | Yes | A group, topic or channel of this bot. When the method's own chat is a group, the same group; when it is a private chat, any group of the bot. |
expected_installation_generation | Revision | No | The installation of the bot in that group. |
expected_access_revision | Revision | No | The access revision of the bot's installation in that group. |
ChatContext#
One chat as it is now. Exactly one of private and group is present.
| Field | Type | Required | Description |
|---|---|---|---|
chat | Chat | Yes | With access_revision and, for groups, installation_generation of this moment. |
space | Space | Yes | |
private | ChatContextPrivate | No | |
group | ChatContextGroup | No | |
user_check | UserCheck | No | Present when user_id was passed. |
ChatContextPrivate#
| Field | Type | Required | Description |
|---|---|---|---|
user | User | Yes | |
status | string | Yes | As in chat_access_changed, and as sending would find it now: blocked also when the person blocked the bot's account in their general block list, removed also when they are no longer an active member of the bot's space. One of: started, stopped, blocked, removed. |
ChatContextGroup#
| Field | Type | Required | Description |
|---|---|---|---|
owner | User | Yes | |
permissions | GroupBotPermissions | Yes | |
installed_at | Timestamp | Yes | When the current installation began; the bot sees nothing from before it. |
UserCheck#
| Field | Type | Required | Description |
|---|---|---|---|
user_id | Id | Yes | |
is_chat_user | boolean | No | Private chats only. The person is the one of this chat. |
known | boolean | No | Groups only. The group's owner, or a person the bot received an update from in this group since the current installation (for about 8 days after the last one). |
is_member | boolean | No | Only with known true. In the group and an active member of its space. |
is_owner | boolean | No | Only with known true. The group's current owner and an active member of its space. |
File#
| Field | Type | Required | Description |
|---|---|---|---|
file_id | FileId | Yes | |
kind | string | Yes | One of: photo, document, voice. |
file_name | string | Yes | |
mime_type | string | No | |
size | integer | Yes | |
status | FileStatus | Yes | |
reason | string | No | For rejected and scan_failed. Values: malware_detected, type_not_allowed (markup, programs and other refused types), too_large (a file above the limit is rejected; one the scanner refuses for its size ends as scan_failed), empty (no content), scanner_unavailable, scanner_outdated (signatures older than 3 days), scan_timeout, source_missing (the attachment disappeared before the check). Consumers must accept unknown values. |
retry_after | integer | No | For scanning; seconds before asking again. |
download_path | string | No | Present only for ready files; relative to the API base, requires the token. |
Errors#
Any failure. See ErrorCode for the meaning of each code and the HTTP status it comes with. The Retry-After header is set whenever the body carries retry_after (429, 503, and 409 CONSUMER_CONFLICT and FILE_NOT_READY).
Headers of an error answer: Retry-After, X-Request-Id.
Error object#
Every failed call answers with this envelope and a matching HTTP status.
| Field | Type | Required | Description |
|---|---|---|---|
ok | boolean | Yes | Always false. |
error | Error | Yes | |
request_id | string | Yes |
The error field:
| Field | Type | Required | Description |
|---|---|---|---|
code | ErrorCode | Yes | |
message | string | Yes | Human-readable, English, free of secrets. Do not parse it. |
retry_after | integer | No | Seconds to wait before retrying. Present only when a retry makes sense. At least 1. |
confirmed_offset | Id | No | Present with CURSOR_BEHIND and OFFSET_NOT_ISSUED. |
max_issued_update_id | Id | No | Present with OFFSET_NOT_ISSUED. |
field | string | No | Present with INVALID_REQUEST when one parameter is at fault, with the four conflict and initiator codes (the condition that failed, such as expected_access_revision or initiator.user_id), and with CHAT_NOT_FOUND and GROUP_BOT_NOT_INSTALLED about the initiator's chat (initiator.chat_id). |
reason | string | No | Present with FILE_REJECTED: why the file cannot be used (not_a_photo, type_not_allowed, malware_detected, too_large, empty, scanner_unavailable, ...). With INVALID_REQUEST about reply_markup, mini_app_not_configured: a mini_app button in a group or from a bot without a Mini App. Consumers must accept unknown values. |
limit | integer | No | Present with PAYLOAD_TOO_LARGE: the largest accepted size in bytes. A 413 answered by the proxy in front of the API, before the request reaches SMeet, may come without it. |
Error codes#
| Code | HTTP | Meaning |
|---|---|---|
INVALID_REQUEST | 400 | Bad text, buttons or parameters |
IDEMPOTENCY_KEY_REQUIRED | 400 | The method needs an Idempotency-Key header |
OFFSET_NOT_ISSUED | 400 | offset jumps over updates that were never returned |
INVALID_TOKEN | 401 | Unknown or revoked token |
BOT_STOPPED_OR_BLOCKED | 403 | The user stopped or blocked the bot |
BOT_SUSPENDED | 403 | Paused by the owner or suspended by the platform |
SPACE_BOTS_DISABLED | 403 | The organisation turned bot runtime off |
GROUP_BOT_PERMISSION_DENIED | 403 | The bot was not granted this action in the group or channel |
INITIATOR_NOT_AUTHORIZED | 403 | The initiator does not hold the required role now; do not retry |
CHAT_NOT_FOUND | 404 | No such chat for this bot (foreign chats are not listed) |
GROUP_BOT_NOT_INSTALLED | 404 | The group installation has ended |
MESSAGE_NOT_FOUND | 404 | No such message for this bot |
UPDATE_NOT_FOUND | 404 | The update is not issued, already confirmed or not this bot's |
CALLBACK_QUERY_NOT_FOUND | 404 | Unknown or expired callback query |
BOT_NOT_FOUND | 404 | The bot was deleted while the request was on its way; its token no longer works |
FILE_NOT_FOUND | 404 | Unknown file, or access to it has ended |
METHOD_NOT_FOUND | 404 | No such method |
DELIVERY_MODE_CONFLICT | 409 | getUpdates while the bot is in webhook mode |
CONSUMER_CONFLICT | 409 | Another poll holds the lease, or the epoch is stale; retry_after says when to ask again |
CURSOR_BEHIND | 409 | offset is below the confirmed position |
IDEMPOTENCY_CONFLICT | 409 | The key was used with different parameters |
FILE_NOT_READY | 409 | The file is still being scanned |
ACCESS_REVISION_CONFLICT | 409 | The access to this chat changed; expected_access_revision no longer matches. Do not retry |
INSTALLATION_GENERATION_CONFLICT | 409 | The bot was installed again; expected_installation_generation no longer matches. Do not retry |
MESSAGE_REVISION_CONFLICT | 409 | The message changed after expected_message_revision. Do not retry |
PAYLOAD_TOO_LARGE | 413 | The upload exceeds the size limit |
FILE_REJECTED | 422 | The file failed the check or is not allowed; error.reason says why (not_a_photo, type_not_allowed, malware_detected, ...) |
RATE_LIMITED | 429 | Too many requests; wait retry_after |
QUOTA_EXCEEDED | 429 | Daily file quota used up; wait retry_after |
TEMPORARILY_UNAVAILABLE | 503 | Transient failure; retry with the same key |
BOTS_MAINTENANCE | 503 | Technical pause of the bot platform; retry later with the same key and offset |