A ApproveHub
Начать бесплатно
Войти
Как это работаетТарифыAPIСправка
Открыть справочный центр
Разработчикам

API и вебхуки

REST API поверх JSON и подписанные вебхуки: ваши системы могут создавать заявки, читать их состояние и реагировать сразу после решения. API в бете — детали еще могут меняться, но мы предупредим до того, как что-то сломается.

Аутентификация и токены

  1. Откройте Настройки → API в вашей организации (доступно администраторам).
  2. Создайте токен и сразу скопируйте — он показывается только один раз.
  3. Передавайте его в каждом запросе в заголовке: Authorization: Bearer <token>.
GET /api/v1/workflows/
curl https://approvehub.app/api/v1/workflows/ \
-H "Authorization: Bearer ah_live_…"

Токены принадлежат организации, а не человеку, и отзываются в любой момент. В организации может быть до 20 активных токенов и до 60 вызовов API в минуту.

API

Все методы живут под https://approvehub.app/api/v1 и говорят на обычном JSON.

GET /api/v1/workflows/ Список процессов
POST /api/v1/workflows/ Создать процесс, при желании сразу опубликовав
GET /api/v1/workflows/{id}/ Прочитать процесс и поля, которые ждет его форма
PUT /api/v1/workflows/{id}/ Обновить или опубликовать процесс
GET /api/v1/requests/ Список заявок с фильтрами по процессу, статусу или заявителю
POST /api/v1/requests/ Отправить заявку в процесс
GET /api/v1/requests/{id}/ Прочитать поля, текущий этап и полную историю решений
POST /api/v1/requests/{id}/decisions/ Согласовать или отклонить от имени согласующего
POST /api/v1/files/ Загрузить файл для вложения в заявку

Процессы подробно

GET /api/v1/workflows/ листает процессы организации: ?page=N (по 10 на страницу), фильтры ?status=published и ?status=unpublished; в ответе workflows, page и total_count. Каждый элемент — краткая карточка: id, slug, name, description, status, version и даты.

POST /api/v1/workflows/ принимает name (обязательно) и опционально slug, description, publish, requesters, access, fields и stages. Типы полей: text, long_text, email, url, phone, number, date, time, date_range, checkbox, select, radio, file — у каждого свой объект settings. Этап состоит из групп; группа бывает "any" (достаточно одного согласования) или "all" (нужны все), согласующие — участники организации по id или email. "publish": true сразу открывает процесс для заявок; публикуемому процессу нужно хотя бы одно поле.

POST /api/v1/workflows/
curl -X POST https://approvehub.app/api/v1/workflows/ \
-H "Authorization: Bearer ah_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Vendor contract",
"publish": true,
"fields": [
{"type": "text", "title": "Vendor", "required": true},
{"type": "number", "title": "Amount", "settings": {"kind": "integer", "min": 0}},
{"type": "file", "title": "Contract"}
],
"stages": [
{"name": "Finance", "allow_decline": true,
"groups": [{"type": "any", "approvers": [{"email": "[email protected]"}]}],
"skip_when": [
{"match": "all",
"rules": [{"field": "amount", "operator": "less", "values": ["50000"]}]}
]},
{"name": "Legal",
"groups": [{"type": "all", "approvers": [{"email": "[email protected]"}]}]}
]
}'

У этапа может быть skip_when — список наборов условий, которые читают данные поданной формы. Как только целиком выполняется любой набор, этап согласуется автоматически и его согласующих ни о чем не спрашивают. Набор — это {"match": "all"|"any", "rules": [...]}, наборы объединяются по «или», а условие — {"field": "<слаг поля>", "operator": "…", "values": ["…"]}. Операторы с одним операндом читают первый элемент values, а filled и empty не берут ничего. На этап приходится не больше 5 наборов, на набор — не больше 10 условий.

Доступные оператору условия зависят от типа поля: текстовые поля принимают equals, not_equals, contains, not_contains, starts_with, ends_with; number, date и timeequals, not_equals, greater, greater_or_equal, less, less_or_equal; date_rangestarts_before, starts_after, ends_before, ends_after, longer_than, shorter_than; select и radioequals, not_equals, any_of, none_of; checkboxany_of, all_of, none_of; file сравнивает количество вложений через equals, greater, less. У любого типа есть еще filled и empty. Даты передаются как YYYY-MM-DD, время — как HH:MM, а значения вариантов должны быть вариантами самого поля.

