SMeet Bot API
Сообщения и кнопки
Бот общается обычным текстом с кнопками под сообщениями. Здесь описано, что приходит боту, что он может отправить и какие правила не дают старым или скопированным кнопкам сработать не там.
Получение сообщений#
Сообщение человека приходит событием типа message:
{
"update_id": "43",
"event_id": "c2a1f5d0-0b7e-4a52-9f11-5a8c3e2d7b64",
"type": "message",
"date": "2026-09-26T10:00:04Z",
"message": {
"message_id": "7302",
"chat": {"id": "550", "type": "private"},
"from": {"id": "812", "display_name": "Anna", "username": "anna", "is_bot": false},
"date": "2026-09-26T10:00:04Z",
"text": "Where is my order A-1001?",
"reply_to_message_id": "7300"
}
}chat.idпередаётся какchat_id, когда вы отвечаете. Каждый чат личный: один человек и ваш бот.fromсодержит отображаемое имя человека и, если он есть, публичный адрес. E-mail, телефон, другие чаты и сессии не передаются никогда.textсодержит читаемый текст.reply_to_message_idесть, если человек ответил на сообщение.attachmentsперечисляет фото и документы (Фото и документы).has_unsupported_content: trueозначает, что в сообщении есть то, что версия 1 не передаёт: голосовое или видеосообщение, геопозиция, опрос или чек-лист, а также файлы, если работа с файлами выключена. Тогдаtextсодержит ту читаемую часть, которая есть.
Бот никогда не получает свои собственные сообщения, сообщения других ботов и системные уведомления, поэтому два бота не могут бесконечно отвечать друг другу. Сообщение, которое человек переслал боту, приходит обычным текстом, без сведений об исходном чате и авторе.
Команды#
Командой считается сообщение, текст которого начинается с /, например /help или /track A-1001. Боту она приходит обычным message; что она означает, решает ваша программа. Возможна и форма /help@support_helper_bot, поэтому берите часть до @.
Меню команд, которое люди видят после ввода «/», задаётся в BotFather или через setMyCommands (Управление ботом). Это только подсказка: человек может ввести любую команду.
/stop обрабатывает сам SMeet: чат останавливается (Организации и доступ), сообщение остаётся в истории, а бот получает только chat_access_changed со stopped, но не само сообщение.
Start и ссылки с параметром#
Ссылка на бота может нести параметр: https://messenger.scrile.com/u/support_helper_bot?start=A-1001. Параметр содержит от 0 до 64 символов из A-Z a-z 0-9 _ -. Когда человек открывает ссылку и нажимает Начать, бот получает два события:
chat_access_changedс"status": "started"и"start_param": "A-1001";messageчеловека с текстом/start A-1001(просто/start, если параметра нет).
Реагируйте на одно из двух, а не на оба. Большинство ботов используют сообщение, чтобы ссылка и набранная вручную команда /start A-1001 работали одинаково. Нажатие «Начать» в уже запущенном чате даёт только сообщение. Человек, который остановил бота, может и просто написать в чат /start, чтобы запустить его снова.
Отправка сообщения#
curl -s https://messenger.scrile.com/bot-api/v1/sendMessage \
-H "Authorization: Bearer $SMEET_BOT_TOKEN" \
-H "Idempotency-Key: c2a1f5d0-0b7e-4a52-9f11-5a8c3e2d7b64:reply" \
-H "Content-Type: application/json" \
-d '{"chat_id": "550", "text": "Your order A-1001 ships tomorrow.", "reply_to_message_id": "7302"}'{
"ok": true,
"result": {
"message_id": "7303",
"chat": {"id": "550", "type": "private"},
"from": {"id": "5001", "display_name": "Support helper", "username": "support_helper_bot", "is_bot": true},
"date": "2026-09-26T10:00:05Z",
"text": "Your order A-1001 ships tomorrow.",
"reply_to_message_id": "7302"
}
}- Отправитель всегда бот, которому принадлежит токен; параметра, чтобы писать от чужого имени, нет.
- Текст передаётся обычным текстом, от 1 до 4096 символов. Он не может начинаться с
__META__:или__SYSTEM__, а управляющие символы, кроме перевода строки и табуляции, отклоняются. Idempotency-Keyобязателен. Стройте его из события, на которое отвечаете, как в примере, и повторно доставленное событие никогда не даст второго сообщения (Idempotency-Key).
| Ошибка | Когда |
|---|---|
404 CHAT_NOT_FOUND | Чат не принадлежит этому боту. Чаты других ботов выглядят так же, как несуществующие. |
403 BOT_STOPPED_OR_BLOCKED | Человек остановил или заблокировал бота либо вышел из его пространства. Больше не пишите в этот чат. |
404 MESSAGE_NOT_FOUND | reply_to_message_id указывает не на сообщение этого чата. |
400 INVALID_REQUEST | Неверный текст или кнопки; error.field называет параметр. |
429 RATE_LIMITED | Слишком быстро; подождите retry_after и повторите с тем же ключом. |
Ответы на сообщения#
Передайте reply_to_message_id с сообщением из того же чата, и человек увидит ваше сообщение как ответ с цитатой исходного. Цитату строит сам SMeet из первых 200 символов исходного текста. Цитата учитывается в 4096 символах, поэтому очень длинный ответ может получить INVALID_REQUEST с "field": "text".
Кнопки#
Кнопки размещаются под сообщением в reply_markup.inline_keyboard рядами:
{
"chat_id": "550",
"text": "Выберите вопрос:",
"reply_markup": {
"inline_keyboard": [
[{"text": "Часы работы", "callback_data": "faq:hours"}, {"text": "Доставка", "callback_data": "faq:delivery"}],
[{"text": "О ботах SMeet", "url": "https://messenger.scrile.com/docs/bots/ru/"}]
]
}
}У каждой кнопки есть text и ровно одно из полей:
callback_data, от 1 до 64 байт: нажатие приходит боту какcallback_queryс этими данными. Приложение человека эти данные не видит никогда; SMeet хранит их на сервере и даёт каждой кнопке собственный идентификатор.url, ссылкаhttps://без имени пользователя и пароля, до 2048 символов: приложение открывает её, а бот об этом не узнаёт.
До 10 рядов, до 8 кнопок в ряду и до 40 кнопок всего; текст кнопки от 1 до 64 символов и не из одних пробелов. Кнопки работают в веб-приложении и в SMeet для iOS и macOS.
В приложениях без поддержки ботов#
Приложения без поддержки ботов (SMeet для Android до выпуска с ботами, а также старые версии веб-приложения и SMeet для iOS) не дают нажать Начать. В уже запущенном чате они показывают сообщения бота обычным текстом без кнопок, а человек по-прежнему может отвечать текстом и набирать команды. Поэтому дублируйте каждое важное действие кнопки командой из меню, например /status рядом с кнопкой Статус, и упоминайте её в тексте: тогда чат остаётся пригодным и в этих приложениях.
Нажатия кнопок#
Нажатие callback-кнопки приходит как callback_query:
{
"update_id": "45",
"event_id": "5b9e0c3a-2d41-4f7a-8e6b-1c2d3e4f5a6b",
"type": "callback_query",
"date": "2026-09-26T10:00:09Z",
"callback_query": {
"id": "cb_EXAMPLE",
"from": {"id": "812", "display_name": "Anna", "is_bot": false},
"message": {"message_id": "7304", "chat": {"id": "550", "type": "private"}},
"data": "faq:hours",
"date": "2026-09-26T10:00:09Z"
}
}Ответьте на него через answerCallbackQuery, затем выполните работу, например измените сообщение:
- Приложение человека показывает индикатор ожидания. Если ответа нет 15 секунд, оно показывает Бот не ответил, попробуйте позже; более поздний ответ всё равно применится, пока чат открыт.
- На нажатие можно ответить один раз и в течение 1 часа. Повтор того же ответа безвреден; другой второй ответ получает
409 IDEMPOTENCY_CONFLICT; после часа приходит404 CALLBACK_QUERY_NOT_FOUND. textдо 200 символов показывается человеку коротким уведомлением;"show_alert": trueпоказывает его диалогом.
Так нажатие обрабатывает python/faq_bot.py:
def handle(client: smeet.Client, update: dict) -> None:
event_id = update["event_id"]
if update["type"] == "message":
chat_id = update["message"]["chat"]["id"]
client.send_message(chat_id, MENU_TEXT, reply_markup=menu_markup(),
idempotency_key=smeet.action_key(event_id, "menu"))
log.info("Menu sent to chat %s", chat_id)
return
if update["type"] != "callback_query":
return # access changes, edits and unknown types need no answer here
query = update["callback_query"]
chat_id = query["message"]["chat"]["id"]
message_id = query["message"]["message_id"]
data = query["data"]
if data == "menu":
text, markup = MENU_TEXT, menu_markup()
elif data.startswith("faq:") and data[len("faq:"):] in FAQ:
title, answer = FAQ[data[len("faq:"):]]
text, markup = f"{title}\n\n{answer}", BACK_MARKUP
else:
# A button from an older version of this bot: say so instead of leaving the user waiting.
client.answer_callback_query(query["id"], text="This button is out of date. Send /start.",
idempotency_key=smeet.action_key(event_id, "answer"))
return
# Answer the press first: it stops the waiting indicator in the app. Then edit the message.
client.answer_callback_query(query["id"], idempotency_key=smeet.action_key(event_id, "answer"))
client.edit_message_text(chat_id, message_id, text, reply_markup=markup,
idempotency_key=smeet.action_key(event_id, "edit"))
log.info("Button %s handled in chat %s", data, chat_id)Редактирование сообщений#
editMessageText меняет текст и кнопки сообщения, которое этот бот отправил в этот чат; для любого другого сообщения приходит 404 MESSAGE_NOT_FOUND.
textобязателен и заменяет текст. Ответ сохраняет свою цитату.reply_markupзаменяет кнопки. Без него после правки у сообщения не будет кнопок.- Новые кнопки заменяют старые целиком. Нажатие на старые кнопки, например на втором устройстве, которое ещё не обновилось, отклоняется, и человек видит, что кнопка больше не действует.
- Приложения людей сразу обновляют сообщение. В результате есть
edit_date. О своей собственной правке бот события не получает. - Чат должен оставаться запущенным: после остановки бот не может и редактировать свои сообщения.
Изменённые и удалённые сообщения#
- Когда человек изменяет сообщение, бот получает
message_editedс полным новым текстом вedited_messageиedit_date. Это исправление, а не новая команда: не выполняйте команду повторно из-за того, что её сообщение изменили. - Когда человек удаляет сообщение, которое бот ещё не получил, его событие отменяется и не доставляется. Если бот его уже получил, приходит событие
message_deletedсmessage_id; текст не повторяется. - Файлы удалённого сообщения сразу становятся недоступны боту.
Старые, пересланные и отложенные сообщения#
- Ничего из того, что человек отправил до нажатия «Начать», до бота не доходит. После остановки и нового запуска события, отменённые остановкой, не возвращаются.
- Нажатие работает только на текущих кнопках сообщения, которое ещё существует, в запущенном чате. Старые версии кнопок, удалённые сообщения и чаты после остановки нажатия отклоняют.
- У пересланной копии сообщения бота нет кнопок: пересылка не может перенести действие в другой чат.
- Сообщение, которое человек запланировал на потом, становится событием только в момент фактической отправки. Если его удалят раньше, бот о нём не узнает.
Уведомления только с согласия#
Нажатие «Начать» разрешает разговор, а не поток сообщений. Бот, который присылает уведомления (статус заказа, напоминание), должен явно спросить согласие, например кнопкой Уведомлять меня, и сохранять подписку только после этого нажатия. Весь сценарий показан в python/status_bot.py.
Получив chat_access_changed со stopped, blocked или removed, бот должен удалить подписки этого чата. Если уведомление разминулось с остановкой, о которой бот ещё не узнал, SMeet отвечает 403 BOT_STOPPED_OR_BLOCKED, и бот должен поступить так же:
def set_chat_active(db: sqlite3.Connection, chat_id: str, active: bool) -> None:
"""Mark a chat as reachable or not. A chat that is not reachable loses all its subscriptions."""
with db: # one transaction
db.execute("INSERT OR REPLACE INTO chats (chat_id, active, updated_at) VALUES (?, ?, ?)",
(chat_id, 1 if active else 0, time.time()))
if not active:
db.execute("DELETE FROM subscriptions WHERE chat_id = ?", (chat_id,))Ключ Idempotency-Key уведомления стройте из того, о чём оно, тогда повторный запуск того же уведомления ничего нового не отправит:
def notification_key(application_id: str, status: str, chat_id: str, change_id: Optional[str]) -> str:
raw = "\n".join((application_id, status, change_id or "", chat_id))
return "notify-" + hashlib.sha256(raw.encode("utf-8")).hexdigest()[:40]Ограничения#
| Что | Ограничение |
|---|---|
| Текст сообщения | От 1 до 4096 символов; не начинается с __META__: или __SYSTEM__; без управляющих символов, кроме перевода строки и табуляции |
| Подпись к фото или документу | До 1024 символов, те же правила |
| Цитата в ответе | До 200 символов исходного текста, входит в 4096 |
| Кнопки | До 10 рядов, до 8 в ряду, до 40 всего |
| Текст кнопки | От 1 до 64 символов, не из одних пробелов |
callback_data | От 1 до 64 байт в UTF-8 |
url кнопки | Только https://, до 2048 символов, без имени пользователя и пароля |
| Ответ на нажатие | Один раз, в течение 1 часа; уведомление до 200 символов |
| Меню команд | До 100 команд; команда от 1 до 32 символов из a-z 0-9 _ |
| Темп отправки | 1 сообщение в секунду в один чат (всплеск 3), 10 в секунду на бота (всплеск 20), 20 в секунду на всех ботов одного владельца (всплеск 40) |