SMeetBot API

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 as POST https://messenger.scrile.com/bot-api/v1/<method> with a JSON body in UTF-8 and Content-Type: application/json.
  • Read-only methods (getMe, getMyCommands, getFile, getDeliveryInfo, getWebhookInfo) also accept GET with query parameters.
  • uploadFile takes multipart/form-data. File bytes are downloaded with GET /files/<file_id>/content.
  • A request body may be at most 21 MiB; a larger one is refused with 413 PAYLOAD_TOO_LARGE and limit in 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:

HTTP
POST /bot-api/v1/getUpdates HTTP/1.1
Host: messenger.scrile.com
Authorization: Bearer sbt1_EXAMPLE_REPLACE_WITH_YOUR_TOKEN
Content-Type: application/json

A 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:

JSON
{"ok": true, "result": {"message_id": "7301", "chat": {"id": "550", "type": "private"}}}

A failure has a matching HTTP status and this shape:

JSON
{
  "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.code is stable and is what your program checks. error.message is English text for people; do not parse it.
  • retry_after (seconds) is present only when a retry makes sense, and then the Retry-After header carries the same value. SMeet sends it with every 429 and 503, and with every 409 CONSUMER_CONFLICT and 409 FILE_NOT_READY.
  • Some codes add fields to error: field names the parameter at fault with INVALID_REQUEST, confirmed_offset and max_issued_update_id come with the offset errors, reason with FILE_REJECTED, limit (the largest accepted size in bytes) with PAYLOAD_TOO_LARGE.
  • request_id, also sent as the X-Request-Id header, 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_LIMITED with retry_after 1, and 413 PAYLOAD_TOO_LARGE, both with an empty request_id. The proxy's 413 may come without limit.

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

MethodIdempotency-Key
sendMessage, editMessageText, sendPhoto, sendDocument, uploadFile, rejectUpdateRequired. Without it: 400 IDEMPOTENCY_KEY_REQUIRED.
answerCallbackQuery, setMyCommandsAccepted, 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 countedRateBurst
Messages to one chat: sendMessage, editMessageText, sendPhoto, sendDocument1 per second3
The same methods, per bot10 per second20
The same methods, all bots of one owner together20 per second40
getUpdates, per bot5 per second10
uploadFile, per bot2 per second5
File downloads, per bot5 per second10
All requests from one IP address20 per second40

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; v1 keeps working.
  • Within v1 SMeet adds things: new fields in objects, new update types, new values of reason and status, 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.

MethodHTTPIdempotency-KeyDescription
BotThe bot's own profile and commands.
getMeGET POSTCheck the token and return the bot's public profile.
getMyCommandsGET POSTRead the command menu.
setMyCommandsPOSToptionalReplace the command menu shown when the user types "/".
UpdatesReceiving updates with long polling.
getUpdatesPOSTReceive pending updates with long polling.
rejectUpdatePOSTrequiredMove one issued, unconfirmed update to FAILED.
MessagesSending and editing messages, answering button presses.
sendMessagePOSTrequiredSend a text message to a chat the user has started with this bot.
editMessageTextPOSTrequiredEdit the text and buttons of a message this bot sent.
answerCallbackQueryPOSToptionalConfirm that a button press was handled.
FilesPhotos and documents.
uploadFilePOSTrequiredUpload a photo or document for later sending.
sendPhotoPOSTrequiredSend a ready photo file to a chat.
sendDocumentPOSTrequiredSend a ready document file to a chat.
getFileGET POSTRead a file's metadata and processing status.
downloadFileGETDownload the bytes of a ready file.
DeliveryRead-only delivery diagnostics.
getDeliveryInfoGET POSTRead the delivery mode, queue sizes and the last error.
getWebhookInfoGET POSTRead 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#

NameTypeRequired
language_codeLanguageCodeNo

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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyNoAccepted 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)#

FieldTypeRequiredDescription
commandsarray of BotCommandYesUp to 100 items.
language_codeLanguageCodeNoOmit 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_offset is always accepted (a no-op confirmation).
  • confirmed_offset < offset <= max_issued_update_id + 1 confirms the prefix.
  • offset < confirmed_offset fails with 409 CURSOR_BEHIND; the error carries the current confirmed_offset. Nothing is re-sent.
  • offset > max_issued_update_id + 1 fails with 400 OFFSET_NOT_ISSUED unless it equals the next_offset the server returned for the current lease.
  • Omitting offset continues 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)#

