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.в пути отбрасывается). Правила и трансформеры видят её как таблицу DuckDBctx_<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. Что происходит при создании сессии¶
- Сессия создаётся с контекстом от вызывающей стороны (период, номер
фонда, …) и
context_fetch_report.status = "pending"; срабатываетsession.context_fetch_started. - Воркер выполняет источники последовательно; упавший источник фиксируется, остальные всё равно выполняются.
- Фрагменты вливаются в контекст сессии. Полученное значение побеждает
переданное вызывающей стороной; отчёт помечает такие источники
overwrote_caller_value: true. - Объединённый контекст заново валидируется по схеме. Срабатывает
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. Подробности — в
справочнике плейсхолдеров.