Public API cookbook
Документ для инженеров, которые впервые подключают Tracium Public API. Если
лежит вопрос «что делать в моей ситуации» — ответ здесь. Контракт целиком
описан в 20-architecture/schemas/api/public-api.openapi.yaml; этот
cookbook — навигация поверх него.
Базовое окружение
| Окружение | URL |
|---|---|
| Production | https://api.tracium.ru/v1 |
| Staging | https://api.staging.tracium.ru/v1 |
| Local dev | https://api.tracium.dev/v1 |
Везде, где в примерах ниже встречается BASE, подставь URL нужного
окружения. API-token (trk_…) клиент получает в кабинете на странице
API ключи. Все запросы отправляются с заголовком
Authorization: Bearer trk_….
Что сейчас live, а что target
Документ описывает и live-сценарии, и целевую карту API. Для интеграции клиента начинать нужно только с live endpoint’ов:
| Статус | Endpoint | Зачем нужен |
|---|---|---|
| Live | GET /v1/whoami | Проверить токен и identity клиента |
| Live | GET /v1/products | Пролистать read-side каталог |
| Live | GET /v1/products/{id_or_viewable} | Получить карточку товара, описание, характеристики, supplier offers |
| Live | POST /v1/products/resolve | Сопоставить supplier SKU с canonical product refs |
| Live | POST /v1/search/proposals | По списку позиций получить предложения: поставщик, цена, наличие, доставка |
Остальные endpoint’ы в OpenAPI (/offers/*, /pricing, /estimates/*,
/enrichment-jobs/*, /products/analog-search) пока считаются
target-контрактом. Их нельзя делать основным путём клиентской интеграции, пока
handler не появится в live coverage.
Первый рабочий сценарий: список товаров → поставщики, цена, наличие, доставка
Это базовый сценарий для инженера с готовой таблицей позиций.
Входные данные
Минимальная строка входной таблицы:
| Поле клиента | Как передавать в Tracium |
|---|---|
| Внутренний номер строки сметы | items[].id |
| Supplier SKU | POST /products/resolve → items[].canonical_ref |
| Tracium product ref, если уже известен | сразу items[].product_ref в POST /search/proposals |
| Количество | items[].qty_requested |
product_ref для POST /search/proposals — это viewable_id вида
TR-XXXXXXX или canonical UUID. Если у клиента на руках supplier SKU /
артикул поставщика, сначала вызываем POST /products/resolve.
Шаг 1. Проверить токен
curl -sS "$BASE/whoami" \
-H "Authorization: Bearer $TRACIUM_TOKEN"Если ответ 200, можно выполнять рабочие запросы. Если 401, токен невалиден
или отозван. Если 403, у токена не хватает scope.
Шаг 2. Сопоставить supplier SKU с canonical product
curl -sS "$BASE/products/resolve" \
-H "Authorization: Bearer $TRACIUM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "line-1", "supplier": "etm", "supplier_sku": "ABC123", "qty_requested": 5 },
{ "id": "line-2", "supplier": "russvet", "supplier_sku": "DEF5678", "qty_requested": 12 }
]
}'Как читать результат:
| Поле | Что означает |
|---|---|
items[].status=resolved | Найден один canonical product; можно идти в /search/proposals |
items[].canonical_ref | UUID canonical product для items[].product_ref следующего запроса |
items[].viewable_id | Человекочитаемый ref TR-XXXXXXX, его тоже можно использовать как product_ref |
items[].offer_ref | Оффер, по которому exact selector был сопоставлен |
items[].status=not_found | Такой supplier + supplier_sku не найден в read-side каталоге |
items[].status=unsupported_query | Shape запроса валиден, но selector ещё не live |
Контракт уже принимает manufacturer, mpn, query, characteristics[] для
будущего параметрического resolver’а. Сейчас такие строки возвращают
unsupported_query с reason characteristic_resolver_not_live, чтобы клиент
не спутал подготовленный контракт с работающим подбором.
Шаг 3. Получить предложения по списку
curl -sS "$BASE/search/proposals" \
-H "Authorization: Bearer $TRACIUM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mode": "reference",
"target_currency": "RUB",
"items": [
{ "id": "line-1", "product_ref": "01HF0000000000000000000001", "qty_requested": 5 },
{ "id": "line-2", "product_ref": "01HF0000000000000000000002", "qty_requested": 12 }
],
"policy": {
"trace": { "inline_level": "summary" }
}
}'Что вернётся по каждой позиции:
| Где смотреть | Что означает |
|---|---|
items_outcome[] | Собрались ли предложения по строке сметы |
proposals_by_item[query_item_id] | Индексы предложений этой строки в общем массиве proposals[] |
proposals[].supplier_ref / seller_ref | Поставщик и продавец |
proposals[].price | Финальная цена в target_currency; mode показывает точность цены |
proposals[].stock | Сколько доступно и покрывает ли это qty_requested |
proposals[].delivery | Диапазон срока доставки и статус доступности |
proposals[].canonical_ref | Canonical product, по которому можно загрузить карточку товара |
proposals[].offer_ref | Конкретный оффер, из которого собрано предложение |
proposals[].position | Позиция в ранжировании; 0 — лучший вариант для строки |
Алгоритм чтения ответа:
- Идём по
items_outcome[]. - Если
status=no_results, фиксируем строку как не найденную. - Если
status=ok, берёмproposals_by_item[query_item_id]. - По каждому индексу читаем
proposals[index]. - Для закупочного решения сравниваем
position,price,stock.status,stock.available_qty,delivery.status,delivery.lead_time_*.
Шаг 4. Загрузить описания и характеристики
POST /v1/search/proposals отдаёт коммерческое предложение, но не является
полной карточкой товара. Когда нужно показать описание, изображения и
характеристики, берём уникальные canonical_ref из выбранных предложений и
загружаем карточки:
curl -sS "$BASE/products/01HF0000000000000000000000" \
-H "Authorization: Bearer $TRACIUM_TOKEN"В Product важны поля:
| Поле | Что использовать |
|---|---|
name, description, images[] | Клиентское отображение товара |
characteristics[] | Характеристики для проверки соответствия смете |
supplier_offers[] | Read-side список офферов поставщиков по этому товару |
prices, stock, delivery | Текущий primary snapshot; для выбора по смете приоритетнее proposals[] |
Правило: для решения «у кого купить эту смету» основным источником остаётся
POST /v1/search/proposals. GET /v1/products/{id_or_viewable} нужен как
дозагрузка карточки и характеристик.
Карта сценариев
| Что у тебя на руках | Куда идти |
|---|---|
| Supplier SKU и количества | Первый рабочий сценарий |
| Список Tracium product refs и количеств (смета) | Сценарий A |
| Нужно пролистать доступные карточки каталога | Сценарий B |
Известен canonical_id или viewable_id, нужно описание | Сценарий C |
| Нужно подобрать по характеристикам | POST /products/resolve shape подготовлен, live-результат пока unsupported_query |
| Нужно найти аналоги по неточным характеристикам | POST /products/analog-search — target |
| Нужны все поставщики данного товара | Сценарий D |
| Нужна история цен или остатков | Сценарий E |
| Хочу пересчитать цену по правилам клиента | Сценарий F |
Чем cached отличается от live | Раздел про RefreshMode |
Сценарий A — собрать смету по известным товарам
Задача. На входе — таблица «Tracium product ref + количество». Нужно понять, у каких поставщиков есть эти товары, по какой цене, в каком количестве и с каким сроком поставки.
Endpoint. POST /v1/search/proposals — главный сценарный endpoint.
Статус. Live. Это основной путь для первой клиентской интеграции.
Важно: в текущем live API items[].product_ref — это TR-XXXXXXX или
canonical UUID. Supplier SKU как product_ref не поддержан.
curl -sS "$BASE/search/proposals" \
-H "Authorization: Bearer $TRACIUM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mode": "reference",
"target_currency": "RUB",
"items": [
{ "id": "p1", "product_ref": "TR-ABC1234", "qty_requested": 5 },
{ "id": "p2", "product_ref": "TR-DEF5678", "qty_requested": 12 },
{ "id": "p3", "product_ref": "TR-GHI9999", "qty_requested": 1 }
],
"policy": { "trace": { "inline_level": "summary" } }
}'Что задаём:
mode: reference— не требуем строгой свежести; pipeline соберёт предложения и из «не свежих» наблюдений (рабочее значение по умолчанию для большинства смет).target_currency: RUB— все цены вернутся в рублях; конвертация (если поставщик в евро или долларах) применится автоматически по курсу слоя 3a.- Каждый
items[].id— твой стабильный идентификатор строки сметы; в ответе он попадёт вquery_item_idи поможет сшить ответ обратно с таблицей. policy.trace.inline_level: summary— pipeline вернёт безопасный trace-блок (полезен для отладки причин «почему вышло именно это предложение»).
Как читать ответ
{
"search_id": "01HF…UUID", // та же UUID есть в заголовке X-Search-Id
"items_echo": [ /* эхо твоих items[] */ ],
"items_outcome": [
{ "query_item_id": "p1", "proposal_count": 3, "status": "ok" },
{ "query_item_id": "p2", "proposal_count": 0, "status": "no_results" },
{ "query_item_id": "p3", "proposal_count": 1, "status": "ok" }
],
"proposals_by_item": {
"p1": [0, 1, 2],
"p3": [3]
},
"proposals": [
{ "candidate_id": "…", "query_item_id": "p1", "supplier_ref": "etm",
"price": { "mode": "fixed",
"amount": { "value": "129050", "currency": "RUB", "precision": 2 } },
"stock": { "available_qty": 17, "status": "ok",
"source_mode": "cached_actual" },
"delivery":{ "lead_time_min_hours": 24, "lead_time_max_hours": 48,
"status": "ok" },
"score": 1.0, "position": 0 },
/* … остальные предложения */
],
"trace": { /* … включается через policy.trace.inline_level */ }
}Алгоритм чтения:
- Прохожу по
items_outcome[]. Еслиstatus=no_results— по этой строке сметы предложений нет, идём дальше. - Для
status=okберуproposals_by_item[query_item_id]— массив индексов вproposals[]. Они уже отсортированы по убыванию релевантности (position=0— лучшее). - По индексу читаю само предложение:
price(сadjustments[]— разбивкой по НДС/наценкам/скидкам),stock,delivery,supplier_ref,seller_ref,lifecycle,source_mode. - Если включил trace —
trace.entries[]пошагово показывает, какие слои pipeline’а отбросили или модифицировали кандидатов.
Когда нужны характеристики/описания товаров
POST /v1/search/proposals не отдаёт описание товара (description,
characteristics). После того как вернулись предложения, для каждого
интересующего canonical_ref дернуть GET /v1/products/{canonical_ref} —
там полные характеристики.
Сценарий B — пролистать каталог
Задача. Нужна страница доступных canonical-карточек для UI или ручной проверки состава каталога.
Endpoint. GET /v1/products?page=…&page_size=….
curl -sS -G "$BASE/products" \
-H "Authorization: Bearer $TRACIUM_TOKEN" \
--data-urlencode "page=1" \
--data-urlencode "page_size=20"Что вернётся: страница ProductListResponse с canonical-карточками,
описанием, характеристиками и текущим read-side срезом по primary supplier
snapshot: цена, остатки, доставка и список supplier_offers.
Если нужно выбрать поставщика под количество, валюту, availability и delivery,
дальше берёшь подходящий id или viewable_id, кладёшь в items[] запроса
POST /v1/search/proposals и получаешь ранжированные предложения.
Сценарий C — получить карточку известного товара
Задача. На руках есть canonical_id (UUID) или человекочитаемый
viewable_id формата TR-XXXXXXX. Нужны описание и характеристики, без
цен.
Endpoint. GET /v1/products/{id_or_viewable}.
curl -sS "$BASE/products/TR-ABC1234" \
-H "Authorization: Bearer $TRACIUM_TOKEN"Вернётся Product с полным набором: name, description, images[],
characteristics[], lifecycle_status, а также текущий read-side срез
prices, stock, delivery и supplier_offers. Для ранжированного выбора
поставщика под конкретное количество всё равно используем /search/proposals.
Сценарий D — посмотреть офферы поставщиков
Задача. Знаем товар и хотим увидеть всех поставщиков, у которых он есть, без расчёта итоговой цены.
Endpoint. GET /v1/products/{id_or_viewable}/offers.
Статус. Target. Для live-интеграции сейчас используйте
GET /v1/products/{id_or_viewable} и поле supplier_offers[].
curl -sS "$BASE/products/TR-ABC1234/offers?only_available=true&limit=50" \
-H "Authorization: Bearer $TRACIUM_TOKEN"Параметр only_available=true отрежет офферы с нулевым стоком. Возвращаются
карточки оффера с последним наблюдением цены/наличия и ссылкой на
поставщика. Цены тут «как есть», без применения правил клиента — для
финальной цены идти в POST /v1/pricing или POST /v1/search/proposals.
Сценарий E — получить историю наблюдений
Задача. Аудит, графики, выявление трендов: нужны точки наблюдений по конкретному офферу за период.
Endpoint. GET /v1/offers/{id}/observations.
Статус. Target. В live coverage этот endpoint пока не зарегистрирован.
curl -sS -G "$BASE/offers/$OFFER_UUID/observations" \
-H "Authorization: Bearer $TRACIUM_TOKEN" \
--data-urlencode "from=2026-04-01T00:00:00Z" \
--data-urlencode "to=2026-05-01T00:00:00Z" \
--data-urlencode "kind=price"kind — price, stock или all. Данные append-only: каждое
наблюдение — отдельная запись с observed_at. Пагинация — через
next_cursor.
Сценарий F — точечный расчёт цены
Задача. Известен offer_id или canonical_id одной позиции, нужна
финальная цена клиента (со всеми правилами: НДС, наценка, скидка,
custom-handler) — без полного pipeline’а сметы.
Endpoint. POST /v1/pricing.
Статус. Target. В live coverage этот endpoint пока не зарегистрирован.
Для сметного сценария используйте POST /v1/search/proposals.
curl -sS "$BASE/pricing" \
-H "Authorization: Bearer $TRACIUM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "offer_id": "01HF…UUID", "qty": 5 },
{ "canonical_id": "01HG…UUID", "qty": 1 }
],
"target_currency": "RUB"
}'Если задан canonical_id, движок сам выберет подходящий observation
(с учётом visibility и preferred-sellers). Поддерживает batch до многих
позиций.
POST /v1/pricing уместен в калькуляторах, экспорте прайс-листа,
проверке стоимости одной позиции. Для сметы — POST /v1/search/proposals,
который сделает всё то же самое плюс наличие, доставку и ранжирование.
Режимы RefreshMode: cached_only vs live
POST /v1/search/proposals поддерживает два режима обращения к данным,
управляются через policy.refresh_mode:
cached_only(по умолчанию для всехmode, кромеmode=live). Pipeline читает только локальные кэши. Всегда быстрый, не зависит от доступности поставщика, не расходует customer credentials. Подходит для основной массы запросов.live. Pipeline для каждого кандидата пытается сделать live-вызов к поставщику через customer credential. Если живой вызов не удался (нет credential, исчерпан бюджет на cache-warm, ошибка коннектора), pipeline откатывается на cached-данные и явно проставляет причину в поляхstock.live_attempt_reason/delivery.live_attempt_reason(live_used,live_unavailable_no_credential,live_budget_exhausted,live_connector_failedи т.д.). Клиент видит, что попытка была и почему она не удалась.
Когда брать live: «прямо сейчас нужны точные данные у конкретного
поставщика» (закупка, оперативный заказ, проверка перед отправкой).
В остальных случаях кэшированные данные дешевле и быстрее.
Заголовки потребления и лимитов
Каждый ответ Public API несёт служебные заголовки, которые помогают клиенту следить за лимитами:
| Заголовок | Что внутри |
|---|---|
X-RateLimit-{Limit,Remaining,Reset}-{minute,hour,day,week} | Скользящие окна |
X-Quota-Requests-{Limit,Remaining,Reset} | Биллинговая квота на запросы (обычно месяц) |
X-Quota-Data-Bytes-* | То же по объёму отданных данных |
X-Usage-Packages-Requests-Remaining | Остаток в prepaid-пакетах |
X-Plan-Code | Код активного плана |
X-Request-Weight | Сколько единиц списано с квоты за запрос |
X-Search-Id | UUID запуска (только у POST /search/proposals) |
Превышение короткого окна — 429 rate_limited + Retry-After. Превышение
биллинговой квоты — 429 quota_exhausted. Endpoint, недоступный в плане,
— 402 plan_required.
Дальнейшее чтение
- Полный контракт:
20-architecture/schemas/api/public-api.openapi.yaml. - Аутентификация и API-ключи:
30-services/cabinet-keys/, ADR-0051 (20-architecture/adr/0051-public-api-auth-and-api-key-rbac.md). - Семантика свежести и no-proxy: ADR-0014.
- Visibility: ADR-0012.
- Multi-protocol API (gRPC, WS): ADR-0015.