FieldTypeRequiredDescription
offsetIdNo
limitintegerNoFrom 1 to 100. Default: 100.
timeoutintegerNoSeconds to wait when nothing is pending. 0 returns immediately. From 0 to 25. Default: 25.
consumer_idstringYesA stable name of this receiving process, e.g. its host name. 1-64 characters. Pattern: ^[A-Za-z0-9._-]+$.
epochIdNoThe 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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyYesUnique 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)#

FieldTypeRequiredDescription
update_idIdYes
reasonstringYesShown to the owner. Must not contain secrets or personal data. 1-256 characters.
consumer_idstringYes
epochIdYes

Result#

The update is FAILED. The result field holds an object with these fields.

FieldTypeRequiredDescription
update_idIdYes
statusstringYesAlways 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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyYesUnique 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)#

FieldTypeRequiredDescription
chat_idIdYes
textstringYesPlain text. Strings starting with "__META__:" or "__SYSTEM__" are rejected. 1-4096 characters.
reply_to_message_idIdNoA message in the same chat.
reply_markupInlineKeyboardMarkupNo

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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyYesUnique 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)#

FieldTypeRequiredDescription
chat_idIdYes
message_idIdYes
textstringYes1-4096 characters.
reply_markupInlineKeyboardMarkupNo

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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyNoAccepted 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)#

FieldTypeRequiredDescription
callback_query_idstringYes
textstringNoA short notice shown to the user. Up to 200 characters.
show_alertbooleanNoShow 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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyYesUnique 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)#

FieldTypeRequiredDescription
filefileYes
kindstringNoOne of: photo, document. Default: document.
file_namestringNoUp 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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyYesUnique 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)#

FieldTypeRequiredDescription
chat_idIdYes
file_idFileIdYesA 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.
captionstringNoUp to 1024 characters.
reply_to_message_idIdNo
reply_markupInlineKeyboardMarkupNo

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#

HeaderTypeRequiredDescription
Idempotency-KeyIdempotencyKeyYesUnique 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)#

FieldTypeRequiredDescription
chat_idIdYes
file_idFileIdYesA 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.
captionstringNoUp to 1024 characters.
reply_to_message_idIdNo
reply_markupInlineKeyboardMarkupNo

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#

NameTypeRequired
file_idFileIdYes

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#

NameTypeRequired
file_idFileIdYes

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#

HeaderTypeRequiredDescription
X-SMeet-SignaturestringYesExample: t=1790416800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-SMeet-Event-Idstring (uuid)Yes
X-SMeet-Update-IdIdYes
X-SMeet-Delivery-AttemptintegerYesAt 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.

typePayload fieldPayload type
messagemessageMessage
message_editededited_messageMessage
message_deleteddeleted_messageDeletedMessage
callback_querycallback_queryCallbackQuery
chat_access_changedchat_accessChatAccess
file_status_changedfile_statusFileStatusChange

Types#

Simple types#

Strings with a fixed format. Fields refer to them by name.

NameTypeDescription
IdstringA decimal identifier passed as a string. Not a secret, not a permission. Pattern: ^[0-9]{1,20}$. Example: 1842.
FileIdstringAn opaque file identifier valid only for this bot. Pattern: ^[A-Za-z0-9_-]{16,64}$. Example: f_Qm9vdGZpbGVfMDAwMDAx.
IdempotencyKeystring1-128 characters. Pattern: ^[A-Za-z0-9._:-]+$.
LanguageCodestringPattern: ^[a-z]{2}$. Example: en.
Timestampstring (date-time)RFC 3339, UTC. Example: 2026-09-26T10:00:00Z.
UpdateTypestringConsumers must ignore unknown values. One of: message, message_edited, message_deleted, callback_query, chat_access_changed, file_status_changed.
FileStatusstringOne of: scanning, ready, rejected, scan_failed.

User#

Only what the chat needs. E-mail, phone, other chats, other accounts and sessions are never included.