Успешное создание отвечает 201 с полным процессом — включая сгенерированный slug, слаги полей, под которыми отправляются значения, и id этапов и полей. GET /api/v1/workflows/{id}/ возвращает тот же вид, а PUT /api/v1/workflows/{id}/ принимает то же тело, что и создание, — им же процесс публикуется. version отмечает версию определения, по которой поданы заявки, и меняется только при правке формы или цепочки согласования; revision считает каждое сохранение, включая переименования. Передайте прочитанный revision в PUT — и сохранение будет отклонено с 400, если процесс тем временем изменил кто-то другой; без него запись перезапишет то, что сохранено:

201 Created
{
"id": "5c1f…",
"slug": "vendor-contract",
"name": "Vendor contract",
"status": "published",
"version": 1,
"revision": 3,
"requesters": {"mode": "all"},
"fields": [
{"id": "d81f…", "type": "text", "title": "Vendor", "slug": "vendor", "required": true},
{"id": "42aa…", "type": "number", "title": "Amount", "slug": "amount",
"settings": {"kind": "integer", "min": 0}},
{"id": "f7c3…", "type": "file", "title": "Contract", "slug": "contract"}
],
"stages": [
{"id": "9be2…", "name": "Finance", "accept_label": "Approve",
"decline_label": "Decline", "allow_decline": true,
"groups": [{"type": "any", "approvers": [{"id": "27b0…", "name": "Marina K."}]}],
"skip_when": [
{"match": "all",
"rules": [{"field": "amount", "operator": "less", "values": ["50000"]}]}
]},
{"id": "b4d8…", "name": "Legal", "accept_label": "Approve", "allow_decline": false,
"groups": [{"type": "all", "approvers": [{"id": "91e5…", "name": "Lev A."}]}]}
],
"created_at": "2026-07-22T09:14:02Z",
"updated_at": "2026-07-22T09:14:02Z"
}

Заявки подробно

GET /api/v1/requests/ листает заявки организации, новые сверху: ?page=N (по 20 на страницу), фильтры ?workflow=<slug>, ?status=new|in_progress|completed|declined и ?requester=<email участника>. В ответе requests, page и total_count.

POST /api/v1/requests/ создает заявку: workflow — слаг процесса, fields — значения по слагам полей. Обязательные поля должны присутствовать, неизвестные слаги отклоняются.

POST /api/v1/requests/
curl -X POST https://approvehub.app/api/v1/requests/ \
-H "Authorization: Bearer ah_live_…" \
-H "Content-Type: application/json" \
-d '{"workflow": "vendor-contract",
"fields": {"vendor": "Acme Ltd", "amount": 4200, "contract": ["f0a1…"]}}'

Формат значения зависит от типа поля:

  • text, long_text, email, url, phone, select, radio, date и time принимают строку (даты — YYYY-MM-DD).
  • number принимает число.
  • checkbox принимает массив значений выбранных опций.
  • date_range принимает {"from": "…", "to": "…"}.
  • file принимает массив id загруженных файлов (см. «Файлы» ниже).

Создание отвечает 201 с полной заявкой; GET /api/v1/requests/{id}/ возвращает тот же вид позже. stage — этап, на котором заявка ждет решения (после завершения его нет), fields повторяют отправленные значения, decisions растут с каждым решением — action равен "accept" или "decline":

200 OK
{
"id": "8a6c…",
"workflow": "vendor-contract",
"workflow_name": "Vendor contract",
"status": "in_progress",
"origin": "api",
"stage": {"id": "b4d8…", "name": "Legal"},
"created_by": {"id": "27b0…", "name": "Marina K."},
"created_at": "2026-07-22T09:14:02Z",
"fields": [
{"field_id": "d81f…", "slug": "vendor", "title": "Vendor",
"type": "text", "values": ["Acme Ltd"]},
{"field_id": "f7c3…", "slug": "contract", "title": "Contract",
"type": "file", "files": ["contract.pdf"]}
],
"decisions": [
{"stage_id": "9be2…", "decided_by": {"id": "27b0…", "name": "Marina K."},
"action": "accept", "created_at": "2026-07-22T10:02:41Z"}
]
}

POST /api/v1/requests/{id}/decisions/ записывает решение от имени администратора, выпустившего токен, — он должен быть согласующим текущего этапа: action равен "approve" или "reject", для reject обязателен comment. В ответе обновленная заявка; повторное решение или решение без ожидающего хода — 400.

POST /api/v1/requests/{id}/decisions/
curl -X POST https://approvehub.app/api/v1/requests/{id}/decisions/ \
-H "Authorization: Bearer ah_live_…" \
-H "Content-Type: application/json" \
-d '{"action": "reject", "comment": "Budget exceeded"}'

Файлы

