API
Публичный API — для своих скриптов и ботов: каналы, сообщения, реакции, файлы, задачи и люди. Всё, что здесь описано, останется в силе: в версии v1 API только дополняется.
- Адрес:
https://app.kontext.su/api/v1/… - Схема OpenAPI:
https://app.kontext.su/api/v1/openapi.json— доступна без входа, её можно импортировать в Postman и Insomnia. - Приложения на Slack Bolt и
slack_sdkработают, если поменять в них адрес API (в режиме HTTP, без Socket Mode) — см. «Совместимость со Slack» и переезд за шесть шагов.
Быстрый старт
- Выпустите токен: Настройки → Токены API → Новый токен. Выберите пространство, области доступа (что токену можно) и срок, подтвердите паролем. Токен показывается один раз — скопируйте его сразу.
- Проверьте его:
curl -H "Authorization: Bearer ktx_p_…" https://app.kontext.su/api/v1/me - Напишите в канал:
Под сообщением в ленте появится неяркая подпись «через API · имя токена».curl -X POST https://app.kontext.su/api/v1/channels/42/messages \ -H "Authorization: Bearer ktx_p_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: report-2026-09-19" \ -d '{"text": "Отчёт дежурного: всё спокойно"}'
Токены
Токен — пропуск скрипта в API. Передавайте его только в заголовке Authorization: Bearer ktx_…. В адресе запроса (?token=…) токен не принимается нигде: адреса запросов попадают в журналы.
| Вид | Префикс | Кто выпускает | Срок |
|---|---|---|---|
| Личный | ktx_p_ | Сам человек, «Настройки → Токены API» | Обязателен: 7, 30, 90 (по умолчанию) или 365 дней |
| Бота | ktx_b_ | Тот, у кого есть право «Интеграции», в карточке бота | Без срока, 30, 90 или 365 дней |
- Одно пространство. Токен выпускается для одного пространства, и API берёт пространство из токена — в путях его нет. На запрос к объекту из другого пространства приходит
404, как будто объекта нет. - Прав не больше, чем у хозяина. Токен может только то, что одновременно разрешают его области доступа и роль его хозяина (человека или бота), и только в пространстве токена. Гость, например, через API видит ровно то же, что в приложении.
- Выпуск подтверждается паролем, а если включена двухфакторная защита (2FA), — ещё и кодом из приложения. Тем, кто входит только через корпоративную учётную запись (SSO), пароль не нужен, но с момента входа должно пройти не больше 10 минут — иначе придётся войти заново. О выпуске личного токена приходит письмо (если почта не подтверждена — сообщение в личку от помощника), за 7 дней до конца срока — ещё одно. О скором конце срока токена бота пишут ответственному за бота.
- Токен перестаёт работать сам, если хозяин вышел из пространства, стал гостем или ему закрыли доступ. Смена пароля и «Выйти везде» токены не отключают — чтобы скрипты не ломались от смены пароля; после неё проверьте список токенов в настройках.
- Администратор пространства видит все действующие токены («Интеграции → Токены»): имя, хозяина, области доступа, последнее использование — и может отозвать любой. Сами значения токенов не видит никто. Там же он может выключить личные токены для всего пространства: они перестанут работать, но не будут отозваны — если включить их снова, скрипты заработают.
- Гостю личные токены не выдаются.
Области доступа
| Область | Что открывает |
|---|---|
| — | GET /v1/me — доступен любому токену |
users:read | Люди пространства — те, кого хозяин токена видит |
channels:read | Список каналов, канал, участники канала |
channels:write | Создать канал, сменить тему и название, вступить в публичный, выйти, добавить и убрать участника |
messages:read | История, тред, одно сообщение, поиск, свои эмодзи пространства |
messages:write | Писать, править и удалять свои сообщения, реакции, личка, сообщения «видно только вам» (только бот) |
files:read | Скачать файл |
files:write | Загрузить файл, чтобы приложить его к сообщению или задаче |
tasks:read | Задачи, комментарии, история задачи, проекты, метки |
tasks:write | Создать и изменить задачу, комментировать |
usergroups:write | Создать группу людей, изменить её, её состав и каналы |
Не хватает области доступа — ответ 403 с кодом missing_scope, а в списке needed — каких областей не хватает.
Общие правила
- Время — ISO 8601 в UTC:
2026-09-19T10:15:00Z. - Id — целые числа. Задачи — по номеру в пространстве (
#123в приложении →/v1/tasks/123). - Списки —
{"items": […], "next_cursor": "…"}.limitот 1 до 200, по умолчанию 50. Следующая страница — тот же запрос сcursor=<next_cursor>;next_cursor: null— страниц больше нет. Курсор не разбирайте и не меняйте: передавайте его как есть. - Неизвестные поля и значения клиент должен игнорировать: в
v1появляются новые методы, поля и значения перечислений. Удаление и переименование — только в/v2; после выхода второй версии прежняя работает ещё не меньше полугода, с заголовкамиDeprecationиSunset. Idempotency-KeyуPOST /v1/channels/{id}/messagesиPOST /v1/tasks: повтор с тем же ключом в течение суток не создаёт второй объект, а возвращает первый (с заголовкомIdempotent-Replayed: true). Пока первый запрос ещё выполняется, повтор получает409— повторите через пару секунд.- Ограничение частоты: 300 запросов в минуту на чтение и 60 на запись — на каждый токен, а не на IP-адрес: скрипты одного офиса за общим NAT не делят предел между собой. В каждом ответе есть
X-RateLimit-LimitиX-RateLimit-Remaining; при превышении —429сRetry-Afterв секундах.
Ошибки
Тело у всех ошибок устроено одинаково: detail — по-русски, для человека; code — для программы.
{"detail": "Срок токена «Отчёт» истёк 2026-09-01 — выпустите новый",
"code": "token_expired"}
| HTTP | Код | Когда |
|---|---|---|
| 401 | invalid_auth | Нет заголовка, в нём не токен или такого токена нет |
| 401 | token_revoked | Токен отозван |
| 401 | token_expired | Срок истёк |
| 401 | token_inactive | Хозяин больше не участник пространства, стал гостем, или в пространстве выключены личные токены |
| 401 | user_inactive | Доступ хозяина закрыт |
| 401 | bot_disabled | Бот выключен |
| 403 | missing_scope | Не хватает области доступа; какой — в needed |
| 403 | forbidden | Роль хозяина этого не позволяет |
| 404 | not_found | Объекта нет — или к нему нет доступа (ответ одинаковый нарочно) |
| 409 | conflict | Запрос с этим Idempotency-Key ещё выполняется; у групп — @handle или название уже заняты (причина — в reason) |
| 410 | gone | Ответы через адрес команды (response_url) кончились или команда выключена — повторять бесполезно |
| 413 | too_large | Файл больше предела |
| 422 | invalid_request | Запрос не прошёл проверку; fields — поле → что с ним не так |
| 422 | too_many_reactions | На сообщении уже 50 разных эмодзи: новое не поставить, но к уже поставленной реакции присоединиться можно |
| 423 | workspace_read_only | Пространство только для чтения (подписка не оплачена): чтение работает, запись — нет; в теле также workspace_id и state |
| 429 | rate_limited | Слишком много запросов; подождите Retry-After секунд |
| 507 | no_space | В пространстве или на сервере кончилось место |
Коды у 401 разные нарочно: ответ «срок истёк» вместо «неверный токен» сразу называет причину и экономит час поиска.
Токен API работает только с /api/v1 и /api/slack. На остальных адресах приложения — в том числе на веб-сокете и на MCP-коннекторе — запрос с ним получает 401 «Токен API работает только с /api/v1 и /api/slack».
Методы
Кто я
GET /v1/me — не требует области доступа.
{
"kind": "person",
"user": {"id": 7, "username": "anna", "display_name": "Анна",
"is_bot": false, "deactivated": false, "avatar_url": null},
"workspace": {"id": 3, "name": "Ромашка", "slug": "romashka"},
"token": {"name": "Отчёт дежурного", "kind": "personal",
"prefix": "ktx_p_Ab3dE", "scopes": ["messages:read"],
"expires_at": "2026-12-18T10:00:00Z"},
"bot_id": null
}
Люди — users:read
| Запрос | Что |
|---|---|
GET /v1/users | Люди пространства, которых видит хозяин токена; q — часть логина или имени, limit, cursor |
GET /v1/users/{id} | Один человек |
GET /v1/users/by-username/{login} | По логину (@ можно не писать) |
User: id, username, display_name, is_bot, deactivated, avatar_url. Адресов почты API не отдаёт.
Каналы
Чтение — область channels:read, изменение — channels:write, личка — messages:write.
| Запрос | Что |
|---|---|
GET /v1/channels | Список; фильтры: types (channel, dm, voice), member, archived. member=true — каналы, где состоит хозяин токена; false — публичные, куда можно вступить |
GET /v1/channels/{id} | Канал |
GET /v1/channels/{id}/members | Участники, страницами |
POST /v1/channels | Поля name, is_private, topic → 201; роль должна разрешать создавать каналы |
PATCH /v1/channels/{id} | Поля topic, name, archived — те же правила, что в приложении |
POST /v1/channels/{id}/join | Вступить можно только в публичный |
POST /v1/channels/{id}/leave | Выйти → 204 |
POST /v1/channels/{id}/members | Поле user_id — добавить |
DELETE /v1/channels/{id}/members/{user_id} | Убрать → 204 |
POST /v1/dm | {"user_ids": [5]} → личка; если такая уже есть, вернётся она |
Channel: id, kind (channel, dm или voice), name (у лички null), is_private, topic, archived, is_member, created_at.
Сообщения
Чтение и поиск — область messages:read, остальное — messages:write.
| Запрос | Что |
|---|---|
GET /v1/channels/{id}/messages | Сообщения канала, кроме ответов в тредах, новые первыми; since, limit, cursor |
GET /v1/messages/{id} | Одно сообщение |
GET /v1/messages/{id}/replies | Ответы в треде, старые первыми; id — первое сообщение треда или любой ответ в нём |
POST /v1/channels/{id}/messages | Поля text, blocks, thread_id, file_ids, cite → 201; поддерживает Idempotency-Key |
PATCH /v1/messages/{id} | Поля text, blocks — только свои сообщения; blocks: null — не менять карточку, [] — убрать её |
DELETE /v1/messages/{id} | Своё — или любое, если роль разрешает удалять чужие → 204 |
PUT /v1/messages/{id}/reactions/{emoji} | Поставить реакцию |
DELETE /v1/messages/{id}/reactions/{emoji} | Снять свою |
POST /v1/messages/ephemeral | Только бот: поля channel_id, user_id, text, blocks, thread_id — сообщение «видно только вам» |
GET /v1/search/messages | Поиск: q, channel_id, author_id, from, to, limit (до 100, по умолчанию 30; одна страница, без курсора). Находит ровно то, что хозяин токена видит в приложении |
- Реакцию ставят и снимают отдельными запросами, а не переключают: скрипты повторяют запросы, и переключатель при повторе то ставил бы реакцию, то снимал. Повторный
PUTничего не меняет.emoji— сам символ (🔥, в адресе — в URL-кодировке), имя Slack (thumbsup,+1,white_check_mark,eyes,rocket…; с тоном кожи —+1::skin-tone-3) или своё эмодзи пространства (partyparrotили:partyparrot:, псевдоним тоже подходит). Неизвестное эмодзи —422invalid_request. Разных эмодзи на сообщении не больше 50: новое сверх предела —422too_many_reactions, а к уже поставленной реакции присоединиться можно всегда. thread_id— ответить в тред (id первого сообщения треда или любого ответа в нём).file_ids— сначала загрузите файл (POST /v1/files), потом приложите его id.text— с той же разметкой, что в поле ввода приложения:**жирный**,_курсив_,`код`, списки, цитаты>,[подпись](адрес),:эмодзи:, свои эмодзи пространства тоже. Внутриblocks— mrkdwn Slack.- Упоминание —
@loginв тексте, как в приложении;@группауведомляет тех её участников, кто есть в канале;@channelи@everyoneуведомляют весь канал,@here— тех, кто сейчас в сети. - Сообщение «видно только вам» не хранится: получатель видит его, пока не перезагрузит страницу. И бот, и получатель должны состоять в канале.
blocks— карточка в формате Slack Block Kit (массив блоков, как у Slack): секции с полями, картинки, контекст, кнопки, списки, даты. Кнопки и списки — только у токена бота: нажатия приходят на его адрес действий (см. «Кнопки и окна»); у личного токена они убираются, а кнопки-ссылки остаются.textрядом — запасной текст для push-уведомления. Ошибка в блоках —422invalid_requestсfields.blocks(тексты ошибок как у Slack, с указанием места). Предупреждения (например, что неподдерживаемый блок показан пометкой) приходят в заголовкеKontext-Warning.
Message: id, channel_id, thread_id (id первого сообщения треда; null — если это не ответ в треде), author (User), text, created_at, edited_at, deleted, reply_count, reactions ([{emoji, name, count, user_ids, url}]: emoji — символ или :имя: своего эмодзи, name — имя Slack без двоеточий, url — картинка своего эмодзи, у стандартного — null), files ([{id, name, mime, size}]), via ({"kind": "api", "name": "Отчёт дежурного"} у сообщения, написанного личным токеном, иначе null), permalink, blocks (карточка в том виде, в каком её прислали; null — обычное сообщение), legacy_attachments (вложения Slack, если сообщение отправлено через Slack API).
Файлы
| Запрос | Что |
|---|---|
POST /v1/files?name=отчёт.pdf | Область files:write. Тело запроса — сам файл, тип — из Content-Type → 201 {id, name, mime, size} |
GET /v1/files/{id} | Область files:read. Скачать файл — только если хозяин токена видит его в этом пространстве |
curl -X POST "https://app.kontext.su/api/v1/files?name=report.pdf" \
-H "Authorization: Bearer ktx_p_…" -H "Content-Type: application/pdf" \
--data-binary @report.pdf
Предел размера — как в приложении (файл больше — 413, too_large). Если в пространстве кончилось место, ответ 507 (no_space) приходит сразу, ещё до конца загрузки.
Свои эмодзи — messages:read
| Запрос | Что |
|---|---|
GET /v1/emoji | Свои эмодзи пространства по алфавиту, вместе с псевдонимами; limit до 1000 (по умолчанию 200), cursor |
GET /v1/emoji/files/{key} | Картинка эмодзи (адрес — поле url из списка), токен — в заголовке; картинка из другого пространства — 404 |
Emoji: name, url, alias_of (у псевдонима — имя исходного эмодзи, картинка тоже от него), animated, created_at. В тексте и реакциях своё эмодзи пишется как :name:. Добавлять и удалять эмодзи через API нельзя, только в приложении.
Задачи
Чтение — область tasks:read, изменение и комментарии — tasks:write.
| Запрос | Что |
|---|---|
GET /v1/tasks | status (через запятую), assignee (id или me), project_id, label_id, priority, q, include_closed; без status — только открытые; новые номера первыми |
GET /v1/tasks/{number} | Задача |
POST /v1/tasks | Поля title, description, status, priority, due_at, assignee_id, project_id, label_ids, is_private, viewer_ids, file_ids → 201; поддерживает Idempotency-Key |
PATCH /v1/tasks/{number} | Те же поля; чтобы очистить поле, перечислите его в clear — можно due_at, assignee_id, project_id |
GET /v1/tasks/{number}/comments | Комментарии, старые первыми |
POST /v1/tasks/{number}/comments | Поля text, file_ids → 201 |
GET /v1/tasks/{number}/history | Кто и что менял — например, кто перенёс срок |
GET /v1/projects | Проекты; archived=false — без архивных |
GET /v1/labels | Метки |
Статусы: todo, in_progress, testing, done, cancelled. Приоритеты: low, normal, high, urgent. Удалять задачи через API нельзя — только в приложении.
# мои открытые задачи
curl -H "Authorization: Bearer ktx_p_…" \
"https://app.kontext.su/api/v1/tasks?assignee=me"
Task: number, title, description, status, priority, due_at, assignee, project, labels, is_private, created_by, created_at, completed_at, url.
Группы людей
Группа — имя вида @handle, которым в тексте упоминают сразу всех её участников, кто есть в канале (@devs, @oncall).
| Запрос | Область | Что |
|---|---|---|
GET /v1/groups | users:read | Группы пространства; ?disabled=true — вместе с отключёнными; limit, cursor |
GET /v1/groups/{id} | users:read | Группа |
POST /v1/groups | usergroups:write | {"handle", "name", "description", "members", "channels"} → 201; каналы — только публичные |
PATCH /v1/groups/{id} | usergroups:write | handle, name, description; "disabled": true — отключить (упоминание группы никого не уведомляет, но @handle остаётся за ней) |
PUT /v1/groups/{id}/members | usergroups:write | {"members": [12, 15]} — новый состав целиком: так бот дежурств меняет, кто сейчас в @oncall |
Group: id, handle, name, description, members, channels, disabled, created_at, updated_at. Создавать группы может хозяин токена с правом «Заводить группы людей» (у гостевой роли этого права не бывает), менять — автор группы, её участник или тот, у кого есть право «Управлять группами». Если у хозяина токена гостевая роль, групп ему не видно: список пустой, запрос одной группы — 404. При отказе в ответе есть поле reason (handle_taken, is_login, invalid_members, too_many_members…) и, где нужно, ids — какие id не подошли.
Боты
Бот — учётная запись пространства для интеграций: пишет от своего имени (с пометкой «бот»), работает с API по своему токену и не занимает места в подписке. Создаёт его тот, у кого есть право «Интеграции»: Пространство → Интеграции → Боты → Новый бот.
- Что бот видит — решает его роль. Рекомендуем «только каналы, куда добавите» (гостевая роль): бот видит только те каналы, куда его добавили. Роль «как участник» открывает публичные каналы и задачи — она нужна, например, боту, который синхронизирует задачи.
- У бота не больше прав, чем у того, кто его настраивает. Если у вас нет права управлять пространством, боту можно дать только роль, все права которой есть у вас самих. Если роль бота шире ваших прав, выпустить ему токен и сменить секрет может администратор пространства: токен бота действует с правами бота.
- В канал бота добавляют в его карточке («Каналы»). У кого есть токен бота, тот может читать всё, что видит бот, — поэтому перед добавлением бота в приватный канал приложение показывает, у кого его токены.
- Секрет подписи — им Kontext подписывает свои запросы к серверу бота (события, слеш-команды и нажатия кнопок) по схеме Slack
v0. Секрет показывается один раз, сменить его можно в карточке. - Выключить — токены бота перестают работать (
bot_disabled), события не приходят, команды не срабатывают; бота можно включить обратно. Удалить — учётная запись бота закрывается, токены отзываются; сообщения бота остаются. - Входящий вебхук от имени бота: в настройках вебхука («Интеграции → Вебхуки») выберите «Писать от имени: бота». Бот сам добавится в канал, и вебхук больше не будет зависеть от того, в каких каналах сейчас состоит человек. Подробнее — Входящие вебхуки.
Исходящие события
Бот может получать события пространства на свой адрес: карточка бота → «События» → Новая подписка. В подписке задают адрес, формат (Kontext, Slack или Mattermost), события, каналы (все каналы бота или выбранные) и галочку «и сообщения ботов». У бота может быть до 10 подписок, в пространстве — до 50.
| Событие | Когда | В data |
|---|---|---|
message.created | Сообщение или ответ в треде | message, channel |
message.mention | То же, но только сообщения, где бота упомянули @логином | message, channel |
message.updated | Автор изменил сообщение | message, channel |
message.deleted | Сообщение удалено | message_id, channel |
reaction.added, reaction.removed | Поставлена или снята реакция | message_id, emoji (в том виде, в каком хранится), name (имя Slack без двоеточий), user, channel |
member.joined, member.left | Человек вступил в пространство, вышел из него или его убрали | user |
channel.created | Создан публичный канал | channel |
task.created, task.updated, task.deleted | Задача создана, изменена, удалена | task, changes (при изменении — [{field, old, new}]) |
task.commented | Комментарий к задаче | task, comment |
Если бот отвечает только на обращения к нему, подпишите его на message.mention: message.created присылает каждое сообщение канала. Если подписка включает оба события, сообщение с упоминанием придёт дважды — у доставки упоминания event_id оканчивается на _m.
Кто что получает. События канала приходят, если бот состоит в канале (или это личка с ботом) и канал проходит фильтр подписки. О собственных действиях бот событий не получает; сообщения других ботов и вебхуков — только с галочкой «и сообщения ботов». Тот, кто создал подписку, тоже должен видеть канал: иначе сообщения приватного канала уходили бы на адрес человека, которого туда не приглашали. Если он ушёл из канала или из пространства, события оттуда больше не приходят, а в карточке подписки появляется предложение переназначить её на себя. События о вступлении, выходе и новом канале получают боты с любой ролью, кроме гостевой. События задач — если задачу видят и бот, и автор подписки.
Тело (формат Kontext) — в data те же объекты, что возвращают методы выше:
{
"type": "event_callback",
"event_id": "evt_3f2c9a…",
"event": "message.created",
"event_time": "2026-09-19T10:15:00Z",
"workspace": {"id": 3, "slug": "romashka"},
"bot": {"id": 12, "user_id": 45},
"data": {
"message": {"id": 901, "text": "Сборка упала", "…": "…"},
"channel": {"id": 42, "…": "…"}
}
}
Заголовки: X-Kontext-Event, X-Kontext-Delivery (он же event_id), X-Kontext-Request-Timestamp и X-Kontext-Signature (те же значения повторяются в X-Slack-Request-Timestamp и X-Slack-Signature), при повторной доставке — X-Kontext-Retry-Num; User-Agent: Kontext-Hooks/1.0.
Подпись — по схеме Slack v0, секретом подписи бота: v0= + hex(HMAC-SHA256(секрет, v0:<отметка>:<тело>)), где отметка — время запроса из заголовка. Запросы с отметкой старше пяти минут отклоняйте.
import hmac, time
def signed(secret: str, headers, body: bytes) -> bool:
ts = headers["X-Kontext-Request-Timestamp"]
if abs(time.time() - int(ts)) > 300:
return False
base = b"v0:" + ts.encode() + b":" + body
mine = "v0=" + hmac.new(secret.encode(), base, "sha256").hexdigest()
return hmac.compare_digest(mine, headers["X-Kontext-Signature"])
Ответ и повторы. Любой ответ 2xx в течение 5 секунд — событие доставлено; что в теле ответа, не важно (кроме формата Mattermost), его начало видно в журнале. Иначе Kontext повторяет доставку через 1 минуту, 5 минут, 30 минут, 2 часа и 6 часов, а затем помечает её «не доставлено». Перенаправление (3xx) считается неудачей: укажите конечный адрес. Ответ 410 Gone сразу выключает подписку, 20 окончательных неудач подряд — тоже; ответственному за бота приходит сообщение в личку, включить подписку снова можно в карточке. Доставка «хотя бы раз»: одно событие может прийти дважды — отбрасывайте повторы по event_id; порядок событий не гарантирован.
- Проверка связи — при сохранении и по кнопке «Проверить»: приходит событие
pingс пустымdata. - Журнал доставок — в карточке подписки, за 7 дней: код ответа, время, начало ответа; кнопка «Повторить» — для одной доставки или для всех неудачных за сутки.
- Адрес — только
httpилиhttpsи только в интернете: адреса внутренних сетей (10.…,192.168.…,127.…,169.254.…и подобные) отклоняются и при сохранении, и при каждой доставке; по перенаправлениям Kontext не переходит. - Зацикливание. Если два бота в одном канале отвечают друг другу по кругу, подписка выключается. Если на подписку приходится больше 600 событий в минуту, лишние за эту минуту отбрасываются; пять таких минут подряд — и подписка выключается.
- Неоплаченное пространство (только для чтения) событий не отправляет; те, что ждали доставки, отбрасываются.
Формат Slack — Events API (event_callback с token, team_id, api_app_id, authorizations): message.created — message, message.mention — app_mention, правка и удаление — message с subtype message_changed и message_deleted, реакции — reaction_added и reaction_removed, вступление — team_join, канал — channel_created; событий задач и выхода у Slack нет. Проверка связи — url_verification с challenge (Bolt отвечает на неё сам). Подробнее — Боты со Slack.
Формат Mattermost — поля исходящего вебхука Mattermost; приходят только новые сообщения со словом-триггером или из выбранных каналов, а ответ скрипта бот публикует от своего имени — Боты с Mattermost.
Слеш-команды
Если начать сообщение с /, появится список команд. Встроенные: /remind <когда> <что> (напоминание себе в личку: «через 20 минут», «завтра в 10», «в пятницу в 15:00»), /task <название> [@кто] [до <когда>], /topic, /invite @кто, а в самом приложении ещё /help, /me, /shrug, /away, /dnd, /online, /status, /mute, /unmute, /search, /summary, /ask, /call, /msg @кто, /join #канал, /leave. Эти имена нельзя дать своей команде.
Своя команда создаётся в карточке бота → «Команды»: имя (латиница, кириллица, цифры, _ и -, до 32 знаков), адрес, метод (POST или GET), описание и подсказка к аргументам. Проверочный токен показывается один раз. В пространстве — до 50 своих команд; гости их не видят.
Запрос приходит формой (POST) или в строке запроса (GET) — сразу с полями и Slack, и Mattermost: token, team_id, team_domain, enterprise_id, enterprise_name, is_enterprise_install, api_app_id, channel_id, channel_name, user_id, user_name, command, text, response_url, trigger_id, user_mentions, user_mentions_ids, channel_mentions, channel_mentions_ids, root_id и thread_ts (команда в треде) — и kontext_workspace_id, kontext_channel_id, kontext_user_id для скриптов, которые потом обращаются к API. Заголовки: подпись v0 секретом бота (как у событий), Authorization: Token <проверочный токен>, User-Agent: Kontext-Commands/1.0.
Ответ. Если сервер бота не ответил за 3 секунды, вызвавший видит «ждём ответ…»; ответ принимается в течение 30 секунд. Команда не повторяется никогда: деплой не запустится дважды из-за таймаута.
- пустой
200— ничего не показывать; - простой текст — сообщение «видно только вам» для вызвавшего;
- JSON
{"response_type": "ephemeral" | "in_channel", "text", "attachments", "blocks"}: приin_channelбот пишет ответ в канал (даже если сам в нём не состоит), а над ответом видно, кто и какую команду вызвал; если команду вызвали в треде, ответ придёт в тред; блоки и вложения показываются карточкой, и её кнопки работают; - поддерживаются также
replace_original,delete_original,extra_responses(до пяти),channel_id(канал, где состоит бот и который видит вызвавший);usernameиicon_*не действуют.
response_url — чтобы ответить позже или несколько раз: отправьте POST на адрес из поля response_url (https://app.kontext.su/api/v1/respond/<секрет>) с тем же телом, что у обычного ответа. Токен не нужен — секрет уже есть в адресе; адрес действует 30 минут и принимает до пяти ответов. Ошибки — в общем формате API: 404 — адрес устарел или не существует, 410 (gone) — ответы кончились или команда выключена, 413 — ответ больше 64 КБ, 423 — пространство только для чтения, 429 — запросы чаще 30 раз в минуту.
Кнопки и окна
Кнопки, списки и модальные окна приложений — в формате Slack: приложение на Bolt работает без правок.
- Адрес действий — один на бота, как Interactivity Request URL у Slack: карточка бота → «Интерактивность». Для Bolt это тот же
/slack/events, что у событий. Задаёт его тот, кто может выпустить боту токен. - Нажатие приходит запросом
POSTс формойpayload=<JSON>— телоblock_actions, как у Slack (user,trigger_id,container,channel,message,state,response_url,actions), с подписьюv0секретом бота иUser-Agent: Kontext-Interactive/1.0. Ответить нужно за 3 секунды; нажатие не повторяется никогда — «Одобрить» не отправится дважды. Нажать кнопку может любой, кто видит сообщение; кому можно «Одобрить», решает приложение поuser.id. Kontext гарантирует, что этот id настоящий, а кнопка действительно из этого сообщения. response_urlнажатия — как у команды: по умолчанию — новое сообщение «видно только вам» для нажавшего;replace_originalзаменяет исходную карточку (текст и блоки целиком),delete_original— удаляет её,in_channel— пишет в канал. Адрес действует 30 минут, до пяти ответов.- Окна —
views.open,views.update,views.pushпоtrigger_idиз нажатия или команды: он действует 10 секунд и только один раз. Окно видит тот, кто нажал, — в той вкладке, где нажал. Поверх друг друга — до трёх окон. Наview_submissionотвечайтеresponse_action:errors(ошибки под полями),update,push,clear; пустой200закрывает окно.view_closedприходит, если у окна заданnotify_on_close. Через час после последнего действия окно устаревает. - Кнопка со ссылкой (
url) открывает ссылку, а нажатие приложению не отправляет. - Не кладите секреты в
valueиprivate_metadata:valueкнопки доходит до устройства каждого, кто читает канал;private_metadataокна на устройства людей не передаётся, но это поле для данных вашего приложения, а не хранилище секретов. - Картинки из блоков Kontext копирует к себе: у читателей канала они загружаются не с сервера вашего приложения.
- Ярлыки — как Shortcuts у Slack: карточка бота → «Интерактивность» → «Ярлыки» (вид, название, описание,
callback_id). Глобальный ярлык (shortcut) виден в подсказке, которая появляется после «/» в поле ввода; в теле нет канала иresponse_url, и приложение открывает окно поtrigger_id. Ярлык сообщения (message_action) — значок молнии в панели сообщения; он есть только в каналах, которые читает сам бот. В теле —channel,messageиresponse_url. Над ответом в канал видно, кто и каким ярлыком его вызвал; чужое сообщение через этот адрес не изменить и не удалить. external_selectиmulti_external_select— список, варианты которого приложение подбирает по набранному тексту: запросblock_suggestionуходит на тот же адрес действий, когда набраноmin_query_lengthзнаков (по умолчанию 3); ответить нужно{"options"}или{"option_groups"}за 3 секунды. Выбор сверяется с тем, что приложение предложило этому человеку, — подменить вариант или его текст нельзя.- Не поддерживаются: домашняя вкладка (
views.publish), устаревшие интерактивные вложения иdialog.*,rich_text_inputиfile_input, разворачивание ссылок (unfurl). Неподдерживаемый блок в сообщении показывается пометкой, а в окне даёт ошибкуinvalid_argumentsс указанием места.
Совместимость со Slack
Часть методов Slack Web API доступна по адресу https://app.kontext.su/api/slack/ — чтобы приложение на Bolt или slack_sdk заработало без переписывания:
from slack_sdk import WebClient
client = WebClient(token="ktx_b_…",
base_url="https://app.kontext.su/api/slack/")
client.chat_postMessage(channel="#продажи", text="Сборка прошла :rocket:")
Для @slack/web-api (Node) — new WebClient(token, { slackApiUrl: "https://app.kontext.su/api/slack/" }).
| Метод | Область | Заметки |
|---|---|---|
auth.test | — | team_id, user_id, bot_id |
chat.postMessage | messages:write | channel — C…, #имя, U… или @логин (личка); text, blocks, attachments (карточкой), thread_ts |
chat.update, chat.delete | messages:write | Только свои сообщения; blocks и attachments — по правилам Slack (text без blocks убирает блоки) |
chat.postEphemeral | messages:write | Только бот; user должен состоять в канале; blocks — можно с кнопками |
chat.getPermalink | messages:read | |
reactions.add, reactions.remove | messages:write | Имя Slack (с тоном кожи — +1::skin-tone-2), своё эмодзи пространства или сам символ; если на сообщении уже 50 разных эмодзи, новое — ошибка too_many_emoji |
reactions.get | messages:read | channel, timestamp → сообщение с reactions: [{name, users, count}] |
emoji.list | messages:read | Свои эмодзи пространства: имя → адрес картинки (открывается с токеном в заголовке) или alias:… |
conversations.list | channels:read | types, exclude_archived, limit, cursor |
conversations.info, conversations.members | channels:read | |
conversations.history, conversations.replies | messages:read | latest, oldest, inclusive, limit, cursor |
conversations.join | channels:write | Публичный канал |
conversations.open | messages:write | users=U1,U2 → личка |
users.list, users.info | users:read | Без адресов почты |
views.open, views.update, views.push | messages:write | Только токен бота; см. «Кнопки и окна» |
usergroups.list, usergroups.users.list | users:read | include_users, include_count, include_disabled; гостю — пустой список |
usergroups.create, usergroups.update, usergroups.disable, usergroups.enable, usergroups.users.update | usergroups:write | Боту нужна роль «как участник»; users.update задаёт состав целиком |
Не поддерживаются: users.lookupByEmail (адресов почты в API нет), files.* (файлы загружайте через POST /v1/files), views.publish (домашней вкладки нет), dialog.* (вместо него — views.open), chat.unfurl, reactions.list (реакции одного сообщения — через reactions.get). Они отвечают not_supported. Неизвестный метод — unknown_method.
Отличия, о которых стоит знать:
- Id: канал
C<число>, личкаD<число>, человекU<число>, ботB<число>, пространствоT<число>. Просто числа тоже принимаются. tsсообщения — это номер, а не время:"12345.000000". Треды и правка поtsработают, но вычислять поtsвозраст сообщения нельзя — берите время из самого сообщения.- Разметка:
<@U12>в тексте становится настоящим упоминанием, если хозяин токена этого человека видит (иначе остаётся подпись из<@U12|подпись>или простоU12); ссылки<url|текст>становятся ссылками, известные короткие имена эмодзи (:rocket:,:white_check_mark:) — символами.username,icon_*,unfurl_*,reply_broadcastмолча игнорируются. - Ответ при ошибке — всё равно
200с{"ok": false, "error": "…"}, как у Slack, а рядом —detailпо-русски. Исключения:429сRetry-After(SDK повторит сам) и423(restricted_action) — пространство только для чтения. - Коды ошибок — как у Slack:
not_authed,invalid_auth,token_revoked,token_expired,account_inactive,missing_scope(сneededиprovided),channel_not_found,not_in_channel,is_archived,message_not_found,cant_update_message,cant_delete_message,invalid_name,already_reacted,no_reaction,ratelimited.
Безопасность
- Храните токен как пароль: в переменной окружения или хранилище секретов, не в коде и не в адресе запроса.
- Если токен утёк, отзовите его («Настройки → Токены API» или администратор в «Интеграции → Токены»): он перестанет работать со следующего же запроса. Всё, что написано этим токеном, отмечено «через API · имя токена».
- В каждой строке списка токенов видно, когда и с какого IP-адреса им пользовались последний раз и сколько запросов было за неделю: перед отзывом понятно, работает ли ещё скрипт.