FieldTypeRequiredDescription
idIdYes
display_namestringYes
usernamestringNoPublic address without "@", when the user has one.
is_botbooleanYes

Chat#

FieldTypeRequiredDescription
idIdYes
typestringYesOnly private chats in version 1. Consumers must ignore unknown values. One of: private.

Space#

FieldTypeRequiredDescription
idIdYes
kindstringYesOne of: public, organization.
namestringNo

Bot#

FieldTypeRequiredDescription
idIdYesThe bot's account id, the same value as from.id of the messages the bot sends.
usernamestringYes
display_namestringYes
descriptionstringNo
is_botbooleanYesAlways true.
spaceSpaceYes
statusstringYesOne of: active, paused_by_owner, suspended_by_admin.
delivery_modestringYesOne of: polling, webhook.
can_send_filesbooleanNoFalse while file support is turned off for this installation.

InlineKeyboardButton#

FieldTypeRequiredDescription
textstringYes1-64 characters.
callback_datastringNoReturned in callback_query.data when pressed. Exactly one of callback_data or url. 1-64 characters.
urlstring (uri)NoAn https:// link opened by the client. Exactly one of callback_data or url. Up to 2048 characters.

InlineKeyboardMarkup#

FieldTypeRequiredDescription
inline_keyboardarray of array of InlineKeyboardButtonYesRows of buttons; at most 8 buttons per row and 40 in total. 1-10 items.

Attachment#

FieldTypeRequiredDescription
file_idFileIdYes
kindstringYesOne of: photo, document.
file_namestringYesSanitised name, safe to display and to use as a download name.
mime_typestringNoAs 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).
sizeintegerYesAs the sender's app declared it; getFile has the size of the stored copy. At least 0.
processing_statusFileStatusYes
reasonstringNoOnly 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#

FieldTypeRequiredDescription
message_idIdYes
chatChatYes
fromUserYes
dateTimestampYes
edit_dateTimestampNo
textstringNoUp to 4096 characters.
reply_to_message_idIdNo
attachmentsarray of AttachmentNo
reply_markupInlineKeyboardMarkupNo
has_unsupported_contentbooleanNoTrue 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#

FieldTypeRequiredDescription
idstringYesPass to answerCallbackQuery.
fromUserYes
messageobjectYesThe bot's message whose button was pressed.
message.message_idIdYes
message.chatChatYes
datastringYes
dateTimestampYes

ChatAccess#

FieldTypeRequiredDescription
chatChatYes
userUserYes
statusstringYesstarted: 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_paramstringNoThe 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_-]*$.
dateTimestampYes

DeletedMessage#

FieldTypeRequired
message_idIdYes
chatChatYes
dateTimestampYes

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.

FieldTypeRequiredDescription
file_idFileIdYes
statusFileStatusYes
message_idIdNoThe message the file is attached to, for received attachments.
chatChatNo
reasonstringNoFor 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:

typepayload field
messagemessage
message_editededited_message
message_deleteddeleted_message
callback_querycallback_query
chat_access_changedchat_access
file_status_changedfile_status

Editing an old message is not a new command: treat message_edited as a correction.

FieldTypeRequiredDescription
update_idIdYes
event_idstring (uuid)Yes
typeUpdateTypeYes
replay_of_update_idIdNoSet when the owner replayed a FAILED update.
dateTimestampYes
messageMessageNo
edited_messageMessageNo
deleted_messageDeletedMessageNo
callback_queryCallbackQueryNo
chat_accessChatAccessNo
file_statusFileStatusChangeNo

Lease#

FieldTypeRequired
consumer_idstringYes
epochIdYes
expires_atTimestampYes

SkipRange#

FieldTypeRequiredDescription
from_update_idIdYes
to_update_idIdYesInclusive.
reasonstringYescancelled: 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#

FieldTypeRequiredDescription
updatesarray of UpdateYes
confirmed_offsetIdYesPosition after the confirmed prefix, after applying this request's offset.
next_offsetIdYesPass as offset after durably storing every update of this batch.
skippedarray of SkipRangeYes
leaseLeaseYes
queue_gapbooleanNoTrue when updates expired before delivery since the last confirmation.

BotCommand#

