Reference: Custom Slash Commands
Позволяет боту зарегистрировать свои команды вида /deploy, /oncall, /report. Когда пользователь пишет их в канале, PartyFlow доставляет invocation владельцу команды одним из двух способов:
event_delivery_mode="poll": кладёт событиеslash.commandвGET /api/v1/bot/eventsдля owning-бота.event_delivery_mode!="poll": отправляет HMAC-подписанный POST наcallback_url, ждёт ответ до 3 секунд и публикует результат в чат.
Если бот не может ответить сразу — он позже досылает ответ через trigger_id (аналог Slack response_url, TTL 5 минут, single-use).
Base URL
https://api.partyflow.ruВо всех примерах — переменная $BASE.
Authentication
Все CRUD-запросы к slash command API используют тот же Bearer token, что и Bot REST API:
Authorization: Bearer fri_bot_<token>Токен привязан к боту. Через токен бот может управлять только своими командами — попытка POST/DELETE с token от другого бота возвращает 403 Forbidden.
Registration flow
- Бот вызывает
POST /api/v1/bots/{bot_id}/commands— PartyFlow в ответе единоразово возвращаетsigning_secret. - Бот сохраняет
signing_secret(больше PartyFlow его не покажет). - Пользователь в UI пишет
/deploy prod. - Если бот сейчас в poll mode, PartyFlow кладёт
slash.commandв poll queue и сразу показывает пользователю ephemeral ack «Команда принята, ожидаю ответ бота.» - Если бот не в poll mode, PartyFlow шлёт HMAC-подписанный POST на
callback_url. - Бот отвечает JSON (sync callback) или позже отправляет
POST /api/v1/bot/messages+ заголовокX-PartyFlow-Trigger-Id.
Endpoints
POST /api/v1/bots/{bot_id}/commands
Регистрирует новую команду.
Request body:
{
"name": "deploy",
"description": "Deploy service to environment",
"usage_hint": "[env]",
"callback_url": "https://your-bot.example.com/partyflow/slash"
}| Поле | Тип | Обязательно | Ограничения |
|---|---|---|---|
name |
string | Да | 1..32 символа, нижний регистр, ^[a-z][a-z0-9_-]*$. Без ведущего слэша. |
description |
string | Нет | до 200 символов. Показывается в подсказке slash command UI. |
usage_hint |
string | Нет | до 100 символов. Пример параметров ("[env] [service]"). |
callback_url |
string | Условно | HTTPS URL на инфраструктуре бота. Обязательно для не-poll ботов. Для poll-бота можно передать пустую строку или не передавать поле. SSRF-валидируется при регистрации и при каждом callback dispatch. |
Response (201 Created):
{
"ok": true,
"command": {
"id": "9a2f...",
"space_id": "…",
"bot_id": "…",
"name": "deploy",
"description": "…",
"usage_hint": "[env]",
"callback_url": "https://your-bot.example.com/partyflow/slash",
"is_active": true,
"created_at": "2026-04-18T10:00:00Z",
"updated_at": "2026-04-18T10:00:00Z"
},
"signing_secret": "dead...beef"
}Important:
signing_secretвозвращается один раз. Сохраните его сразу — повторного запроса не будет. Для ротации удалите команду и создайте заново.
Errors:
| Статус | Причина |
|---|---|
| 400 | Неверный JSON / name не подходит под регекс / description или usage_hint длиннее лимита / callback_url не HTTPS / callback_url пустой у не-poll бота. |
| 403 | Bearer token не соответствует bot_id в URL (cross-bot попытка). |
| 409 | Имя уже зарегистрировано в space этого бота или зарезервировано (poll, remind). |
| 402 | Превышен кап на количество команд (25 на бота, 100 на space). |
| 500 | Внутренняя ошибка (детали в логах оператора). |
GET /api/v1/bots/{bot_id}/commands
Список активных команд бота.
Response (200):
{
"ok": true,
"commands": [
{
"id": "…",
"name": "deploy",
"description": "…",
"usage_hint": "[env]",
"callback_url": "https://your-bot.example.com/partyflow/slash",
"is_active": true,
"created_at": "…",
"updated_at": "…"
}
]
}
signing_secretне возвращается. Если потеряли — пересоздайте команду. У poll-командcallback_urlможет быть пустой строкой.
DELETE /api/v1/bots/{bot_id}/commands/{name}
Удаляет команду. Возвращает 200 при успехе, 404 если команды нет.
Dispatch contract
Delivery mode выбирается по текущему event_delivery_mode бота-владельца в момент invocation. Если бот сейчас в poll, используется poll delivery даже если у команды заполнен callback_url.
event_types[] не участвует: custom slash всегда доставляется owning-боту команды.
Poll delivery
Poll-бот получает событие через GET /api/v1/bot/events:
{
"event_type": "slash.command",
"event_id": "8b4c7f2a-...",
"payload_json": "{\"trigger_id\":\"8b4c7f2a-...\",\"command\":\"deploy\",\"command_id\":\"9a2f...\",\"space_id\":\"...\",\"conversation_id\":\"...\",\"user_id\":\"...\",\"text\":\"prod\",\"raw_text\":\"/deploy prod\",\"invoked_at\":\"2026-04-18T10:12:45.123456789Z\"}"
}event_id равен trigger_id, поэтому бот может использовать его для idempotency. Пользователь сразу получает ephemeral ack; финальный результат бот отправляет async-ответом через X-PartyFlow-Trigger-Id.
Callback delivery
Не-poll бот получает HTTP запрос на callback_url:
POST https://your-bot.example.com/partyflow/slash
Content-Type: application/json
User-Agent: PartyFlow-Webhooks/1.0
X-PartyFlow-Signature: sha256=<hex>
X-PartyFlow-Timestamp: 1713434400
X-PartyFlow-Delivery-Id: 8b4c7f2a-... // same as trigger_id
X-PartyFlow-Trigger-Id: 8b4c7f2a-... // use for async response
X-PartyFlow-Command: deploy
X-PartyFlow-Event: slash_command
X-PartyFlow-Webhook-Version: 1Body:
{
"trigger_id": "8b4c7f2a-…",
"command": "deploy",
"command_id": "9a2f…",
"space_id": "…",
"conversation_id": "…",
"user_id": "…",
"text": "prod",
"raw_text": "/deploy prod",
"invoked_at": "2026-04-18T10:12:45.123456789Z",
"thread_id": "…"
}text — аргументы после имени команды (всё, что шло после первого пробела). thread_id присутствует только если команда вызвана в треде.
Response modes
Sync response (бот отвечает в течение 3 секунд)
Ответ — JSON с полями:
| Поле | Значения | Эффект |
|---|---|---|
response_type |
"in_channel" / "ephemeral" (default) |
in_channel — сообщение видит весь канал. ephemeral — только вызвавший пользователь. |
text |
string | Текст сообщения. Обязательное поле для непустого ответа. |
Пример sync (публично в канал):
{"response_type": "in_channel", "text": "Deploy to prod started: https://ci.example.com/run/42"}Пример sync (видимое только пользователю):
{"response_type": "ephemeral", "text": "Unknown environment `prod2`. Try `prod`, `staging`."}Async response (долгая операция)
В callback mode, если операция занимает больше 3 секунд — ответьте пустым body и кодом 200. Пользователь получит эпемерное «Команда принята». В poll mode этот ack уже отправлен при постановке события в очередь. Финальный результат в обоих режимах отправляется через:
POST $BASE/api/v1/bot/messages
Authorization: Bearer fri_bot_<token>
Content-Type: application/json
X-PartyFlow-Trigger-Id: <trigger_id из исходного dispatch>
{
"content": "Deploy finished in 4m 12s",
"metadata_json": "{\"version\":1,\"blocks\":[...]}"
}metadata_json необязателен. Если в нём есть interactive blocks, PartyFlow подпишет кнопки/selects так же, как при обычном POST /api/v1/bot/messages.
PartyFlow валидирует:
trigger_idсуществует и не использован (single-use, TTL 5 минут).- Bearer token принадлежит тому же боту, что получил исходный dispatch.
Канал, тред, space берутся из сохранённого контекста триггера — не из тела запроса. Это защита от confused-deputy.
Async errors:
| Статус | Причина |
|---|---|
| 400 | Отсутствует content. |
| 403 | Trigger принадлежит другому боту. |
| 410 | Trigger истёк (TTL 5 мин) или уже использован. |
HMAC verification
HMAC-заголовки используются только в callback delivery. Poll delivery защищается Bearer bot token'ом на GET /api/v1/bot/events.
Схема: v1:{timestamp}:{raw_body} → HMAC-SHA256 → hex → X-PartyFlow-Signature: sha256=<hex>.
Идентично outgoing webhooks — см. Security и verify-signatures.
Python:
import hmac, hashlib, time
def verify_slash(secret, timestamp, body, signature):
# Protect against replay: accept requests within ±5 min
if abs(int(time.time()) - int(timestamp)) > 300:
return False
base = f"v1:{timestamp}:{body.decode()}".encode()
expected = "sha256=" + hmac.new(secret.encode(), base, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Go:
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
)
func VerifySlash(secret, timestamp string, body []byte, signature string) bool {
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || abs(time.Now().Unix()-ts) > 300 {
return false
}
base := fmt.Sprintf("v1:%s:%s", timestamp, string(body))
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(base))
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}Rate limits
| Уровень | Лимит | Поведение |
|---|---|---|
| per-user × command | 30 req/min | Превышение → пользователь получает эпемерный «Too many requests». |
| per-command global | 300 req/min | Защита endpoint бота от stampede. |
Лимиты зашиты на стороне PartyFlow — вашему боту не нужно считать их самому.
Caps (registration)
| Уровень | Cap (default) |
|---|---|
| Команд на бота | 25 |
| Команд на space | 100 |
Built-in команды (/poll, /remind) не учитываются в лимите space.
Best practices
- Отвечайте быстро. Если операция дольше 3 секунд — верните
200 ""и дошлите через trigger_id. - Валидируйте HMAC на каждом запросе. Без проверки подписи любой злоумышленник может вызвать ваш endpoint.
- Храните
signing_secretв secret manager (Vault / AWS Secrets Manager / GCP Secret Manager). Никогда не коммитите в Git. - Не читайте
channel_id/space_idиз async запроса к себе — PartyFlow берёт их только из сохранённого trigger контекста. - Отвечайте
ephemeral, когда это ошибка пользователя (неверные аргументы и т.п.) — весь канал не должен видеть эти сообщения. - Используйте
usage_hintдля автокомплита в UI — помогает пользователям попадать в правильный синтаксис.
Curl quickstart
# 1. Регистрация команды /deploy
curl -X POST "$BASE/api/v1/bots/$BOT_ID/commands" \
-H "Authorization: Bearer $BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "deploy",
"description": "Deploy service to environment",
"usage_hint": "[env]",
"callback_url": "https://your-bot.example.com/partyflow/slash"
}'
# → сохраните signing_secret из ответа
# 2. Листинг команд
curl -s "$BASE/api/v1/bots/$BOT_ID/commands" \
-H "Authorization: Bearer $BOT_TOKEN"
# 3. Удаление команды
curl -X DELETE "$BASE/api/v1/bots/$BOT_ID/commands/deploy" \
-H "Authorization: Bearer $BOT_TOKEN"
# 4. Async отправка (после получения trigger_id от PartyFlow)
curl -X POST "$BASE/api/v1/bot/messages" \
-H "Authorization: Bearer $BOT_TOKEN" \
-H "X-PartyFlow-Trigger-Id: 8b4c7f2a-..." \
-H "Content-Type: application/json" \
-d '{"content": "Deploy finished in 4m 12s"}'