POST /api/v1/files/ принимает multipart-форму с единственным полем file (до 20 МБ) и отвечает 201 с загрузкой. Подставьте ее id в file-поле при создании заявки — загрузки, не прикрепленные ни к одной заявке, исчезают через сутки.

POST /api/v1/files/
curl -X POST https://approvehub.app/api/v1/files/ \
-H "Authorization: Bearer ah_live_…" \
{"id": "f0a1…", "name": "contract.pdf", "size": 482133}

Ошибки

Каждая ошибка — JSON с полем error; ошибки валидации добавляют details по полям в том виде, в котором вы их отправили:

400 Bad Request
{"error": "validation failed",
"details": {"fields.amount": "must be at least 0"}}

Коды: 400 — некорректный ввод, 401 — токен отсутствует или отозван, 403 — владельцу токена нельзя действовать с ресурсом либо подписка заблокирована или исчерпан лимит заявок, 404 — чужой или неизвестный id, 429 — превышен rate limit, 5xx — проблема на нашей стороне, такие запросы повторяйте.

Вебхуки

Добавьте адрес вебхука в Настройках → API — и каждое событие заявок организации будет приходить туда подписанным JSON POST-запросом. Сразу после добавления мы отправляем ping, чтобы вы проверили связку.

У доставок общий вид: event, request_id, workflow (слаг), status заявки после события, кто действовал и occurred_at (RFC 3339, UTC). Люди приходят как {id, name} — email-адресов в payload нет.

ping

Отправляется один раз, сразу после добавления вебхука, — чтобы проверить адрес и подпись до настоящего трафика:

POST https://your-app.example/hooks
{
"event": "ping",
"endpoint_id": "1d4e…",
"occurred_at": "2026-07-22T09:00:00Z"
}

request.submitted

В процесс поступила новая заявка. requested_by — тот, кто ее создал:

POST https://your-app.example/hooks
{
"event": "request.submitted",
"request_id": "8a6c…",
"workflow": "vendor-contract",
"status": "new",
"requested_by": {"id": "27b0…", "name": "Marina K."},
"occurred_at": "2026-07-22T09:14:02Z"
}

request.stage_completed

Этап собрал нужные согласования, и заявка пошла дальше; stage — только что завершенный этап, decided_by — согласующий, чье решение его закрыло. Для финального этапа не отправляется — вместо него приходит request.approved:

POST https://your-app.example/hooks
{
"event": "request.stage_completed",
"request_id": "8a6c…",
"workflow": "vendor-contract",
"status": "in_progress",
"stage": {"id": "9be2…", "name": "Finance"},
"decided_by": {"id": "27b0…", "name": "Marina K."},
"occurred_at": "2026-07-22T10:02:41Z"
}

request.approved

Финальный этап согласовал заявку — она решена положительно:

POST https://your-app.example/hooks
{
"event": "request.approved",
"request_id": "8a6c…",
"workflow": "vendor-contract",
"status": "completed",
"decided_by": {"id": "91e5…", "name": "Lev A."},
"occurred_at": "2026-07-22T11:40:19Z"
}

request.rejected

Согласующий отклонил заявку — она решена отрицательно, в comment причина:

POST https://your-app.example/hooks
{
"event": "request.rejected",
"request_id": "8a6c…",
"workflow": "vendor-contract",
"status": "declined",
"decided_by": {"id": "91e5…", "name": "Lev A."},
"comment": "Budget exceeded",
"occurred_at": "2026-07-22T11:40:19Z"
}

Проверка доставок

Каждая доставка несет имя события, Unix-время и HMAC-подпись:

Headers
Content-Type: application/json
X-ApproveHub-Event: request.approved
X-ApproveHub-Timestamp: 1784714561
X-ApproveHub-Signature: sha256=6b47…

Пересчитайте подпись секретом вебхука по времени и сырому телу и сравните с заголовком:

Signature
expected = "sha256=" + hex(hmac_sha256(secret, timestamp + "." + body))

Сравнивайте за константное время и отбрасывайте устаревшие временные метки, чтобы исключить повторную отправку.

Ретраи и автоотключение

Любой ответ не из диапазона 2xx или таймаут повторяется до 8 раз с экспоненциальной задержкой: сначала минута, максимум 30 минут. Отвечайте в пределах 10 секунд — тяжелую работу делайте после ответа.

После 20 неудачных попыток подряд вебхук отключается, а администраторы организации получают письмо. Когда приемник заработает, включите его снова в Настройках → API.

Полезно знать. Токены и секреты подписи вебхуков показываются ровно один раз — при создании. Мы храним только хеш токена и зашифрованную копию секрета; если что-то утекло — отзовите и создайте заново.