Справочник REST API v1¶
API v1 под /api/v1/ — поддерживаемая поверхность для внешних
интеграций. Она рассчитана на дешёвый поллинг, стабильные идентификаторы
и один wire-формат на каждое понятие. Всё на этой странице подчиняется
правилам совместимости: добавления — не breaking,
а ваш клиент обязан игнорировать неизвестные поля.
Аутентификация не отличается от остальной платформы: API-ключ в заголовке
X-API-Key (или JWT bearer-токен). Все ресурсы ограничены вашей
организацией.
Конвенции¶
Конверт. Успешные ответы — {"data": …}; пагинированные коллекции
добавляют "meta": {"total", "limit", "offset"}. Флагов success нет.
Ошибки. Каждая ошибка — application/problem+json
(RFC 9457):
{
"type": "https://expecto.dev/problems/run-already-active",
"title": "Conflict",
"status": 409,
"detail": "Another run is already active for this session",
"code": "RUN_ALREADY_ACTIVE",
"active_run_id": "…"
}
Ветвитесь по слагу type (или расширению code) — оба стабильны.
Запросы, которые текущее состояние ресурса не допускает, — 409
invalid-state; ошибки валидации — 422 validation-error с массивом
errors[], называющим поля.
Пагинация. ?limit= (по умолчанию 100, максимум 1000) и ?offset=
со стабильным документированным порядком сортировки для каждой коллекции.
Идемпотентность. POST /v1/sessions и POST /v1/sessions/{sid}/runs
принимают заголовок Idempotency-Key. Повтор с тем же ключом и телом
возвращает первый результат вместо создания дубликата; тот же ключ с
другим телом — 409 idempotency-key-reuse. Ключи истекают через
24 часа.
Одновременные чтения сессии. Чтение сессии загружает весь её контекст,
поэтому у одного API-ключа одновременно выполняется не более 4 таких
запросов: GET /v1/sessions/{sid}, его подресурсы context и files, а
также legacy-эндпоинты GET /api/sessions. Запрос сверх лимита ждёт
свободный слот до 15 секунд, затем получает 429 с заголовком
Retry-After. Не распараллеливайте шире лимита: клиент, который делает по
запросу на каждую сессию страницы списка, упрётся в ограничение.
Интерактивных пользователей веб-приложения это не касается.
Модель ресурсов¶
Session (тонкий контейнер одной поставки)
└── Run (одно исполнение пайплайна — центральный ресурс)
├── report лёгкая сводка валидации
└── findings единый формат нарушений всех стадий
Минимальная интеграция — пять вызовов:
POST /v1/sessions → создать поставку
PUT /v1/sessions/{sid}/files/{role} × на каждый файл
POST /v1/sessions/{sid}/runs → запустить пайплайн
(вебхук или дешёвый поллинг)
GET /v1/sessions/{sid}/runs/latest/report
GET /v1/sessions/{sid}/runs/latest/findings — если сводка показала ошибки
Sessions¶
POST /v1/sessions¶
{
"data_contract": {"external_id": "FIBU-STD-01"},
"data_provider": {"external_id": "PM-1162"},
"period": "2026-07",
"context": {}
}
data_contract и data_provider принимают {"id": "<uuid>"} либо
{"external_id": "…"} — внешние ID принадлежат вам, уникальны в
организации и избавляют от хранения UUID-маппингов на вашей стороне.
Опционально: data_contract.revision, deadline, supplier_id. Ответ —
201 с тонкой сессией и заголовком Location.
GET /v1/sessions/{sid} — цель поллинга¶
Возвращает ~1 КБ: идентификаторы, status, health, period, сводку
latest_run и links на подресурсы. Ничего тяжёлого не встроено.
Ответ несёт ETag. Поллинг с If-None-Match: <etag> — неизменившаяся
сессия отвечает 304 с пустым телом; опрос раз в пару секунд безвреден.
Подресурсы¶
| Эндпоинт | Назначение |
|---|---|
GET /v1/sessions/{sid}/context |
Контекст сессии (включая контекстные таблицы). Это единственное место, где отдаются таблицы: список сессий возвращает только скалярные значения контекста. |
PATCH /v1/sessions/{sid}/context |
Заменить контекст: {"context": {…}}. |
GET /v1/sessions/{sid}/files |
Статус загрузки и парсинга по ролям + missing_required_roles. |
PUT /v1/sessions/{sid}/files/{role} |
Загрузить и заменить файл роли — один идемпотентный вызов, без отдельного delete. Неизвестная роль → 404. Multipart-тело, поле file. |
POST /v1/sessions/{sid}/decision¶
Один ресурс для человеческого решения по поставке:
{"action": "approve", "comment": "Salden geprüft"}
action — confirm, approve или reject. comment обязателен для
reject (причина отклонения) и служит причиной override при approve
сессии с блокирующими нарушениями. Решение, недопустимое в текущем
статусе, — проблема 409 invalid-state.
Runs¶
POST /v1/sessions/{sid}/runs¶
Запуск пайплайна — это создание Run'а:
{"stages": ["parse"], "source": "salesforce"}
Оба поля опциональны. Без stages идёт полный пайплайн от текущей стадии
сессии (сначала парсинг — технический результат приходит за секунды,
содержательный позже); ["parse"] останавливается после парсинга.
source — свободная метка происхождения, записываемая на Run.
Ответ — 202 с Location: /v1/sessions/{sid}/runs/{rid} и телом Run'а.
Пока Run активен, второй POST отвечает 409 run-already-active —
повтор с тем же Idempotency-Key вместо этого вернёт исходный 202.
Если автоматический сбор контекста проекта ещё идёт, когда запрошен Run,
Run принимается (202, статус pending), и парсинг стартует сразу —
session.parsed или session.failed (stage = parse) приходит за
секунды. Затем Run ждёт перед трансформацией: статус waiting,
waiting_for: "context_fetch", executed_stages: ["parse"]. Он
продолжается сам, когда сбор завершится, под тем же id. Если сбор упал или
ожидание превысило серверный RUN_WAIT_TIMEOUT_SECONDS (по умолчанию 30
мин, считая от создания Run'а), Run закрывается как
errors и уходит session.failed (reason context_fetch_failed /
context_fetch_timeout). Второй POST, пока Run ждёт, — 409
run-already-active с id ждущего Run'а.
GET /v1/sessions/{sid}/runs и GET …/runs/{rid}¶
История (новые первыми, с пагинацией) и ресурс Run'а: один вердикт
status (pending / waiting / running / passed / warnings /
errors; первые три — не финальные), waiting_for (context_fetch, пока
Run ждёт, иначе null),
requested_stages против executed_stages, исходы и тайминги стадий в
stages, trigger {channel, source}, error + error_class, если Run
упал вне данных.
Литерал latest принимается всюду, где стоит {rid} — после вебхука
читайте /runs/latest/report, не резолвя id.
Результаты¶
GET /v1/sessions/{sid}/runs/{rid}/report — «файл 1»¶
Лёгкая сводка валидации, доступная после завершения Run'а (до того —
409 run-not-finished): счётчики плюс запись на каждое проверенное
правило (rule_id, name, stage, outcome, level, error_class,
total_findings, message). Примеры строк — только с
?include=samples. session_id и run_id всегда в теле.
GET /v1/sessions/{sid}/runs/{rid}/findings¶
Один формат записи для нарушений всех стадий:
{
"finding_id": "1f0c5a…",
"stage": "check",
"rule": {"id": "8ec5e7d9-…", "name": "BR-3 Balance Reconciliation"},
"severity": "error",
"class": "content",
"message": "Pro Konto und Objekt: …",
"location": {"role": "buchungen", "row": 17, "column": "Saldo"},
"data": {"sap_account": "0000300080", "balance_current": "1000.00"}
}
stage—parse(формат файла),validate(правила Entity),check(бизнес-правила проекта).rule.id— стабильный UUID; фильтруйте?rule_id=. Имена правил — отображаемые строки, а не идентификаторы. У parse-проверокrule.id: null.data— ровно те колонки, которые вернул SELECT правила.- Фильтры:
?stage=,?rule_id=,?severity=,?class=; стандартная пагинация; стабильная сортировка(stage, rule, row, finding_id).
Прогрессивная доступность: findings завершившейся стадии читаются,
пока Run ещё выполняет следующие, — запрашивайте ?stage=parse сразу
после вебхука session.parsed, через секунды после старта.
GET …/findings/summary возвращает агрегированные счётчики по стадиям и
правилам без отдельных записей.
CSV¶
Отправьте Accept: text/csv на /report или /findings — те же данные
вернутся синхронным CSV-стримом, с колонками session_id и run_id в
каждой строке. Выгрузка одного правила (?rule_id=…) разворачивает
колонки самого правила («файл на правило»). Выше лимита строк запрос
отвечает problem-деталью со ссылкой на асинхронный экспорт — но никогда
усечённым файлом.
Вебхуки или поллинг?¶
Поддерживается и то и другое, и оба дёшевы: события вебхуков (см.
справочник вебхуков) несут data.run_id, а тонкая сессия
с ETag делает частый поллинг безвредным. Выбирайте то, что подходит
вашей среде исполнения.