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 | Accepted, but not needed: both are idempotent by themselves (a press is answered once, and a command list 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 and then returns its result.
- 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).
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 |
| 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 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.0.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 "/". |
| 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 a chat the user has started with this bot. |
editMessageText | POST | required | Edit the text and buttons of a message this bot sent. |
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.
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 a chat the user has started with this bot.
The sender is always the bot identified by the token; there is no sender_id parameter. The chat must be a private chat in which the user pressed Start and has not stopped or blocked the bot.
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 |
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.
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 |
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.
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.
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 |
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.
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 |
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 |
file_status_changed | file_status | FileStatusChange |
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. |
UpdateType | string | Consumers must ignore unknown values. One of: message, message_edited, message_deleted, callback_query, chat_access_changed, file_status_changed. |
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 | Only private chats in version 1. Consumers must ignore unknown values. One of: private. |
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. |
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 or url. 1-64 characters. |
url | string (uri) | No | An https:// link opened by the client. Exactly one of callback_data or url. Up to 2048 characters. |
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 | One of: photo, document. |
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. |
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 (voice, location, poll and the like). text then holds the readable fallback, if any. |
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 | |
user | User | Yes | |
status | string | Yes | started: the user pressed Start (again). stopped: the user pressed Stop. blocked: the user blocked the bot. 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 |
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 | |
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 |
| file_status_changed | file_status |
Editing an old message is not a new command: treat message_edited as a correction.
| 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 | |
file_status | FileStatusChange | No |
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. 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. |
File#
| Field | Type | Required | Description |
|---|---|---|---|
file_id | FileId | Yes | |
kind | string | Yes | One of: photo, document. |
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. |
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, ...). 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 |
CHAT_NOT_FOUND | 404 | No such chat for this bot (foreign chats are not listed) |
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 |
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 |