SMeet Bot API
Справочник API
Справочник после первых разделов собран из машиночитаемого контракта, который можно скачать как openapi.json. Имена методов, поля JSON и коды ошибок одинаковы во всех языках этой документации.
Запросы#
- Базовый адрес:
https://messenger.scrile.com/bot-api/v1. Метод вызывается какPOST https://messenger.scrile.com/bot-api/v1/<method>с телом в JSON в кодировке UTF-8 и заголовкомContent-Type: application/json. - Методы только для чтения (getMe, getMyCommands, getFile, getDeliveryInfo, getWebhookInfo) принимают и
GETс параметрами в строке запроса. - uploadFile принимает
multipart/form-data. Содержимое файла скачивается черезGET /files/<file_id>/content. - Тело запроса может быть не больше 21 МиБ; более крупное отклоняется с
413 PAYLOAD_TOO_LARGEиlimitв байтах ещё до чтения. - Адрес, которому не соответствует ни один метод, отвечает
404 METHOD_NOT_FOUND.
Аутентификация#
Каждый вызов передаёт токен бота в заголовке:
POST /bot-api/v1/getUpdates HTTP/1.1
Host: messenger.scrile.com
Authorization: Bearer sbt1_EXAMPLE_REPLACE_WITH_YOUR_TOKEN
Content-Type: application/jsonОтсутствующий, испорченный, неизвестный или отозванный токен, а также токен удалённого бота получают один и тот же ответ: 401 INVALID_TOKEN с заголовком WWW-Authenticate: Bearer. Запрос, который уже был в пути, когда бота удалили, может получить вместо этого 404 BOT_NOT_FOUND; после этого токен тоже не работает. Токен никогда не принимается в URL, и SMeet никуда его не передаёт, в том числе в ваш webhook.
Ответы и ошибки#
Любой ответ приходит в JSON. Успешный:
{"ok": true, "result": {"message_id": "7301", "chat": {"id": "550", "type": "private"}}}Ошибка приходит с соответствующим HTTP-статусом и в таком виде:
{
"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стабилен, и именно его проверяет программа.error.messageсодержит английский текст для людей; не разбирайте его программно.retry_after(в секундах) есть, только когда повтор имеет смысл, и тогда то же значение приходит в заголовкеRetry-After. SMeet передаёт его с каждым429и503, а также с каждым409 CONSUMER_CONFLICTи409 FILE_NOT_READY.- Некоторые коды добавляют поля в
error:fieldназывает неверный параметр приINVALID_REQUEST,confirmed_offsetиmax_issued_update_idприходят с ошибками offset,reasonсFILE_REJECTED,limit(наибольший допустимый размер в байтах) сPAYLOAD_TOO_LARGE. request_id, который приходит и в заголовкеX-Request-Id, указывает на конкретный запрос. Сообщайте его при обращении в поддержку.- Прокси перед API отвечает на собственные ограничения в том же виде:
429 RATE_LIMITEDсretry_after1 и413 PAYLOAD_TOO_LARGE, оба с пустымrequest_id.413от прокси может прийти безlimit.
Все коды с HTTP-статусами собраны в таблице кодов ошибок.
Идентификаторы#
Все идентификаторы (update_id, chat_id, message_id, epoch, offset) передаются десятичными строками длиной до 20 цифр. Они могут превышать 2^53, поэтому никогда не превращайте их в числа с плавающей точкой; арифметика над ними тоже не нужна, потому что сервер возвращает next_offset с каждым batch. file_id и id нажатия кнопки остаются непрозрачными строками, а event_id является UUID.
Idempotency-Key#
Методы, которые что-то меняют, принимают заголовок Idempotency-Key: от 1 до 128 символов из набора A-Z a-z 0-9 . _ : -.
| Метод | Idempotency-Key |
|---|---|
| sendMessage, editMessageText, sendPhoto, sendDocument, uploadFile, rejectUpdate | Обязателен. Без него: 400 IDEMPOTENCY_KEY_REQUIRED. |
| answerCallbackQuery, setMyCommands | Принимается, но не нужен: оба метода идемпотентны сами по себе (на нажатие отвечают один раз, а список команд заменяет прежний), поэтому ключ не сохраняется. |
- Тот же ключ и те же параметры: возвращается результат первого вызова, и ничего не выполняется дважды. Перед ответом SMeet заново проверяет токен и чат, поэтому повтор после того, как пользователь остановил бота, получает
BOT_STOPPED_OR_BLOCKED. - Тот же ключ и другие параметры:
409 IDEMPOTENCY_CONFLICT. Параметры сравниваются как JSON, так что порядок полей и пробелы не важны. - Ошибка до выполнения действия (
FILE_NOT_READY,429,503, ошибка проверки параметров) ключ не расходует. Повторите тот же запрос с тем же ключом. - Повтор, который пришёл, пока первый вызов ещё выполняется, дожидается его и возвращает его результат.
- Ключи принадлежат одному боту и одному методу: один и тот же ключ в sendMessage и в editMessageText считается двумя разными ключами. Ключи uploadFile уникальны в пределах бота. SMeet хранит ключи 7 дней.
Стройте ключ из события, на которое отвечаете, и действия, например <event_id>:reply: тогда повторно доставленное или повторённое владельцем событие даёт тот же ключ, и SMeet возвращает уже отправленный ответ. Ваши собственные побочные эффекты (запись в базе, платёж) требуют такой же защиты на вашей стороне по event_id (Дедупликация).
Ограничения#
Запросы сверх ограничения получают 429 RATE_LIMITED с retry_after. Короткий всплеск сверх темпа допускается до числа в последнем столбце.
| Что учитывается | Темп | Всплеск |
|---|---|---|
| Сообщения в один чат: sendMessage, editMessageText, sendPhoto, sendDocument | 1 в секунду | 3 |
| Те же методы на бота | 10 в секунду | 20 |
| Те же методы для всех ботов одного владельца вместе | 20 в секунду | 40 |
| getUpdates на бота | 5 в секунду | 10 |
| uploadFile на бота | 2 в секунду | 5 |
| Скачивание файлов на бота | 5 в секунду | 10 |
| Все запросы с одного IP-адреса | 20 в секунду | 40 |
Другие ограничения: текст до 4096 символов и кнопки, как в разделе Сообщения и кнопки; фото до 10 МиБ, документы до 20 МиБ и 200 МиБ загрузок на бота за сутки UTC (Фото и документы); очередь до 10 000 событий или 50 МиБ на бота (Получение событий). Платформа может задать другие ограничения отдельному боту и другой общий темп для всех ботов одного владельца.
Версии и совместимость#
- Основная версия входит в путь:
/bot-api/v1. Изменение, которое может сломать работающую программу, получает новый путь;v1продолжает работать. - Внутри
v1SMeet добавляет новое: поля в объектах, типы событий, значенияreasonиstatus, коды ошибок. Программа должна пропускать незнакомые поля и типы событий, а незнакомые значения считать «прочими». - Каждое изменение контракта записывается в истории изменений API.
- Сам контракт опубликован как openapi.json (OpenAPI 3.1); по нему можно сгенерировать клиент или проверять им свои тесты.
Справочник собран из контракта SMeet Bot API 1.0.0 (OpenAPI 3.1.0). Базовый адрес: https://messenger.scrile.com/bot-api/v1. Машиночитаемый файл: openapi.json.
HTTP API для программ, которые работают как боты SMeet.
Методы#
Каждый метод вызывается как POST https://messenger.scrile.com/bot-api/v1/<method> с телом в JSON; методы только для чтения принимают и GET с параметрами в строке запроса. Аутентификация: Токен бота, который один раз показывается в «Моих ботах» или в SMeet BotFather. Формат sbt1_<key id>_<secret>; считайте его непрозрачной строкой. SMeet хранит только хэш. Отозванный токен сразу на всех серверах получает 401 INVALID_TOKEN.
| Метод | HTTP | Idempotency-Key | Описание |
|---|---|---|---|
| BotПрофиль бота и его команды. | |||
getMe | GET POST | Проверить токен и получить публичный профиль бота. | |
getMyCommands | GET POST | Прочитать меню команд. | |
setMyCommands | POST | можно | Заменить меню команд, которое пользователь видит после ввода «/». |
| UpdatesПолучение событий через long polling. | |||
getUpdates | POST | Получить ожидающие события через long polling. | |
rejectUpdate | POST | обязателен | Перевести одно выданное и неподтверждённое событие в FAILED. |
| MessagesОтправка и редактирование сообщений, ответы на нажатия кнопок. | |||
sendMessage | POST | обязателен | Отправить текстовое сообщение в чат, где пользователь запустил этого бота. |
editMessageText | POST | обязателен | Изменить текст и кнопки сообщения, которое отправил этот бот. |
answerCallbackQuery | POST | можно | Подтвердить, что нажатие кнопки обработано. |
| FilesФото и документы. | |||
uploadFile | POST | обязателен | Загрузить фото или документ, чтобы отправить его позже. |
sendPhoto | POST | обязателен | Отправить готовое фото в чат. |
sendDocument | POST | обязателен | Отправить готовый документ в чат. |
getFile | GET POST | Прочитать сведения о файле и состояние его обработки. | |
downloadFile | GET | Скачать содержимое готового файла. | |
| DeliveryДиагностика доставки, только чтение. | |||
getDeliveryInfo | GET POST | Прочитать режим доставки, размер очередей и последнюю ошибку. | |
getWebhookInfo | GET POST | Прочитать состояние webhook (часть getDeliveryInfo). | |
getMe#
GET POST /getMeBot
Проверить токен и получить публичный профиль бота.
Результат#
В поле result приходит Bot. Бот.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
getMyCommands#
GET POST /getMyCommandsBot
Прочитать меню команд.
Параметры#
| Имя | Тип | Обязательно |
|---|---|---|
language_code | LanguageCode | Нет |
С GET передавайте их в строке запроса, с POST как поля тела в JSON.
Результат#
В поле result приходит массив BotCommand. Команды.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
setMyCommands#
POST /setMyCommandsBot
Заменить меню команд, которое пользователь видит после ввода «/».
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Нет | Принимается, но не нужен: метод идемпотентен сам по себе (повтор того же вызова даёт тот же результат), поэтому ключ не сохраняется. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
commands | массив BotCommand | Да | До 100 элементов. |
language_code | LanguageCode | Нет | Не передавайте для списка по умолчанию, который видят все языки. |
Результат#
В поле result приходит true. Сохранено.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
getUpdates#
POST /getUpdatesUpdates
Получить ожидающие события через long polling.
Возвращает ожидающие события начиная с подтверждённой позиции. Переданный offset подтверждает все события с update_id < offset; подтверждённые события больше не возвращаются. Неподтверждённые события возвращаются снова при следующем вызове (после потерянного ответа или перезапуска), поэтому программа должна спокойно переносить повторы.
Запрос ждёт до timeout секунд и завершается сразу, как только появляется событие, а не в конце тайм-аута. Пустой ответ по тайм-ауту ничего не меняет.
У бота один активный получатель. Первый вызов называет consumer_id и получает аренду с epoch; следующие вызовы передают те же consumer_id и epoch и продлевают аренду (TTL 60 секунд). Второй одновременный вызов, даже с тем же consumer_id, получает CONSUMER_CONFLICT. Вызов с чужой epoch (аренду с тех пор взял другой получатель, владелец её сбросил или сменил режим) получает новую аренду, но ничего не подтверждает: его offset не учитывается, и неподтверждённые события приходят снова. Если истекла ваша собственная аренда и за это время её никто не взял, следующий вызов получает новую аренду, и его offset по-прежнему подтверждает события.
Правила offset (confirmed_offset означает позицию после подтверждённого префикса, max_issued_update_id означает наибольший когда-либо выданный update_id):
offset = confirmed_offsetпринимается всегда (подтверждение без изменений).confirmed_offset < offset <= max_issued_update_id + 1подтверждает префикс.offset < confirmed_offsetполучает409 CURSOR_BEHIND; в ошибке есть текущийconfirmed_offset. Ничего не отправляется повторно.offset > max_issued_update_id + 1получает400 OFFSET_NOT_ISSUED, если только он не равенnext_offset, который сервер выдал для текущей аренды.- Без
offsetвыдача продолжается с позиции, сохранённой на сервере.
Пример: после выдачи события 42 offset=43 подтверждает его; offset=44 отклоняется; когда 43 уже подтверждён, offset=42 получает CURSOR_BEHIND.
События, которые были отменены, истекли или ушли в FAILED до подтверждения, перечисляются в skipped, и next_offset перепрыгивает через них. Подтверждайте всегда полученным next_offset, после того как весь batch надёжно сохранён.
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
offset | Id | Нет | |
limit | integer | Нет | От 1 до 100. По умолчанию: 100. |
timeout | integer | Нет | Сколько секунд ждать, если ничего нет. 0 возвращает ответ сразу. От 0 до 25. По умолчанию: 25. |
consumer_id | string | Да | Устойчивое имя этого процесса-получателя, например имя хоста. От 1 до 64 символов. Шаблон: ^[A-Za-z0-9._-]+$. |
epoch | Id | Нет | Epoch аренды, полученной раньше. Храните её вместе с offset и передавайте и после перезапуска; не передавайте, только если её нет. Без неё аренда, которую этот получатель ещё держит, отвечает 409 CONSUMER_CONFLICT до своего истечения (retry_after говорит, сколько ждать); затем повторите запрос. |
Результат#
В поле result приходит GetUpdatesResult. Пачка событий (возможно, пустая).
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
rejectUpdate#
POST /rejectUpdateUpdates
Перевести одно выданное и неподтверждённое событие в FAILED.
Нужен, когда программа не может обработать одно конкретное событие и не хочет, чтобы оно задерживало остальной batch. Событие выходит из активной последовательности, хранит данные до конца исходного срока хранения и появляется в списке FAILED у владельца, где его можно повторить или пропустить. Отклонить можно только событие, которое уже выдано текущей аренде и ещё не подтверждено.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Да | Уникален для каждой логической операции (подойдёт UUID). При повторе после тайм-аута, 429 или 503 передавайте тот же ключ. Привязывайте его к event_id события, на которое отвечаете, и к действию, чтобы повторно доставленное событие не дало второго ответа. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
update_id | Id | Да | |
reason | string | Да | Видна владельцу. Не должна содержать секретов и персональных данных. От 1 до 256 символов. |
consumer_id | string | Да | |
epoch | Id | Да |
Результат#
Событие в статусе FAILED. В поле result приходит объект с такими полями.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
update_id | Id | Да | |
status | string | Да | Всегда failed. |
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
sendMessage#
POST /sendMessageMessages
Отправить текстовое сообщение в чат, где пользователь запустил этого бота.
Отправитель всегда бот, которому принадлежит токен; параметра sender_id нет. Чат должен быть личным: пользователь нажал в нём «Начать» и не остановил и не заблокировал бота.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Да | Уникален для каждой логической операции (подойдёт UUID). При повторе после тайм-аута, 429 или 503 передавайте тот же ключ. Привязывайте его к event_id события, на которое отвечаете, и к действию, чтобы повторно доставленное событие не дало второго ответа. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
chat_id | Id | Да | |
text | string | Да | Обычный текст. Строки, которые начинаются с "__META__:" или "__SYSTEM__", отклоняются. От 1 до 4096 символов. |
reply_to_message_id | Id | Нет | Сообщение из этого же чата. |
reply_markup | InlineKeyboardMarkup | Нет |
Результат#
В поле result приходит Message. Отправленное сообщение.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
editMessageText#
POST /editMessageTextMessages
Изменить текст и кнопки сообщения, которое отправил этот бот.
Изменять можно только сообщения этого бота. Если reply_markup не передан, после правки у сообщения не будет кнопок.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Да | Уникален для каждой логической операции (подойдёт UUID). При повторе после тайм-аута, 429 или 503 передавайте тот же ключ. Привязывайте его к event_id события, на которое отвечаете, и к действию, чтобы повторно доставленное событие не дало второго ответа. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
chat_id | Id | Да | |
message_id | Id | Да | |
text | string | Да | От 1 до 4096 символов. |
reply_markup | InlineKeyboardMarkup | Нет |
Результат#
В поле result приходит Message. Изменённое сообщение.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
answerCallbackQuery#
POST /answerCallbackQueryMessages
Подтвердить, что нажатие кнопки обработано.
Убирает у пользователя индикатор ожидания и при желании показывает короткое уведомление. Если на нажатие не ответить за 15 секунд, пользователь увидит «Бот не ответил»; бот всё равно может ответить позже и изменить своё сообщение. На нажатие можно ответить один раз и в течение 1 часа; повтор такого же ответа возвращает тот же результат, а другой второй ответ получает 409 IDEMPOTENCY_CONFLICT.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Нет | Принимается, но не нужен: метод идемпотентен сам по себе (повтор того же вызова даёт тот же результат), поэтому ключ не сохраняется. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
callback_query_id | string | Да | |
text | string | Нет | Короткое уведомление для пользователя. До 200 символов. |
show_alert | boolean | Нет | Показать уведомление диалогом, а не всплывающей подсказкой. По умолчанию: false. |
Результат#
В поле result приходит true. Ответ принят.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
uploadFile#
POST /uploadFileFiles
Загрузить фото или документ, чтобы отправить его позже.
Файл сохраняется в карантин и проверяется. Возвращённый file_id имеет статус scanning; отправить файл можно только после того, как getFile сообщит ready. Ограничения: фото до 10 МиБ (JPEG, PNG, WebP, HEIC), документы до 20 МиБ и суточная квота объёма на бота. Один и тот же Idempotency-Key никогда не создаёт второй файл.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Да | Уникален для каждой логической операции (подойдёт UUID). При повторе после тайм-аута, 429 или 503 передавайте тот же ключ. Привязывайте его к event_id события, на которое отвечаете, и к действию, чтобы повторно доставленное событие не дало второго ответа. |
Тело запроса (multipart/form-data)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
file | файл | Да | |
kind | string | Нет | Одно из значений: photo, document. По умолчанию: document. |
file_name | string | Нет | До 255 символов. |
Результат#
В поле result приходит File. Сохранённый файл (обычно в статусе scanning).
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
sendPhoto#
POST /sendPhotoFiles
Отправить готовое фото в чат.
Файл должен иметь вид photo (JPEG, PNG, WebP или HEIC по содержимому). Любой другой готовый файл получает 400 INVALID_REQUEST с field: file_id; отправьте его через sendDocument.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Да | Уникален для каждой логической операции (подойдёт UUID). При повторе после тайм-аута, 429 или 503 передавайте тот же ключ. Привязывайте его к event_id события, на которое отвечаете, и к действию, чтобы повторно доставленное событие не дало второго ответа. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
chat_id | Id | Да | |
file_id | FileId | Да | Файл, который загрузил этот бот, или вложение сообщения, которое этот бот получил в том же пространстве. Должен быть в статусе ready: иначе FILE_NOT_READY (повторите позже с тем же Idempotency-Key) или FILE_REJECTED. |
caption | string | Нет | До 1024 символов. |
reply_to_message_id | Id | Нет | |
reply_markup | InlineKeyboardMarkup | Нет |
Результат#
В поле result приходит Message. Отправленное сообщение.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
sendDocument#
POST /sendDocumentFiles
Отправить готовый документ в чат.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
Idempotency-Key | IdempotencyKey | Да | Уникален для каждой логической операции (подойдёт UUID). При повторе после тайм-аута, 429 или 503 передавайте тот же ключ. Привязывайте его к event_id события, на которое отвечаете, и к действию, чтобы повторно доставленное событие не дало второго ответа. |
Тело запроса (JSON)#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
chat_id | Id | Да | |
file_id | FileId | Да | Файл, который загрузил этот бот, или вложение сообщения, которое этот бот получил в том же пространстве. Должен быть в статусе ready: иначе FILE_NOT_READY (повторите позже с тем же Idempotency-Key) или FILE_REJECTED. |
caption | string | Нет | До 1024 символов. |
reply_to_message_id | Id | Нет | |
reply_markup | InlineKeyboardMarkup | Нет |
Результат#
В поле result приходит Message. Отправленное сообщение.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
getFile#
GET POST /getFileFiles
Прочитать сведения о файле и состояние его обработки.
Работает для файлов, которые загрузил бот, и для вложений полученных им сообщений. Поле download_path есть, только пока файл в статусе ready и доступ к нему сохраняется. Для scanning в ответе есть retry_after.
Параметры#
| Имя | Тип | Обязательно |
|---|---|---|
file_id | FileId | Да |
С GET передавайте их в строке запроса, с POST как поля тела в JSON.
Результат#
В поле result приходит File. Файл.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
downloadFile#
GET /files/{file_id}/contentFiles
Скачать содержимое готового файла.
При каждом вызове нужны токен бота и действующие права. Публичного или постоянного адреса в хранилище нет. Доступ заканчивается, когда исходное сообщение удалено, пользователь остановил или заблокировал бота либо вышел из пространства.
Параметры#
| Имя | Тип | Обязательно |
|---|---|---|
file_id | FileId | Да |
Параметры пути входят в сам адрес.
Результат#
Ответом служит сам файл, а не JSON. Содержимое файла. Заголовки ответа: Content-Disposition.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
getDeliveryInfo#
GET POST /getDeliveryInfoDelivery
Прочитать режим доставки, размер очередей и последнюю ошибку.
Секрет webhook никогда не возвращается.
Результат#
В поле result приходит DeliveryInfo. Состояние доставки.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
getWebhookInfo#
GET POST /getWebhookInfoDelivery
Прочитать состояние webhook (часть getDeliveryInfo).
Результат#
В поле result приходит WebhookInfo. Состояние webhook.
При ошибке приходит объект ошибки с кодом из таблицы кодов ошибок.
Запрос webhook#
POST ваш HTTPS-адрес
SMeet доставляет одно событие на HTTPS-адрес владельца.
Отправляется только в режиме доставки webhook. На бота один запрос в полёте; сетевой тайм-аут 10 секунд; перенаправления не выполняются.
Проверьте X-SMeet-Signature, прежде чем доверять телу: t содержит Unix-время, а v1 содержит HMAC-SHA256 от "<t>.<сырое тело запроса>" в шестнадцатеричном виде строчными буквами, с ключом, равным секрету webhook, который один раз показывается в «Моих ботах». Отклоняйте timestamp, который отличается от ваших часов больше чем на 5 минут. API-токен в webhook не передаётся.
Надёжно сохраните событие (уникальность по event_id) и быстро ответьте любым 2xx; долгую бизнес-логику выполняйте после ответа. 2xx подтверждает приём, а не успех бизнес-операции. Потерянные ответы приводят к повторам с тем же update_id и event_id.
Обработка ответа: 2xx подтверждает. 400, 413 или 422 переводит это событие в FAILED, очередь идёт дальше. 401, 403, 404, 410, сертификат, который не проходит проверку, адрес, куда webhook не отправляются, перенаправление и любой другой неподдерживаемый ответ приостанавливают endpoint, пока владелец не исправит настройку и не возобновит доставку в «Моих ботах» (состояние paused, причина в last_error); в это время ничего не повторяется, попытки событий не расходуются, очередь сохраняется. 429 замедляет доставку с учётом Retry-After. Ошибки DNS и соединения, а также тайм-ауты соединения и TLS-рукопожатия до отправки запроса оставляют событие в ожидании и увеличивают паузу (5 секунд с удвоением до 5 минут). 5xx, 408 и сбои после начала отправки расходуют общий бюджет: 5 попыток или 10 минут на событие, после чего оно уходит в FAILED; три таких события подряд открывают circuit breaker, который перед продолжением делает пробную доставку текущего события.
Заголовки#
| Заголовок | Тип | Обязательно | Описание |
|---|---|---|---|
X-SMeet-Signature | string | Да | Пример: t=1790416800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd |
X-SMeet-Event-Id | string (uuid) | Да | |
X-SMeet-Update-Id | Id | Да | |
X-SMeet-Delivery-Attempt | integer | Да | Не меньше 1 |
Тело#
Объект Update в JSON.
Ваш ответ#
Принято. Любой 2xx обрабатывается одинаково; тело ответа не читается.
Типы событий#
Поле type у Update называет ровно одно поле с данными, которое присутствует в событии. Незнакомые значения нужно пропускать.
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 |
Типы#
Простые типы#
Строки с заданным форматом. Поля ссылаются на них по имени.
| Имя | Тип | Описание |
|---|---|---|
Id | string | Десятичный идентификатор, передаётся строкой. Не секрет и не разрешение. Шаблон: ^[0-9]{1,20}$. Пример: 1842. |
FileId | string | Непрозрачный идентификатор файла, действует только для этого бота. Шаблон: ^[A-Za-z0-9_-]{16,64}$. Пример: f_Qm9vdGZpbGVfMDAwMDAx. |
IdempotencyKey | string | От 1 до 128 символов. Шаблон: ^[A-Za-z0-9._:-]+$. |
LanguageCode | string | Шаблон: ^[a-z]{2}$. Пример: en. |
Timestamp | string (date-time) | RFC 3339, UTC. Пример: 2026-09-26T10:00:00Z. |
UpdateType | string | Незнакомые значения нужно пропускать. Одно из значений: message, message_edited, message_deleted, callback_query, chat_access_changed, file_status_changed. |
FileStatus | string | Одно из значений: scanning, ready, rejected, scan_failed. |
User#
Только то, что нужно для чата. E-mail, телефон, другие чаты, другие аккаунты и сессии не передаются никогда.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
id | Id | Да | |
display_name | string | Да | |
username | string | Нет | Публичный адрес без «@», если он есть у пользователя. |
is_bot | boolean | Да |
Chat#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
id | Id | Да | |
type | string | Да | В версии 1 только личные чаты. Незнакомые значения нужно пропускать. Одно из значений: private. |
Space#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
id | Id | Да | |
kind | string | Да | Одно из значений: public, organization. |
name | string | Нет |
Bot#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
id | Id | Да | Идентификатор аккаунта бота; то же значение, что from.id в сообщениях, которые отправляет бот. |
username | string | Да | |
display_name | string | Да | |
description | string | Нет | |
is_bot | boolean | Да | Всегда true. |
space | Space | Да | |
status | string | Да | Одно из значений: active, paused_by_owner, suspended_by_admin. |
delivery_mode | string | Да | Одно из значений: polling, webhook. |
can_send_files | boolean | Нет | false, пока работа с файлами выключена в этой установке SMeet. |
InlineKeyboardButton#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
text | string | Да | От 1 до 64 символов. |
callback_data | string | Нет | При нажатии возвращается в callback_query.data. Нужно ровно одно из полей callback_data или url. От 1 до 64 символов. |
url | string (uri) | Нет | Ссылка https://, которую открывает клиент. Нужно ровно одно из полей callback_data или url. До 2048 символов. |
InlineKeyboardMarkup#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
inline_keyboard | массив массив InlineKeyboardButton | Да | Ряды кнопок; не больше 8 кнопок в ряду и 40 всего. От 1 до 10 элементов. |
Attachment#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
file_id | FileId | Да | |
kind | string | Да | Одно из значений: photo, document. |
file_name | string | Да | Очищенное имя, его безопасно показывать и использовать при сохранении. |
mime_type | string | Нет | Как его указало приложение отправителя. Тип, определённый по содержимому, есть в getFile после проверки: разметка, программы и другие опасные типы там отклоняются (type_not_allowed), а при остальных расхождениях файл хранится под определённым типом («фото», которое не картинка, становится документом). |
size | integer | Да | Как его указало приложение отправителя; в getFile есть размер сохранённой копии. Не меньше 0. |
processing_status | FileStatus | Да | |
reason | string | Нет | Только если вложение уже в статусе rejected или scan_failed, например too_large для вложения больше размера, который бот может получить. Значения те же, что у File.reason. |
Message#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
message_id | Id | Да | |
chat | Chat | Да | |
from | User | Да | |
date | Timestamp | Да | |
edit_date | Timestamp | Нет | |
text | string | Нет | До 4096 символов. |
reply_to_message_id | Id | Нет | |
attachments | массив Attachment | Нет | |
reply_markup | InlineKeyboardMarkup | Нет | |
has_unsupported_content | boolean | Нет | true, если в сообщении есть содержимое, которое эта версия API не передаёт (голосовое, геопозиция, опрос и подобное). Тогда text содержит читаемую замену, если она есть. |
CallbackQuery#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
id | string | Да | Передайте в answerCallbackQuery. |
from | User | Да | |
message | объект | Да | Сообщение бота, на кнопку которого нажали. |
message.message_id | Id | Да | |
message.chat | Chat | Да | |
data | string | Да | |
date | Timestamp | Да |
ChatAccess#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
chat | Chat | Да | |
user | User | Да | |
status | string | Да | started: пользователь нажал «Начать» (снова). stopped: пользователь остановил бота. blocked: пользователь заблокировал бота. removed: пользователь вышел из пространства бота, и платформа отозвала доступ бота. После любого статуса, кроме started, отправка в этот чат получает BOT_STOPPED_OR_BLOCKED. Одно из значений: started, stopped, blocked, removed. |
start_param | string | Нет | Параметр ссылки ?start=, только для started. Сам по себе он никогда не команда, но Start также добавляет в чат видимое сообщение пользователя /start или /start <param>, которое приходит отдельным событием message сразу после этого. Бот, который приветствует при Start, должен реагировать на одно из двух, а не на оба. До 64 символов. Шаблон: ^[A-Za-z0-9_-]*$. |
date | Timestamp | Да |
DeletedMessage#
FileStatusChange#
Отправляется, когда проверка закончена: для вложений, полученных ботом (с message_id и chat), и для собственных загрузок бота (без них). Никогда не отправляется в чат, где пользователь остановил бота или из которого вышел. scan_failed не всегда окончателен: файл, который сканер не смог проверить (scanner_unavailable, scanner_outdated, scan_timeout), проверяется снова примерно через час, всего до трёх раундов, и после этого приходит ещё один file_status_changed; в это время getFile снова показывает scanning.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
file_id | FileId | Да | |
status | FileStatus | Да | |
message_id | Id | Нет | Сообщение, к которому прикреплён файл, для полученных вложений. |
chat | Chat | Нет | |
reason | string | Нет | Для rejected и scan_failed. Значения: malware_detected, type_not_allowed (разметка, программы и другие запрещённые типы), too_large (файл больше лимита отклоняется; файл, который сканер отказался проверять из-за размера, получает scan_failed), empty (нет содержимого), scanner_unavailable, scanner_outdated (сигнатуры старше 3 дней), scan_timeout, source_missing (вложение исчезло до проверки). Незнакомые значения нужно принимать. |
Update#
Присутствует ровно одно поле с данными, его выбирает type:
| type | поле с данными |
|---|---|
| message | message |
| message_edited | edited_message |
| message_deleted | deleted_message |
| callback_query | callback_query |
| chat_access_changed | chat_access |
| file_status_changed | file_status |
Правка старого сообщения не новая команда: считайте message_edited исправлением.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
update_id | Id | Да | |
event_id | string (uuid) | Да | |
type | UpdateType | Да | |
replay_of_update_id | Id | Нет | Есть, если владелец повторил событие из FAILED. |
date | Timestamp | Да | |
message | Message | Нет | |
edited_message | Message | Нет | |
deleted_message | DeletedMessage | Нет | |
callback_query | CallbackQuery | Нет | |
chat_access | ChatAccess | Нет | |
file_status | FileStatusChange | Нет |
Lease#
SkipRange#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
from_update_id | Id | Да | |
to_update_id | Id | Да | Включительно. |
reason | string | Да | cancelled: доступ к чату или источник события пропал до доставки. failed: событие ушло в FAILED (rejectUpdate или бюджет webhook). expired: истёк срок хранения. skipped_by_owner: владелец пропустил событие из FAILED. delivered: событие уже подтверждено другим путём, например через webhook до того, как владелец вернул бота на long polling. Незнакомые значения нужно принимать. Одно из значений: cancelled, failed, expired, skipped_by_owner, delivered. |
GetUpdatesResult#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
updates | массив Update | Да | |
confirmed_offset | Id | Да | Позиция после подтверждённого префикса, уже с учётом offset этого запроса. |
next_offset | Id | Да | Передайте как offset после того, как каждое событие этого batch надёжно сохранено. |
skipped | массив SkipRange | Да | |
lease | Lease | Да | |
queue_gap | boolean | Нет | true, если после последнего подтверждения часть событий истекла до доставки. |
BotCommand#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
command | string | Да | Без ведущей косой черты. Шаблон: ^[a-z0-9_]{1,32}$. |
description | string | Да | От 1 до 256 символов. |
DeliveryError#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
date | Timestamp | Да | |
code | string | Да | До отправки запроса: DNS_ERROR, CONNECT_ERROR, CONNECT_TIMEOUT, TLS_ERROR. После отправки: TIMEOUT, NO_RESPONSE, IO_ERROR, HTTP_408, HTTP_5xx (сам статус, например HTTP_503). Пауза до возобновления владельцем: HTTP_401, HTTP_403, HTTP_404, HTTP_410, TLS_CERTIFICATE_ERROR, ADDRESS_FORBIDDEN, REDIRECT_NOT_FOLLOWED и HTTP_<статус> любого другого неподдерживаемого ответа. Кроме того, HTTP_400, HTTP_413, HTTP_422 (событие ушло в FAILED), HTTP_429 (доставка замедлена) и DISABLED_BY_PLATFORM. Незнакомые значения нужно принимать. |
message | string | Нет | Очищено; никогда не содержит секретов или текста сообщений. |
DeliveryInfo#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
mode | string | Да | Одно из значений: polling, webhook. |
config_version | Id | Да | |
pending_update_count | integer | Да | |
failed_update_count | integer | Да | |
oldest_pending_date | Timestamp | Нет | |
last_delivery_date | Timestamp | Нет | |
last_error | DeliveryError | Нет | |
confirmed_offset | Id | Нет | Режим polling. |
lease | Lease | Нет | Режим polling, когда аренду держит получатель. |
webhook | WebhookInfo | Нет | |
maintenance | boolean | Да | true во время технической паузы платформы. |
retention_seconds | integer | Нет | Сколько хранятся неподтверждённые события (604800 = 7 дней). |
WebhookInfo#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
url | string | Да | Пустая строка в режиме polling. |
state | string | Да | paused: ждёт владельца после ответа, который может исправить только он (см. last_error); владелец возобновляет доставку в «Моих ботах». circuit_open: сбои повторяются, SMeet сам время от времени пробует доставить событие. disabled_by_platform: администратор выключил webhook. Одно из значений: none, active, paused, circuit_open, disabled_by_platform. |
pending_update_count | integer | Да | |
next_attempt_date | Timestamp | Нет | |
last_error | DeliveryError | Нет | |
max_connections | integer | Нет | Всегда 1. |
File#
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
file_id | FileId | Да | |
kind | string | Да | Одно из значений: photo, document. |
file_name | string | Да | |
mime_type | string | Нет | |
size | integer | Да | |
status | FileStatus | Да | |
reason | string | Нет | Для rejected и scan_failed. Значения: malware_detected, type_not_allowed (разметка, программы и другие запрещённые типы), too_large (файл больше лимита отклоняется; файл, который сканер отказался проверять из-за размера, получает scan_failed), empty (нет содержимого), scanner_unavailable, scanner_outdated (сигнатуры старше 3 дней), scan_timeout, source_missing (вложение исчезло до проверки). Незнакомые значения нужно принимать. |
retry_after | integer | Нет | Для scanning; через сколько секунд спросить снова. |
download_path | string | Нет | Есть только у готовых файлов; путь относительно базового адреса API, нужен токен. |
Ошибки#
Любая ошибка. Значение каждого кода и HTTP-статус, с которым он приходит, указаны в ErrorCode. Заголовок Retry-After ставится всегда, когда в теле есть retry_after (429, 503, а также 409 CONSUMER_CONFLICT и FILE_NOT_READY).
Заголовки ответа с ошибкой: Retry-After, X-Request-Id.
Объект ошибки#
Любой неудачный вызов отвечает этой обёрткой и соответствующим HTTP-статусом.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
ok | boolean | Да | Всегда false. |
error | Error | Да | |
request_id | string | Да |
Поле error:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
code | ErrorCode | Да | |
message | string | Да | Понятный человеку текст на английском, без секретов. Не разбирайте его программно. |
retry_after | integer | Нет | Сколько секунд подождать перед повтором. Есть, только когда повтор имеет смысл. Не меньше 1. |
confirmed_offset | Id | Нет | Есть при CURSOR_BEHIND и OFFSET_NOT_ISSUED. |
max_issued_update_id | Id | Нет | Есть при OFFSET_NOT_ISSUED. |
field | string | Нет | Есть при INVALID_REQUEST, если ошибка в одном параметре. |
reason | string | Нет | Есть при FILE_REJECTED: почему файл нельзя использовать (not_a_photo, type_not_allowed, malware_detected, too_large, empty, scanner_unavailable, ...). Незнакомые значения нужно принимать. |
limit | integer | Нет | Есть при PAYLOAD_TOO_LARGE: наибольший допустимый размер в байтах. Ответ 413, который отдал прокси перед API до того, как запрос дошёл до SMeet, может его не содержать. |
Коды ошибок#
| Код | HTTP | Значение |
|---|---|---|
INVALID_REQUEST | 400 | Неверный текст, кнопки или параметры |
IDEMPOTENCY_KEY_REQUIRED | 400 | Методу нужен заголовок Idempotency-Key |
OFFSET_NOT_ISSUED | 400 | offset перепрыгивает через события, которые не выдавались |
INVALID_TOKEN | 401 | Неизвестный или отозванный токен |
BOT_STOPPED_OR_BLOCKED | 403 | Пользователь остановил или заблокировал бота |
BOT_SUSPENDED | 403 | Бот приостановлен владельцем или платформой |
SPACE_BOTS_DISABLED | 403 | Организация выключила работу ботов |
CHAT_NOT_FOUND | 404 | У этого бота нет такого чата (чужие чаты не раскрываются) |
MESSAGE_NOT_FOUND | 404 | У этого бота нет такого сообщения |
UPDATE_NOT_FOUND | 404 | Событие не выдавалось, уже подтверждено или принадлежит другому боту |
CALLBACK_QUERY_NOT_FOUND | 404 | Неизвестное или истёкшее нажатие кнопки |
BOT_NOT_FOUND | 404 | Бот удалён, пока запрос был в пути; его токен больше не действует |
FILE_NOT_FOUND | 404 | Неизвестный файл, или доступ к нему закончился |
METHOD_NOT_FOUND | 404 | Такого метода нет |
DELIVERY_MODE_CONFLICT | 409 | getUpdates, когда бот в режиме webhook |
CONSUMER_CONFLICT | 409 | Аренду держит другой запрос, или epoch устарела; retry_after говорит, когда спросить снова |
CURSOR_BEHIND | 409 | offset меньше подтверждённой позиции |
IDEMPOTENCY_CONFLICT | 409 | Ключ уже использован с другими параметрами |
FILE_NOT_READY | 409 | Файл ещё проверяется |
PAYLOAD_TOO_LARGE | 413 | Загрузка больше допустимого размера |
FILE_REJECTED | 422 | Файл не прошёл проверку или запрещён; error.reason объясняет причину (not_a_photo, type_not_allowed, malware_detected, ...) |
RATE_LIMITED | 429 | Слишком много запросов; подождите retry_after |
QUOTA_EXCEEDED | 429 | Суточная квота файлов исчерпана; подождите retry_after |
TEMPORARILY_UNAVAILABLE | 503 | Временный сбой; повторите с тем же ключом |
BOTS_MAINTENANCE | 503 | Техническая пауза платформы ботов; повторите позже с тем же ключом и offset |