# HTTP Bot API: поддерживаемые методы Документ перечисляет все методы Bot API, которые HTTP-шлюз telesrv (`internal/botapi`, порт по умолчанию `8081`) действительно реализует, вместе с принимаемыми параметрами, добавленными расширениями и официальными методами, которые сознательно не поддержаны. Английская версия этого документа: [bot-api.en.md](./bot-api.en.md). ## Как пользоваться этим документом - [Транспорт](#транспорт) - [Аутентификация](#аутентификация) - [Формат запросов и ответов](#формат-запросов-и-ответов) - [Ошибки](#ошибки) - [Сводка методов](#сводка-методов) - [Обновления](#обновления) - [Методы: бот и команды](#методы-бот-и-команды) - [Методы: отправка](#методы-отправка) - [Методы: rich-сообщения](#методы-rich-сообщения) - [Методы: ephemeral-сообщения](#методы-ephemeral-сообщения) - [Методы: редактирование и удаление](#методы-редактирование-и-удаление) - [Методы: файлы](#методы-файлы) - [Методы: webhooks](#методы-webhooks) - [Методы: меню, emoji status и web apps](#методы-меню-emoji-status-и-web-apps) - [Методы: платежи](#методы-платежи) - [Разметка и entities](#разметка-и-entities) - [Лимиты](#лимиты) - [Не реализовано](#не-реализовано) - [Демо и self-тесты](#демо-и-self-тесты) ## Транспорт | Параметр | Значение | |---|---| | Метод | `http(s):///bot/` | | Скачивание файлов | `http(s):///file/bot/` | | Настройка слушателя | `TELESRV_BOT_API_ADDR`; пустое значение отключает шлюз | | Публичный адрес, который показывает BotFather | `TELESRV_BOT_API_PUBLIC_URL`; пустое значение — не показывать | | Лимит тела запроса | 25 MiB файла + 1 MiB служебных данных (`FILE_TOO_BIG`) | | Content-Type | `application/json`, `multipart/form-data`, `application/x-www-form-urlencoded` | `` сопоставляется без учёта регистра. Диспетчер доступен и по `GET`, и по `POST`, поэтому `urlencoded`-запросы методом GET работают. Настройка клиентов: - `python-telegram-bot`: `--base-url http://host:8081/bot` и `--base-file-url http://host:8081/file/bot`; - `aiogram`: `TelegramAPIServer.from_base("http://host:8081")`; - grammY: `new Api("host:8081")` либо `api.config.use((ctx) => ({ apiRoot: "http://host:8081/bot" }))`. ## Аутентификация Токен в пути должен быть парой `:` бота, а секрет — совпадать со значением, сохранённым для этого бота. Всё остальное даёт HTTP `401` с `ACCESS_TOKEN_INVALID`. Отдельного auth-заголовка нет: токен в URL и есть credential, ровно как на официальном шлюзе. Скачивание файлов (`/file/bot...`) аутентифицируется так же. ## Формат запросов и ответов Успех: ```json {"ok": true, "result": {}} ``` Ошибка: ```json {"ok": false, "error_code": 400, "description": "CHAT_ID_INVALID"} ``` `error_code` — это HTTP-статус. `description` — маркер ошибки в верхнем регистре (`SCREAMING_SNAKE`), не обязательно в официальных формулировках Telegram — сопоставляйте по маркеру, а не по всей строке. Разбор полей универсальный: JSON-тело «расплющивается» в строки, нестроковые значения перемаршируются в свой JSON, а в `multipart` берётся первое значение каждого поля. Поэтому часть методов принимает и сокращение `message_text`, и полный объект `InputTextMessageContent`, а `setChatMenuButton` — и вложенный объект `menu_button`, и «плоское» тело с `type` на верхнем уровне. ## Ошибки Маркеры, которые шлюз может вернуть напрямую: | Маркер | HTTP | Комментарий | |---|---:|---| | `ACCESS_TOKEN_INVALID` | 401 | неверный или неизвестный токен | | `METHOD_NOT_FOUND` | 404 / 501 | неизвестный метод либо фича не собрана в этой сборке | | `BAD_REQUEST` | 400 | неопознанная ошибка | | `CHAT_ID_INVALID` | 400 | `chat_id` отсутствует, равен нулю или не разбирается | | `MESSAGE_ID_INVALID` | 400 | `message_id` отсутствует или `<= 0` | | `MESSAGE_IDENTIFIER_INVALID` | 400 | `inline_message_id` смешан с `chat_id`/`message_id` | | `MESSAGE_EMPTY` | 400 | не передан `text` | | `MESSAGE_TOO_LONG` | 400 | превышен лимит текста | | `MESSAGE_NOT_MODIFIED` | 400 | правка ничего не меняет | | `REPLY_PARAMETERS_INVALID` | 400 | конфликтующие селекторы ответа | | `REPLY_MESSAGE_ID_INVALID` | 400 | отрицательный или некорректный адресат ответа | | `MESSAGE_THREAD_ID_INVALID` | 400 | вне диапазона id верхнего сообщения ephemeral-треда | | `ENTITY_INVALID`, `ENTITY_BOUNDS_INVALID`, `ENTITY_TYPE_UNSUPPORTED`, `ENTITIES_TOO_LONG` | 400 | проблемы со списком entity | | `FILE_ID_INVALID`, `FILE_TOO_BIG`, `MEDIA_INVALID` | 400 | проблемы с файлом/медиа | | `BUTTON_INVALID`, `BUTTON_TYPE_INVALID`, `BUTTON_DATA_INVALID`, `BUTTON_URL_INVALID` | 400 | проблемы с разметкой | | `RICH_MESSAGE_INVALID`, `RICH_MESSAGE_TOO_LONG`, `RICH_MESSAGE_DATE_INVALID`, `RICH_MESSAGE_BLOCKS_UNSUPPORTED`, `RICH_MESSAGE_MEDIA_UNSUPPORTED`, `RICH_MESSAGE_OPTION_UNSUPPORTED` | 400 | проблемы rich-сообщения | | `WEBPAGE_MEDIA_EMPTY` | 400 | rich HTML сослался на удалённое медиа, которое локальный blob-backend не может материализовать | | `EFFECT_ID_INVALID`, `VALUE_INVALID` | 400 | некорректный числовой параметр | | `BOT_COMMAND_INVALID` | 400 | некорректный или слишком большой `commands` | | `BOT_COMMAND_SCOPE_UNSUPPORTED` | 400 | не-default scope или задан `language_code` | | `EPHEMERAL_MESSAGE_ID_INVALID`, `EPHEMERAL_TARGET_REQUIRED`, `EPHEMERAL_ACTION_EXPIRED` | 400 | проблемы адресации ephemeral-сообщений | | `QUERY_ID_INVALID`, `USER_ID_INVALID`, `RESULT_ID_INVALID`, `RESULT_ID_EMPTY`, `RESULT_TYPE_INVALID` | 400 | проблемы query/user/result | | `OFFSET_INVALID` | 400 | offset в `getUpdates` меньше `-10000` | | `ALLOWED_UPDATES_INVALID` | 400 | не JSON-массив из не более 100 строк | | `ALLOWED_UPDATES_UNSUPPORTED` | 501 | шлюз без сервиса управления обновлениями | | `MAX_CONNECTIONS_INVALID` | 400 | вне `1..100` | | `SECRET_TOKEN_INVALID` | 400 | символы вне `[A-Za-z0-9_-]` или длиннее 256 | | `WEBHOOK_URL_INVALID` | 400 | не абсолютный `http(s)`-URL, длиннее 2048, с userinfo/fragment или с портом вне `1..65535` | | `CERTIFICATE_PINNING_UNSUPPORTED` | 400 | передан `certificate` или загруженный сертификат | | `IP_ADDRESS_UNSUPPORTED` | 400 | передан `ip_address` | | `WEBHOOK_UNSUPPORTED` | 501 | шлюз без сервиса webhooks | | `METHOD_NOT_FOUND` | 501 | `answerShippingQuery` | | `BLOCKED_USER_EMOJI_STATUS_SERVICE_MISSING`, `BLOCKED_WEBAPP_QUERY_SERVICE_MISSING`, `BLOCKED_PREPARED_INLINE_SERVICE_MISSING` | 501 | опциональный сервис не собран | | `USER_PERMISSION_DENIED` | 403 | emoji status без нужного разрешения пользователя | | `PREMIUM_ACCOUNT_REQUIRED` | 400 | emoji status без Premium | | `PREMIUM_GIFT_SELF_INVALID`, `PREMIUM_GIFT_CODE_INVALID`, `BALANCE_TOO_LOW`, `STAR_COUNT_INVALID`, `MONTH_COUNT_INVALID` | 400 | проблемы подарка Premium | | `IDEMPOTENCY_KEY_INVALID` | 400 | конфликт `request_id`/`Idempotency-Key` либо плохой charset/длина | | `PAYMENT_FORM_INVALID` | 400 | платёжная форма отклонена | | `CHAT_WRITE_FORBIDDEN`, `CHAT_ADMIN_REQUIRED` | 400 | не хватает прав | | `USER_BOT_REQUIRED` | 400 | адресат не является ботом | | `CONFLICT: ...` | 409 | параллельный опрос или активная доставка webhook | | `INTERNAL_SERVER_ERROR` | 500 / 503 | сбой хранилища или транспорта | ## Сводка методов Реализовано (40 методов + 1 файловый маршрут): | Группа | Методы | |---|---| | Бот и команды | `getMe`, `setMyCommands`, `deleteMyCommands`, `getMyCommands` | | Обновления | `getUpdates` | | Текст | `sendMessage`, `sendRichMessage` | | Медиа | `sendPhoto`, `sendAnimation`, `sendAudio`, `sendDocument`, `sendLivePhoto`, `sendSticker`, `sendVideo`, `sendVideoNote`, `sendVoice` | | Гео | `sendContact`, `sendLocation`, `sendVenue` | | Правка/удаление | `editMessageText`, `editMessageCaption`, `editMessageReplyMarkup`, `deleteMessage` | | Действия в чате | `sendChatAction` | | Правка/удаление ephemeral | `editEphemeralMessageText`, `editEphemeralMessageMedia`, `editEphemeralMessageCaption`, `editEphemeralMessageReplyMarkup`, `deleteEphemeralMessage` | | Callback | `answerCallbackQuery` | | Файлы | `getFile`, `GET /file/bot/` | | Webhooks | `setWebhook`, `deleteWebhook`, `getWebhookInfo` | | Меню и статус | `setChatMenuButton`, `getChatMenuButton`, `setUserEmojiStatus` | | Web apps и inline | `answerWebAppQuery`, `savePreparedInlineMessage` | | Платежи | `giftPremiumSubscription` | | Заблокировано по design | `answerShippingQuery` (HTTP 501) | ## Обновления Шлюз порождает только три вида обновлений: | Значение `allowed_updates` | Ключ обновления | |---|---| | `message` | `message` | | `edited_message` | `edited_message` | | `callback_query` | `callback_query` | `allowed_updates` — JSON-массив из не более 100 строк; неизвестные имена разбираются без ошибки, но не совпадают ни с чем, поэтому не рассчитывайте на фильтрацию как на способ спрятать обновления — отбрасывайте лишние поля в боте. Настройка принимается и `getUpdates`, и `setWebhook` и хранится отдельно для каждого бота. Ephemeral-сообщения (Bot API 10.2) приходят как обычные `message`-обновления, но их `message_id` принадлежит ephemeral-пространству, а не нумерации сообщений чата, поэтому обычный `reply_to_message_id` их не адресует. Ответ идёт через `reply_parameters.ephemeral_message_id`; см. [Методы: ephemeral-сообщения](#методы-ephemeral-сообщения). Время жизни очереди задаёт `TELESRV_BOT_API_UPDATE_RETENTION` (по умолчанию `24h`); подтверждённые обновления удаляются раньше. ## Методы: бот и команды ### `getMe` Без параметров. Возвращает собственный объект `User` бота. ### `setMyCommands` | Параметр | Тип | Комментарий | |---|---|---| | `commands` | JSON-массив | до 100 записей вида `{command, description, is_ephemeral}` | | `scope` | объект | принимается только `{"type":"default"}` | | `language_code` | строка | должно быть пустым | `is_ephemeral=true` помечает команду как ephemeral: её вызов в группе создаёт сообщение, видимое только вызвавшему, и бот должен ответить в пределах ephemeral-окна. Возвращает `true`. ### `deleteMyCommands` Те же правила по `scope`, что и у `setMyCommands`; очищает список. Возвращает `true`. ### `getMyCommands` Те же правила по `scope`. Возвращает сохранённый массив; поле `is_ephemeral: true` присутствует только у помеченных команд. ## Методы: отправка ### `sendMessage` | Параметр | Тип | Комментарий | |---|---|---| | `chat_id` | целое | обязателен, не ноль; отрицательный для групп/каналов | | `text` | строка | обязателен, до 4096 символов после разбора разметки | | `parse_mode` | строка | `HTML`, `Markdown`, `MarkdownV2` или пусто для обычного текста | | `entities` | JSON-массив | используется при отсутствии `parse_mode`, иначе игнорируется | | `reply_markup` | объект | любой из четырёх конструкторов, см. [Разметка](#разметка-и-entities) | | `disable_web_page_preview` | булево | | | `disable_notification` | булево | | | `reply_to_message_id` | целое | | | `receiver_user_id` | целое | **расширение telesrv**: переключает вызов в ephemeral-отправку | | `callback_query_id` | целое | ephemeral-отправка, привязанная к callback query | | `reply_parameters` | объект | `{ephemeral_message_id}` для ephemeral-ответа, `{message_id}` для обычного | | `message_thread_id` | целое | ephemeral: id верхнего сообщения в треде | Обычная и ephemeral-адресация взаимоисключающи: если задан `receiver_user_id`, поле `reply_to_message_id` должно остаться пустым, а `reply_parameters` может нести только `ephemeral_message_id`. Для ephemeral-отправки допустимы лишь inline-клавиатуры (иначе `BUTTON_TYPE_INVALID`). Результат — та же структура `Message`, где `message_id` содержит ephemeral-id. ### `sendPhoto`, `sendAnimation`, `sendAudio`, `sendDocument`, `sendSticker`, `sendVideo`, `sendVideoNote`, `sendVoice` У них один общий обработчик. | Параметр | Тип | Комментарий | |---|---|---| | `chat_id` | целое | обязателен | | `` | строка / файл | одноимённое поле метода: `photo`, `animation`, `audio`, `document`, `sticker`, `video`, `video_note`, `voice` | | `caption` | строка | до 1024 символов | | `parse_mode`, `caption_entities` | | как в `sendMessage` | | `reply_markup` | объект | все четыре конструктора | | `disable_notification`, `reply_to_message_id` | | | | `width`, `height`, `duration`, `title`, `performer` | целое / строка | принимаются и передаются как подсказки метаданных | | `emoji` | строка | только для стикеров | | `receiver_user_id`, `callback_query_id`, `reply_parameters`, `message_thread_id` | | то же ephemeral-расширение, что и в `sendMessage` | Файловое поле принимает, по убыванию приоритета: 1. `attach://`, ссылающееся на multipart-часть; 2. загруженную multipart-часть с тем же именем поля; 3. URL `http://` или `https://` (сервер забирает его сам); 4. `file_id`, ранее выданный `getFile`. Всё остальное — `FILE_ID_INVALID`. ### `sendLivePhoto` Два медиа-поля: `photo` (кадр) и `live_photo` (видеочасть). Оба принимают загрузку, `attach://` или `file_id`. Видеочасть **не может** быть HTTP-URL — это `FILE_ID_INVALID`, как и в официальном ограничении. Подписи, `parse_mode` и ephemeral-расширение работают так же, как в остальных медиа-методах. ### `sendContact`, `sendLocation`, `sendVenue` Эти три метода **только ephemeral**: они требуют `receiver_user_id` (или `callback_query_id`), иначе `EPHEMERAL_TARGET_REQUIRED`. `sendContact` принимает `chat_id`, `phone_number`, `first_name`, необязательные `last_name` и `vcard` (до 2048 байт), плюс необязательную inline-разметку. `sendLocation` принимает `chat_id`, `latitude` (`-90..90`), `longitude` (`-180..180`), `horizontal_accuracy` (`0..1500`, по умолчанию `0`). Ненулевой `live_period` отклоняется с `MEDIA_INVALID` — live-локация не реализована. `sendVenue` добавляет обязательные `title` и `address` и принимает либо `foursquare_id` + `foursquare_type`, либо `google_place_id` + `google_place_type`. ## Методы: rich-сообщения ### `sendRichMessage` Расширение telesrv, rich-текст Layer 228. Отправляет `InputRichMessage` вместо обычной отформатированной строки, поэтому таблицы, заголовки, разделители, блоки `details` и `footer` доходят до клиента настоящими rich-блоками. | Параметр | Тип | Комментарий | |---|---|---| | `chat_id` | целое | обязателен | | `rich_message` | объект | `{"html": "..."}` **или** `{"markdown": "..."}`, плюс необязательные `is_rtl` и `skip_entity_detection` | | `reply_markup` | объект | только inline-клавиатуры | | `reply_parameters` | объект | `{"message_id": N}`; устаревшее `reply_to_message_id` тоже принимается, но не оба сразу | | `disable_notification`, `protect_content` | булево | | | `message_effect_id` | целое | id эффекта сообщения | Правила и отказы: - ровно одно из `html` / `markdown` должно быть непустым; ноль или оба — `RICH_MESSAGE_INVALID`; - `blocks` разбирается, но отклоняется — `RICH_MESSAGE_BLOCKS_UNSUPPORTED`; - `media` разбирается, но отклоняется — `RICH_MESSAGE_MEDIA_UNSUPPORTED`; - исходник ограничен 256 KiB; - `business_connection_id`, `message_thread_id`, `direct_messages_topic_id`, `allow_paid_broadcast` и `suggested_post_parameters` отклоняются явной ошибкой, а не игнорируются молча; - rich HTML со ссылкой на удалённое медиа (`img`, `video`, `audio`, `tg-map`, `tg-collage`, `tg-slideshow`) падает с `WEBPAGE_MEDIA_EMPTY`, потому что локальный blob-backend не может атомарно материализовать произвольный URL. Описанный в `cmd/bots/bedolagaformat` обходной путь — повторить запрос один раз без логотипа, не деградируя до classic-меню. Поддерживаемые HTML-элементы: `b`/`strong`, `i`/`em`, `u`/`ins`, `s`/`strike`/`del`, `tg-spoiler`, `span` (только как ``), `a`, `code`, `pre` (с `class="language-…"`), `blockquote`, `tg-emoji`, `tg-time`, `footer` и `table` (с атрибутами `bordered` и `striped`, выравниванием по ячейкам). Для `tg-time` обязателен `unix` в диапазоне `1..2^31-1` и `format`, собранный из `t`, `T`, `d`, `D`, `w`/`W` либо ровно `r`/`R` для относительной отметки времени. Markdown разбирает тот же rich-builder, что и на стороне MTProto, поэтому работают заголовки `#`, разделители `---`, pipe-таблицы, цитаты `>`, огороженный код и footer-блоки. `editMessageText` принимает тот же объект `rich_message`; отдельного метода `editMessageRichMessage` нет. ## Методы: ephemeral-сообщения Ephemeral-сообщения (Bot API 10.2) видны только получателю, который видит пометку «видно только вам». telesrv поддерживает весь жизненный цикл. Отправка идёт через обычные методы с `receiver_user_id`: - `sendMessage` — текст; - медиа-методы — медиа плюс подпись; - `sendContact`, `sendLocation`, `sendVenue` — только ephemeral. Правила адресации: - `receiver_user_id` обязателен и должен быть положительным целым; - вместо него якорем может быть `callback_query_id` (получатель берётся из query); - `reply_parameters.ephemeral_message_id` отвечает на существующее ephemeral-сообщение и не комбинируется с `callback_query_id`; - `reply_parameters.message_id` — обычная форма ответа, с получателем не комбинируется; - `message_thread_id` — верхнее сообщение ephemeral-треда; - окно действия — 15 секунд, после него сервер отвечает `EPHEMERAL_ACTION_EXPIRED`. Правка и удаление: | Метод | Параметры | |---|---| | `editEphemeralMessageText` | `chat_id`, `receiver_user_id`, `ephemeral_message_id`, `text`, `parse_mode`, `entities`, `reply_markup` | | `editEphemeralMessageCaption` | то же, с `caption` + `caption_entities` | | `editEphemeralMessageMedia` | то же, плюс `media` (см. ниже) | | `editEphemeralMessageReplyMarkup` | то же, только `reply_markup` | | `deleteEphemeralMessage` | `chat_id`, `receiver_user_id`, `ephemeral_message_id` | `media` — объект в духе `InputMedia`: `type` (одно из `photo`, `animation`, `audio`, `document`, `video`, `live_photo`), файловое поле и необязательные `caption`, `parse_mode`, `caption_entities`, `width`, `height`, `duration`, `title`, `performer`. Для `live_photo` кадр берётся из `photo`, видео — из `media`. Сырая загрузка здесь не допускается — только `file_id`, ссылка на файл без `attach://` или HTTPS-URL (иначе `FILE_ID_INVALID`). `reply_markup` принимается только inline, и важно его *наличие*: переданный пустой `reply_markup` снимает клавиатуру. ## Методы: редактирование и удаление ### `editMessageText` Основной метод правки. Покрывает и обычный текст, и rich-текст, и сообщения чата, и inline-сообщения. | Параметр | Тип | Комментарий | |---|---|---| | `chat_id` + `message_id` | | один способ адресации | | `inline_message_id` | строка | другой способ; не смешивать с первым | | `text` | строка | одна форма содержимого | | `rich_message` | объект | другая форма содержимого; не смешивать с `text` | | `parse_mode`, `entities` | | как в `sendMessage` | | `reply_markup` | объект | только inline; наличие с пустым значением снимает | | `disable_web_page_preview` | булево | | Для `inline_message_id` результат — булево значение, для сообщения чата — обновлённый `Message`. Правка, ничего не меняющая, даёт `MESSAGE_NOT_MODIFIED`. ### `editMessageCaption` | Параметр | Тип | Комментарий | |---|---|---| | `chat_id` + `message_id` | | обязательно; `inline_message_id` пока не поддерживается (`INLINE_MESSAGE_ID_UNSUPPORTED`) | | `caption` | строка | до 1024 символов; отсутствие или пустая строка убирает подпись | | `parse_mode`, `caption_entities` | | как в `sendPhoto` | | `reply_markup` | объект | только inline; отсутствие сохраняет клавиатуру, пустое значение снимает | Подпись есть только у сообщения с фото или документом (фото, видео, анимация, аудио, голосовое, файл); для текстового сообщения возвращается `MESSAGE_HAS_NO_CAPTION`. Возвращает обновлённый `Message`. Работает в личных чатах и в каналах/супергруппах. ### `editMessageReplyMarkup` `chat_id` + `message_id` (без `inline_message_id`) и необязательный inline `reply_markup`. Меняется только клавиатура; отсутствие `reply_markup` её убирает. Возвращает обновлённый `Message`. Методов `editMessageMedia`, `editMessageLiveLocation` и `stopMessageLiveLocation` нет — используйте `editMessageText` (или ephemeral-варианты). ### `sendChatAction` | Параметр | Тип | Комментарий | |---|---|---| | `chat_id` | целое | личный чат или канал/супергруппа, где бот состоит | | `action` | строка | `typing`, `upload_photo`, `record_video`, `upload_video`, `record_voice`, `upload_voice`, `upload_document`, `choose_sticker`, `find_location`, `record_video_note`, `upload_video_note` | | `message_thread_id` | целое | тема форума, необязательно | Возвращает `true`. Статус временный, как MTProto `messages.setTyping`: ничего не сохраняется. `business_connection_id` не поддерживается. ### `deleteMessage` `chat_id` + `message_id`. Возвращает `true`. Удаление уже удалённого или чужого сообщения возвращает `false` либо ошибку, как и на официальном шлюзе. ### `answerCallbackQuery` | Параметр | Тип | Комментарий | |---|---|---| | `callback_query_id` | строка | обязателен | | `text` | строка | текст уведомления | | `url` | строка | | | `show_alert` | булево | модальное окно вместо тоста | | `cache_time` | целое | секунды | Возвращает `true`. ## Методы: файлы ### `getFile` `file_id` должен быть выдан telesrv. В результате всегда `file_unique_id == file_id` и `file_path == file_id`, поэтому URL скачивания — `/file/bot/`. Чужие или неизвестные id — `FILE_ID_INVALID`. ### `GET /file/bot/` Отдаёт файл потоком, чанк за чанком, с сохранённым MIME-типом и `Content-Length`. Путь — это `file_id`; путь с лишними сегментами даёт `404 FILE_NOT_FOUND`. ## Методы: webhooks ### `setWebhook` | Параметр | Тип | Комментарий | |---|---|---| | `url` | строка | пустое значение, наоборот, удаляет webhook | | `secret_token` | строка | `[A-Za-z0-9_-]`, до 256 символов | | `max_connections` | целое | `1..100`, по умолчанию `40` | | `allowed_updates` | JSON-массив | см. [Обновления](#обновления) | | `drop_pending_updates` | булево | | `certificate` (и любая загруженная часть сертификата) отклоняется с `CERTIFICATE_PINNING_UNSUPPORTED` — нужен публично доверенный сертификат. `ip_address` отклоняется с `IP_ADDRESS_UNSUPPORTED`: шлюз использует сконфигурированный список адресов. Установка webhook во время long-polling `getUpdates` возвращает `409 CONFLICT`. Во время доставки `setWebhook` ждёт её завершения до 20 секунд и возвращает `409 CONFLICT`, только если доставка всё ещё идёт. Поведение доставки: - один HTTP `POST` на обновление, тело — **объект `Update`**, а не массив, как у `getUpdates`; - заголовок `X-Telegram-Bot-Api-Secret-Token` несёт секретный токен; - `Content-Type: application/json`; успехом считается любой `2xx`; - редиректы не следуются, поэтому секрет не утечёт на другой хост; - параллельно доставляется до `max_connections` обновлений, суммарно 16 ботов и 64 одновременных HTTP-запроса; - при сбое повтор экспоненциально от 1s до 5 минут плюс небольшой бот-зависимый джиттер; - доставка подтверждается до первого неуспешного обновления, так что ничего не пропускается. ### `getWebhookInfo` Возвращает `url`, `has_custom_certificate` (здесь всегда `false`), `pending_update_count` и — если webhook настроен — `max_connections`, `allowed_updates`, `last_error_date`, `last_error_message`. ### `deleteWebhook` `drop_pending_updates` необязателен. Как и `setWebhook`, ждёт завершения текущей доставки до 20 секунд и возвращает `409`, только если она всё ещё идёт. ## Методы: меню, emoji status и web apps ### `setChatMenuButton` / `getChatMenuButton` `menu_button` — это `{"type": "default"}`, `{"type": "commands"}` или `{"type": "web_app", "text": "...", "web_app": {"url": "..."}}`. Также принимается «плоское» тело с `type` на верхнем уровне — это держит работоспособность старых сериализаторов библиотек. Установка возвращает `true`, чтение — текущую кнопку (никогда не `null`: незаданная кнопка читается как `{"type": "default"}`). ### `setUserEmojiStatus` `user_id`, `emoji_status_custom_emoji_id`, `emoji_status_expiration_date`. Требуется, чтобы пользователь разрешил боту менять свой статус (иначе `403 USER_PERMISSION_DENIED`) и чтобы он был Premium (`PREMIUM_ACCOUNT_REQUIRED`). Возвращает `true`. ### `answerWebAppQuery` `web_app_query_id` плюс `result`. Поддерживается только `InlineQueryResult` типа `article`: `id` (до 64 символов), `title`, `description`, `url` (только HTTPS), `input_message_content` (или сокращение `message_text`), `reply_markup` (только inline). Другие типы результатов — `RESULT_TYPE_INVALID`. В ответе есть `inline_message_id`, если ответ его породил. ### `savePreparedInlineMessage` `user_id`, `result` (то же ограничение на `article`) и фильтры пиров `allow_user_chats`, `allow_bot_chats`, `allow_group_chats`, `allow_channel_chats`. Возвращает `{id, expiration_date}`. ## Методы: платежи ### `giftPremiumSubscription` Оплачивает подарок Premium из Stars-баланса бота через тот же надёжный Stars-пайплайн, что и MTProto `payments.sendStarsForm`. | Параметр | Тип | Комментарий | |---|---|---| | `user_id` | целое | получатель; подарок себе отклоняется | | `month_count` | целое | `1..120` | | `star_count` | целое | положительное, не более 10^15 | | `text` | строка | до 128 символов | | `text_parse_mode`, `text_entities` | | ограниченный набор entity | | `request_id` | строка | расширение telesrv для идемпотентности | Передавайте HTTP-заголовок `Idempotency-Key` (или локальное поле `request_id`) — допустимый charset `[A-Za-z0-9]` плюс `-`, `_`, `.`, `:`, до 128 символов. Повтор того же ключа с тем же запросом возвращает сохранённый результат; повтор с другим получателем, тарифом, ценой или текстом — `IDEMPOTENCY_KEY_INVALID`. Запросы без ключа тоже работают, но каждый ретрай списывает средства снова, поэтому ключ всегда стоит слать. ### `dropPendingUpdates` Работает: отбрасывает неподтверждённые апдейты в очереди бота и отвечает `true`. Нужен боту, который отстал, — иначе он годами продирается через бэклог, а `pre_checkout_query` живёт всего 10 секунд и протухает незамеченным. ### `answerPreCheckoutQuery` Работает. Перед списанием Stars плательщик ждёт ответа бота **не более 10 секунд** (требование Telegram). Бот получает апдейт `pre_checkout_query` и отвечает `pre_checkout_query_id` плюс `ok` и, при `ok=false`, `error_message`. - `ok=true` — Stars списываются, боту приходит `successful_payment`; - `ok=false` — оплата отменяется, `error_message` показывается плательщику; - нет ответа за 10 секунд — оплата считается несостоявшейся (`PAYMENT_FAILED`). Таймаут не переходит в «списать напрямую»: не ответивший бот означает отказ, иначе pre-checkout ничего бы не проверял. Ответ принимается только от бота, которому принадлежит заказ; на устаревший `pre_checkout_query_id` бот получает `QUERY_ID_INVALID`. Сумма и `invoice_payload` в запросе не сверяются с тем, что бот записывал при `sendInvoice`, — проверка остаётся на стороне бота, как и в Telegram. ### `answerShippingQuery` Метод распознаётся, но отвечает HTTP `501 METHOD_NOT_FOUND`: оплата Stars не имеет шага доставки, отвечать не на что. ## Разметка и entities `reply_markup` принимает ровно один конструктор: | Конструктор | Комментарий | |---|---| | `inline_keyboard` | ряды кнопок | | `keyboard` | reply-клавиатура; `resize_keyboard`, `one_time_keyboard`, `is_persistent`, `input_field_placeholder`, `selective` | | `remove_keyboard` | `remove_keyboard: true`, `selective` | | `force_reply` | `force_reply: true`, `input_field_placeholder`, `selective` | Кнопки inline-клавиатуры (ровно одно действие на кнопку, иначе `BUTTON_INVALID`): `url`, `callback_data` (до 64 байт), `web_app`, `login_url` (`url`, `bot_username`, `forward_text`, `request_write_access`), `switch_inline_query`, `switch_inline_query_current_chat`, `switch_inline_query_chosen_chat`, `copy_text`. У каждой кнопки также есть `text` (обязателен), `style` (`primary` / `danger` / `success` / пусто) и `icon_custom_emoji_id`. Неизвестные поля отклоняются, а не игнорируются. Кнопки reply-клавиатуры: обычная строка либо объект с `text` и одним из `request_contact`, `request_location`, `request_poll` (`type`), `request_users` (`request_id`, `user_is_bot`, `user_is_premium`, `max_quantity`, `request_name`, `request_username`, `request_photo`), `request_chat` (`request_id`, `chat_is_channel`, `chat_is_forum`, `chat_has_username`, `chat_is_created`, `user_administrator_rights`, `bot_administrator_rights`, `bot_is_member`, `request_title`, `request_username`, `request_photo`) или `web_app`. Message entities (для `entities` / `caption_entities`, а также entity `date_time`, которая принимает `unix_time` + `date_time_format`): `bold`, `italic`, `underline`, `strikethrough`, `code`, `pre` (`language`), `text_link` (`url`), `text_mention` (`user.id`), `spoiler`, `blockquote`, `expandable_blockquote`, `custom_emoji` (`custom_emoji_id`), `mention`, `hashtag`, `cashtag`, `bot_command`, `url`, `email`, `phone_number`, `bank_card_number`, `date_time`. Лимиты: 256 entity на сообщение (`ENTITIES_TOO_LONG`), смещения в кодовых единицах UTF-16, неизвестные типы — `ENTITY_TYPE_UNSUPPORTED`. ## Лимиты | Лимит | Значение | |---|---| | Длина текста | 4096 символов после разбора | | Длина подписи | 1024 символа | | Entity на сообщение | 256 | | Id inline-результата | 64 байта | | Callback data | 64 байта | | Команд в `setMyCommands` | 100 | | Размер загрузки | 25 MiB на файл | | Записей в `allowed_updates` | 100 | | `limit` в `getUpdates` | по умолчанию и максимум 100 | | `timeout` в `getUpdates` | ограничивается диапазоном 0..50 секунд | | `max_connections` | 1..100, по умолчанию 40 | | Исходник rich-сообщения | 256 KiB | | `offset`/`limit` в `getFile` | `offset` игнорируется, размер отдаётся целиком | ## Не реализовано Все остальные официальные методы возвращают HTTP `404 METHOD_NOT_FOUND`. Чаще всего до них доходят: - правка: `editMessageMedia`, `editMessageLiveLocation`, `stopMessageLiveLocation`, `stopPoll`, а также `inline_message_id` для `editMessageCaption` / `editMessageReplyMarkup`; - медиагруппы и остальной `send*`-набор: `sendMediaGroup`, `sendPoll`, `sendDice`, `sendGame`; - платежи: `createInvoiceLink`, `sendInvoice`, `answerPreCheckoutQuery`, `answerShippingQuery`, `getStarTransactions`, `refundStarPayment`; - администрирование чатов: `banChatMember`, `unbanChatMember`, `restrictChatMember`, `promoteChatMember`, `setChatPermissions`, `setChatPhoto`, `deleteChatPhoto`, `pinChatMessage`, `unpinChatMessage`, `unpinAllChatMessages`, `setChatTitle`, `setChatDescription`, `leaveChat`; - запросы: `getChat`, `getChatAdministrators`, `getChatMember`, `getChatMemberCount`, `getForumTopic*`, `getUserChatBoosts`; - сообщения: `forwardMessage`, `forwardMessages`, `copyMessage`, `copyMessages`, `sendPoll` и смежные с `getFile` помощники; - администрирование бота: `setMyName`, `setMyDescription`, `setMyShortDescription`, `setMyDefaultAdministratorRights`, `close`, `logOut`; - реакции, игры и методы business-аккаунтов. MTProto-клиенты это не затрагивает: всё, что реализовано для официальных клиентов Android/iOS/Desktop, доступно по MTProto, а не по HTTP. Общие Stars- и Premium-пайплайны описаны в `docs/premium-stars.md`. ## Демо и self-тесты | Путь | Назначение | |---|---| | `cmd/bots/ptbecho` | echo на `python-telegram-bot` против шлюза, включая демо ephemeral-команды | | `cmd/bots/aiogramecho` | echo на aiogram 3, ephemeral-ответы, reply- и inline-клавиатуры | | `cmd/bots/bedolagaformat` | HTML / Markdown / MarkdownV2, rich-меню через `sendRichMessage`, Telegram Login | | `cmd/bots/botcheck` | Go-проба: импортирует токен бота по MTProto и проверяет цикл бота | | `cmd/bots/botdemo` | Go-пример: бот шлёт в канал или пользователю по таймеру | | `cmd/bots/grammystore` | Продовый grammY-бот на HTTP-шлюзе (Postgres, собственный деплой) | | `scripts/botapi_echo_once.py` | Одноразовый self-тест отправки/приёма | | `scripts/botapi_receive_selftest.py` | Self-тест очереди обновлений и webhook |