FieldTypeRequiredDescription
commandstringYesWithout the leading slash. Pattern: ^[a-z0-9_]{1,32}$.
descriptionstringYes1-256 characters.

DeliveryError#

FieldTypeRequiredDescription
dateTimestampYes
codestringYesBefore 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.
messagestringNoSanitised; never contains secrets or message text.

DeliveryInfo#

FieldTypeRequiredDescription
modestringYesOne of: polling, webhook.
config_versionIdYes
pending_update_countintegerYes
failed_update_countintegerYes
oldest_pending_dateTimestampNo
last_delivery_dateTimestampNo
last_errorDeliveryErrorNo
confirmed_offsetIdNoPolling mode.
leaseLeaseNoPolling mode, when a consumer holds the lease.
webhookWebhookInfoNo
maintenancebooleanYesTrue during a technical pause of the platform.
retention_secondsintegerNoHow long unconfirmed updates are kept (604800 = 7 days).

WebhookInfo#

FieldTypeRequiredDescription
urlstringYesEmpty string in polling mode.
statestringYespaused: 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_countintegerYes
next_attempt_dateTimestampNo
last_errorDeliveryErrorNo
max_connectionsintegerNoAlways 1.

File#

FieldTypeRequiredDescription
file_idFileIdYes
kindstringYesOne of: photo, document.
file_namestringYes
mime_typestringNo
sizeintegerYes
statusFileStatusYes
reasonstringNoFor 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_afterintegerNoFor scanning; seconds before asking again.
download_pathstringNoPresent 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.

FieldTypeRequiredDescription
okbooleanYesAlways false.
errorErrorYes
request_idstringYes

The error field:

FieldTypeRequiredDescription
codeErrorCodeYes
messagestringYesHuman-readable, English, free of secrets. Do not parse it.
retry_afterintegerNoSeconds to wait before retrying. Present only when a retry makes sense. At least 1.
confirmed_offsetIdNoPresent with CURSOR_BEHIND and OFFSET_NOT_ISSUED.
max_issued_update_idIdNoPresent with OFFSET_NOT_ISSUED.
fieldstringNoPresent with INVALID_REQUEST when one parameter is at fault.
reasonstringNoPresent 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.
limitintegerNoPresent 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#

CodeHTTPMeaning
INVALID_REQUEST400Bad text, buttons or parameters
IDEMPOTENCY_KEY_REQUIRED400The method needs an Idempotency-Key header
OFFSET_NOT_ISSUED400offset jumps over updates that were never returned
INVALID_TOKEN401Unknown or revoked token
BOT_STOPPED_OR_BLOCKED403The user stopped or blocked the bot
BOT_SUSPENDED403Paused by the owner or suspended by the platform
SPACE_BOTS_DISABLED403The organisation turned bot runtime off
CHAT_NOT_FOUND404No such chat for this bot (foreign chats are not listed)
MESSAGE_NOT_FOUND404No such message for this bot
UPDATE_NOT_FOUND404The update is not issued, already confirmed or not this bot's
CALLBACK_QUERY_NOT_FOUND404Unknown or expired callback query
BOT_NOT_FOUND404The bot was deleted while the request was on its way; its token no longer works
FILE_NOT_FOUND404Unknown file, or access to it has ended
METHOD_NOT_FOUND404No such method
DELIVERY_MODE_CONFLICT409getUpdates while the bot is in webhook mode
CONSUMER_CONFLICT409Another poll holds the lease, or the epoch is stale; retry_after says when to ask again
CURSOR_BEHIND409offset is below the confirmed position
IDEMPOTENCY_CONFLICT409The key was used with different parameters
FILE_NOT_READY409The file is still being scanned
PAYLOAD_TOO_LARGE413The upload exceeds the size limit
FILE_REJECTED422The file failed the check or is not allowed; error.reason says why (not_a_photo, type_not_allowed, malware_detected, ...)
RATE_LIMITED429Too many requests; wait retry_after
QUOTA_EXCEEDED429Daily file quota used up; wait retry_after
TEMPORARILY_UNAVAILABLE503Transient failure; retry with the same key
BOTS_MAINTENANCE503Technical pause of the bot platform; retry later with the same key and offset