Перейти к содержанию

Справочник 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 делает частый поллинг безвредным. Выбирайте то, что подходит вашей среде исполнения.