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

BuiltAPI

Забирайте мастер-данные и историю из workspace BuiltAPI в контекст сессии — сальдо прошлого месяца для правила сверки, карточку фонда, список допустимых МВЗ, — чтобы поставщик указывал только период и фонд, а остальное Expecto добирал сам.

Направление: только context. Expecto опрашивает workspace при создании сессии и вливает результат в контекст сессии как скаляры или таблицы, которые читают трансформеры, правила entity и бизнес-правила.

1. Создайте подключение

Settings → Connections → Add Connection, тип BuiltAPI:

Поле Значение
Base URL Корневой URL шлюза BuiltAPI, https://… (публичный хост — SSRF-защита отклоняет приватные диапазоны)
Workspace ID UUID workspace; отправляется в каждом вызове как x-builtapi-workspace-id
Credentials Сервисный токен с правом чтения workspace; отправляется как Authorization: Bearer …

Test запрашивает список entity workspace с take: 1 — самый дешёвый вызов, проверяющий и токен, и членство в workspace. query_error здесь означает, что в конфигурации нет id workspace.

POST /api/integration-connections
{
  "name": "BuiltAPI – master data",
  "type": "builtapi",
  "base_url": "https://api.builtapi.example",
  "config": { "workspace_id": "5c1d…" },
  "credentials": "<service token>"
}

2. Объявите источники контекста в проекте

Проект держит до 20 источников контекста. Каждый называет подключение, запрос и то, что он производит — ровно одну контекстную таблицу или один скаляр. Управление в Project → Context, через PUT /api/projects/{id}/context-sources или MCP-инструмент set_project_context_sources:

{
  "sources": [
    {
      "name": "prev_balances",
      "connection_id": "<connection uuid>",
      "query": {
        "kind": "records_search",
        "entity_id": "balances",
        "filter": { "fund": "{{ context.fund_number }}", "period": "{{ context.prev_period }}" },
        "projection": { "account": 1, "closing_balance": 1 }
      },
      "produces": { "table": "prev_balance" }
    },
    {
      "name": "fund_name",
      "connection_id": "<connection uuid>",
      "query": {
        "kind": "records_search",
        "entity_id": "funds",
        "filter": { "number": "{{ context.fund_number }}" },
        "projection": { "name": 1 },
        "take": 1
      },
      "produces": { "scalar": "fund_name" }
    }
  ]
}

PUT заменяет весь список. Имена источников — ^[A-Za-z][A-Za-z0-9_]{0,63}$; производимые цели уникальны в пределах источников проекта.

Виды запросов

records_search — фильтр + проекция по одной entity:

Ключ Значение
entity_id Id entity BuiltAPI (буквы, цифры, дефисы). Никогда не шаблонизируется.
filter Объект фильтра в синтаксисе запросов BuiltAPI. Строковые значения могут содержать плейсхолдеры.
projection Поле → 1 (включить) или 0 (исключить). Колонки произведённой таблицы следуют включённым полям; скалярный источник должен проецировать ровно одно поле.
take Необязательный общий лимит строк, 1–100 000.

aggregation — Mongo-подобный pipeline по одной entity, для join и группировок:

Ключ Значение
entity_id Как выше.
pipeline Непустой список $-стадий, например $match, $lookup, $group, $project. Стадии записи и серверного кода ($out, $merge, $function, …) отклоняются при сохранении.
take Необязательный общий лимит строк.

Пагинация и лимиты

Expecto автоматически листает результат по 1 000 строк на запрос, пока entity не исчерпана или не достигнут take. Без take результат больше 100 000 строк падает с явной ошибкой, а не обрезается молча — сузьте фильтр или задайте take. Страница не больше 10 MiB; тайм-аут запроса 15 с.

Что производит источник

  • Таблица — {"columns": [...], "rows": [[...], ...]} в context._tables.<name>, колонки названы по проецируемым полям (ведущий data. в пути отбрасывается). Правила и трансформеры видят её как таблицу DuckDB ctx_<name>.
  • Скаляр — единственное проецируемое поле первой записи, в context.<name>.

3. Впишите в контекстную схему

Объявите каждый производимый скаляр или таблицу в контекстной схеме проекта и пометьте filled_by: "integration", чтобы поставщиков не просили их указывать, а предварительная валидация знала, что значение приходит из сбора:

{
  "scalars": [
    { "name": "fund_number", "type": "string", "required": true, "filled_by": "org" },
    { "name": "prev_period", "type": "string", "required": true, "filled_by": "org" },
    { "name": "fund_name", "type": "string", "required": true, "filled_by": "integration" }
  ],
  "tables": [
    { "name": "prev_balance", "required": true, "filled_by": "integration", "columns": [...] }
  ]
}

Флаг required определяет и смысл пустого результата: для обязательной цели пустой поиск — ошибка empty_result, которая валит сбор; для необязательной — пустая таблица.

4. Проверьте, прежде чем полагаться

Test query на карточке источника (или POST /api/integration-connections/test-source, MCP test_context_source) выполняет запрос с примерными значениями плейсхолдеров и возвращает число строк, колонки и первые строки — или семантическую ошибку, — не создавая сессию:

{
  "connection_id": "<uuid>",
  "query": { "kind": "records_search", "entity_id": "funds", "filter": { "number": "{{ context.fund_number }}" }, "projection": { "name": 1 } },
  "produces": { "scalar": "fund_name" },
  "params": { "context.fund_number": "F-1001" }
}

5. Что происходит при создании сессии

  1. Сессия создаётся с контекстом от вызывающей стороны (период, номер фонда, …) и context_fetch_report.status = "pending"; срабатывает session.context_fetch_started.
  2. Воркер выполняет источники последовательно; упавший источник фиксируется, остальные всё равно выполняются.
  3. Фрагменты вливаются в контекст сессии. Полученное значение побеждает переданное вызывающей стороной; отчёт помечает такие источники overwrote_caller_value: true.
  4. Объединённый контекст заново валидируется по схеме. Срабатывает session.context_fetch_completed или session.context_fetch_failed.

Пока отчёт в pending или failed, пайплайн не стартует: задача parse заблокирована с понятными причинами по каждому источнику. Устраните причину (подключение, учётные данные, фильтр) и повторите сбор через POST /api/sessions/{id}/context/fetch или MCP-инструмент fetch_session_context — можно до подтверждения сессии. Ручные правки контекста, сделанные тем временем, сохраняются; полученные ключи обновляются.

Отчёт на сессии (GET /api/sessions/{id} → context_fetch_report):

{
  "status": "failed",
  "fetched_at": "2026-09-15T06:12:41Z",
  "sources": [
    { "name": "prev_balances", "status": "ok", "rows": 412, "overwrote_caller_value": false },
    { "name": "fund_name", "status": "error", "error_kind": "empty_result",
      "message": "No value found in BuiltAPI for scalar source 'fund_name'" }
  ]
}

error_kind — одно из connection_error, auth_error, query_error, empty_result — см. справочник.

Плейсхолдеры

В запросе допустимы только два пространства имён: {{ context.<скаляр> }} для скаляров, объявленных в контекстной схеме проекта, и {{ session.id }}, {{ session.period }}, {{ session.data_provider_id }}. Строка, которая целиком является одним плейсхолдером, сохраняет тип значения (числа остаются числами); плейсхолдеры внутри более длинных строк рендерятся как текст. Плейсхолдеры подставляются только в JSON-тело, никогда в URL. Подробности — в справочнике плейсхолдеров.