Публичный API и MCP-сервер
Аутентификация
Два независимых механизма:
- Bearer API-токен (
Authorization: Bearer <token>) — для всего/api/v1/*. - Сессионная кука админки (
it_session) — единственный способ вызвать эндпоинты токенов (GET/POST /api/v1/tokens,DELETE /api/v1/tokens/{id}), потому что для выпуска первого токена ещё нет самого токена для авторизации.
Выпуск токена
# 1. логин, кука сохраняется в cookies.txt
curl -s -c cookies.txt -X POST https://<трекер>/api/internal/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"<пароль>"}'
# 2. выпуск токена под этой сессией
curl -s -b cookies.txt -X POST https://<трекер>/api/v1/tokens \
-H 'Content-Type: application/json' \
-d '{"name":"my-integration","scopes":["read","write"],"rate_limit":600}'
Значение поля token в ответе отдаётся один раз — дальше в базе хранится только его
хэш. Токен можно привязать к сроку действия (expires_at) и своему rate-limit (запросов
в минуту, дефолт — 600). Роль admin может выпустить токен от имени другого пользователя
(user_id).
Отзыв: DELETE /api/v1/tokens/{id} (тоже под сессионной курой).
Права (scopes)
read— требуется на всеGET.write— требуется на все остальные методы (POST/PATCH/DELETE).mcp— отдельный scope для токенов, которые вы выпускаете под MCP-сервер (см. ниже); сам по себе доступ не расширяет — используйте вместе сread/write. Обязателен, если в лицензии есть модуль MCP, но нет публичного API: тогда/api/v1/*принимает только токены сmcp.push_devices,push_send,app_events— узкие scope для бэкенда витрин (устройства и отправка пушей, события приложений).clicks— узкий scope только для серверного кликаPOST /api/v1/clicks(витрины). Не даёт ниread, ниwrite. Выпустить могут ролиadminиbuyer,write— толькоadmin.
Токену не хватает нужного scope → 403 forbidden.
Rate-limit
Считается на токен, в запросах в минуту (rate_limit при выпуске, дефолт 600).
Превышение → 429 rate_limited. Токен без явно заданного лимита ограничений не имеет.
Формат ошибок
Единый для всего API:
{"error": "unauthorized", "message": "нужен заголовок Authorization: Bearer <токен>"}
Машинные коды: unauthorized (нет/неверный токен) · forbidden (не хватает scope) ·
license_required (фича выключена текущей лицензией, 403) · not_found · bad_request ·
validation (400, неверные параметры списка: limit, sort, ids, cursor) · conflict и
in_use (409) · module_unavailable (модуль недоступен в этой установке, 503) · unavailable
(503, временно недоступна зависимость, повторите) · cache_unavailable (502, изменение потока
не удалось опубликовать в кэш клик-пути) · clickhouse (502) · rate_limited (429, вместе
с заголовком Retry-After) · unsupported_media_type (415, у изменяющего запроса тело без
Content-Type: application/json; исключения — загрузки файлов и CSV-импорт, у них свой тип) ·
cross_origin (403, межсайтовый изменяющий запрос по куке сессии) · payload_too_large (413,
тело больше потолка: INOTRACK_API_MAX_JSON_BYTES, по умолчанию 8 МиБ) · internal. У отдельных ручек
есть свои коды (offer_capped,
in_progress, app_not_found…): они перечислены в описании ответов операции в спецификации
OpenAPI. В спецификации этот формат — схема components/schemas/Error, его описывает ответ
default каждой операции.
Списки: пагинация, поиск, сортировка
Правила одни для всех списков каталога (/offers, /flows, /sources, /sites, /advertisers,
/webhooks, /webhooks/{id}/deliveries, /manual-costs и вложенных списков источника и сайта):
| Параметр | Что делает |
|---|---|
limit |
Размер страницы. По умолчанию 50, всё больше 500 срезается до 500. Ноль, отрицательное число или не число дают 400 validation |
offset |
Сдвиг от начала выборки |
q |
Поиск по имени (регистр не важен). Число ищется ещё и как id. Строка короче двух символов игнорируется, если это не число |
sort |
field,-field, до трёх полей. Минус означает обратный порядок. Допустимые поля свои у каждого списка, неизвестное поле даёт 400 validation. Равные значения всегда упорядочиваются по id, поэтому порядок страниц стабилен |
ids |
1,2,3: только перечисленные записи (не больше 500) |
facets |
Например status,owner: счётчики по значениям во всей выборке. Счётчик поля считается без фильтра по этому же полю |
Ответ имеет форму {"items": [...], "total": 123, "limit": 50, "offset": 0}. items всегда
массив. total равен null, если общее число не удалось посчитать за отведённое время.
Тот же total приходит в заголовке X-Total-Count. Старые клиенты без параметров получают, как
раньше, первую страницу прежнего размера в items/total: 50 записей у каталога и доставок
вебхука, 500 у /webhooks, /manual-costs (потолок limit у него 1000), /verticals и вложенных
списков источника и сайта. Новые поля в ответе только добавились.
Журналы, которые растут без ограничений, листаются курсором: cursor вместо offset, в ответе
next_cursor (null на последней странице). Курсор запоминает фильтры, q и sort, при
которых получен. Если передать его с другими параметрами, придёт 400. offset и cursor
вместе тоже дают 400.
Основные сущности
CRUD (список/получить/создать/изменить/удалить, где применимо) для:
/api/v1/advertisers— рекламодатели/CPA-сети;/api/v1/offers, плюсPOST /offers/{id}/pauseи/resume;/api/v1/flows, плюсPATCH /flows/{id}/weights(веса сплита офферов),POST /flows/{id}/pauseи/resume;/api/v1/sources— источники трафика;/api/v1/sites— сайты сетки;/api/v1/tokens— управление собственными API-токенами;/api/v1/webhooks— подписки на вебхуки, плюс/webhooks/{id}/deliveries(лог доставок).POST /api/v1/clicks— серверный клик без редиректа для витрин (scopeclicksилиwrite, идемпотентность поIdempotency-Key, клик пишется сhost = "s2s"); сайт определяется в том же порядке, что на/go: явныйsite_id→sub1 == sites.sub_value→ хост Referer (sites.domain/site_domains) → единственный сайт потока (при нуле или нескольких привязках сайт не угадывается) (подробности — docs/showcases.md). Необязательныйallow_fallback: trueвключает цепочку запасной поток/смартлинк → fallback_offer → default_url;allow_last_resort: trueразрешает оффер на автопаузе после этой цепочки (если разрешён инстансом). Без флагов сохраняется 409 без записи клика. Успех возвращаетselected_by:flow|fallback_flow|fallback_offer|default_url|last_resort; клик записывается на каждом успешном пути. Приfallback_flow/default_urlзначениеoffer_id=0: запасной поток выберет конечный оффер сам, постбек сети по исходному clickid не придёт.fallback_flowведёт на трекер,default_url— на заданный адрес. Уникальность определяется окном потока поvisitor_id(до 128 символов), иначе по IP+UA; без этих данных клик уникален.is_repeat: trueозначает повтор. Приunique_block_repeatповтор может уйти только в запасной путь сallow_fallback, иначе409 unique_repeat.
bandit_goal задаёт цель бандита: approve по умолчанию, любая известная цель (ftd, reg, dep, rebill, lead, reject) или revenue. Для цели считаются её конверсии и выплаты; для revenue — конверсии с payout_usd > 0 и сумма выплат всех целей. Тестовые и вручную исключённые конверсии не учитываются. Поле доступно в POST/PATCH и ответах потоков.
У потока три режима ротации (rotation_mode): fixed — сплит по весам офферов, bandit — по
весам, посчитанным ротатором (bandit_weight), и priority — каскад: у каждого оффера потока
есть priority (чем больше число, тем раньше он в каскаде), и клик идёт в доступный оффер
самого высокого приоритета; при равных приоритетах — обычный сплит по весам внутри уровня. Если
на верхнем уровне доступных офферов не осталось, трекер сначала проверяет fallback_offer_id
каждого оффера этого уровня (если он задан и ведёт на другой оффер того же потока) и только
потом переходит на следующий уровень приоритета.
Условия выплаты по ГЕО доступны в geo_payouts в списке и карточке оффера (всегда массив,
без условий — []). POST и PATCH принимают этот массив как полную замену: отсутствие поля
сохраняет набор, [] очищает его. До 250 строк, одна на ISO-3166 alpha-2 страну; принадлежность
страны к offers.geo не требуется. Клонирование копирует условия.
Выбирается строка по ГЕО клика, иначе общие поля offers.*; отдельные поля строки не наследуются.
Пример: {"geo":"AZ","payout_model":"hybrid","payout":50,"revshare_percent":25,"currency":"USD","goal":"ftd","note":""}.
Гибрид — фиксированная часть за goal плюс RevShare. revshare_percent справочный:
трекер сам не считает процент от депозита, доход RevShare приходит постбеками rebill/commission.
При записи пустая модель становится cpa, валюта — валютой оффера, цель — cap_event (иначе approve).
Для revshare фиксированная часть обнуляется, для cpa/cpl обнуляется процент.
Сумма неотрицательна, процент от 0 до 100. Правки отражаются в аудите оффера;
payout_history хранит только историю общей ставки.
В GET /offers и GET /offers/{id} у оффера есть поля для витрин: network (код сети
рекламодателя), network_offer_id (id в сети, равен external_id) и cap_state (состояние кап,
null при недоступном Redis; по каждому окну — метрики conversions/clicks/payout: детали — в разделе «Капы» ниже). Для витрин же предназначены маркировка (erid, реквизиты
рекламодателя, ?has_erid=, ?updated_since=, POST /offers/erid-import) и доп. поля
(custom_fields, источник custom_fields_source). Набор полей задаётся определениями инстанса;
ответ содержит подходящие вертикали поля и все заполненные. conditions и conditions_source —
deprecated-алиасы для старых витрин: conditions по-прежнему содержит все активные ключи,
отсутствующие значения — null. POST/PATCH принимает одно из custom_fields или conditions:
частичный патч, null удаляет ключ. Обязательность проверяется только при ручной записи через API,
не при импорте сетей и CSV erid.
/api/v1/offer-fields: GET — список (включая архив), POST — создание;
PATCH /api/v1/offer-fields/{key} — правка/восстановление,
DELETE /api/v1/offer-fields/{key} — архив (значения сохраняются), ?purge=1 удаляет
только неиспользуемое определение и снимает ссылки pair_max на него. Запись доступна только admin.
GET /api/v1/offer-fields/presets показывает встроенные пресеты.
Включить «Финпродукты» на rivo: POST /api/v1/offer-fields/presets/fin_showcase/apply
с admin-токеном со scope write, без тела. Повторный вызов безопасен: ручные правки сохраняются.
Массовые операции: POST /flows/batch (до 200 потоков, каждый поток — своя транзакция, в ответе
итог по каждому id) и POST /offers/batch, /sources/batch, /landings/batch (до 500 записей,
весь пакет — одна транзакция: чужая или несуществующая запись откатывает всё). Действия каталога:
pause, resume, archive, set_tags, у офферов ещё set_vertical, set_payout, set_caps.
dry_run: true возвращает, что изменится, ничего не меняя. Потоки, в конфиге которых лежат
изменённые офферы, перепубликуются один раз на пакет. Теги — поле tags оффера и источника,
фильтр списков ?tag=.
Полная схема тел запросов и ответов — спецификация OpenAPI, см. следующий раздел.
Продукты (/api/v1/products)
Продукт — карточка витрины (Белки), под которой сходятся офферы РАЗНЫХ сетей одного и того же
предложения: витрина показывает одну карточку («Займер, займ до 30 000»), а за ней могут стоять
офферы нескольких CPA-сетей и прямой договор. Справочник продуктов — /api/v1/products
(список/карточка/создание/правка/архивация, как /verticals): чтение — любой авторизованный,
запись — только роль admin.
Привязка оффера к продукту — обычный PATCH /offers/{id} с полем product_id (существующий,
не в архиве; null — отвязать). Сама склейка никогда не происходит автоматически: ни создание
продукта, ни правка, ни синк каталога сети не трогают offers.product_id.
Подсказки склейки (GET /products/suggestions) — автоматическое предложение, не привязка:
трекер пересчитывает их после правки brand/inn/vertical/archived продукта и при каждом
синке каталога сети, по двум правилам (приоритет — по порядку):
- ИНН рекламодателя оффера (
advertiser_inn) совпал с ИНН продукта (inn); - бренд продукта (
brand) входит в название оффера без учёта регистра.
Кандидат отбрасывается, если у оффера и продукта заданы разные непустые вертикали. Подсказка
живёт до решения человека: подтвердить (PATCH /offers/{id} с product_id, сама подсказка после
этого снимается) или скрыть (DELETE /products/suggestions/{offer_id}) — скрытая подсказка не
возвращается следующим синком.
GET /products/{id}/offers отдаёт офферы продукта тем же форматом, что GET /offers (сети,
cap_state, счётчики потоков), с учётом видимости вызывающего.
Условия выбора оффера
В offers[] потока поле targeting задаёт условия конкретного оффера. Например:
{"offer_id": 42, "weight": 100, "is_active": true, "targeting": [
{"field": "city", "op": "in", "values": ["Moscow", "Saint Petersburg"]},
{"field": "sub3", "op": "eq", "values": ["telegram"]},
{"field": "site", "op": "eq", "values": ["12"]}
]}
field:region,city,sub1–sub10,utm_source,utm_medium,utm_campaign,utm_term,utm_content,site.op:eq— равно первому значению,ne— не равно первому значению,in— входит в список,nin— не входит в список. Строки сравниваются без учёта регистра.- Все правила оффера соединяются И; пустой массив означает отсутствие условий.
Город — как в отчёте по городам; регион — ISO-код или название в зависимости от базы GeoIP.
Пустой город/регион не отсекает оффер при любом операторе. Отсутствующая метка sub/utm
или сайт (
site_id = 0) не проходитeq/in, но проходитne/nin. siteсравнивается численно сsites.id; вvaluesпередавайте положительные десятичные ID строками, например["12"](API не проверяет числовой формат этих строк).
Условия задаются через POST /api/v1/flows и PUT /api/v1/flows/{id}/offers;
в PATCH /api/v1/flows/{id}/weights их нет. Передавайте полный желаемый список условий:
отсутствующее targeting в заменяемом оффере снимает его условия. Панель позволяет редактировать
правила у каждого оффера, для site — выбирать сайты. API принимает до 10 правил на оффер,
от 1 до 20 значений на правило, до 255 символов в значении; пробелы по краям значений
обрезаются, пустые значения и неизвестные поля/операторы возвращают 400.
После публикации потока условия проверяются до взвешенного выбора и при выборе
оффера «последнего шанса», вместе с остальными ограничениями. На /go/{token} сигналы sub/utm
берутся из query-параметров, сайт определяется по порядку: явный site_id, sub1 == sites.sub_value, хост Referer (sites.domain / site_domains), затем единственный сайт потока. При нуле или нескольких привязках сайт не угадывается. Таргетинг офферов получает итоговый сайт; guard вызывается до резолва потока и fallback из привязки не видит.
В POST /api/v1/clicks при выборе из потока передавайте sub1–sub10, utm_source,
utm_medium, utm_campaign, utm_term, utm_content и числовой site_id в JSON-теле;
sub/utm — до 255 символов, site_id — неотрицательное целое, 0 означает отсутствие сайта.
Город и регион в обоих путях определяет GeoIP по IP клика. Старые edge-ноды игнорируют
новое поле конфигурации и условий не применяют: обновите их перед использованием таргетинга.
Спецификация OpenAPI 3.1
Машиночитаемое описание всего /api/v1/* — OpenAPI 3.1.0
(схемы — JSON Schema 2020-12):
GET /api/v1/openapi.json— JSON, для генераторов клиентов, Postman/Insomnia и MCP;GET /api/v1/openapi.yaml— то же самое в YAML.
В спецификации есть все маршруты /api/v1 (соответствие спецификации и реальных маршрутов
проверяется автотестами в обе стороны), схемы тел и ответов, operationId у каждой операции и единая схема
ошибки Error. Внутренних ручек (/api/internal/*) в ней нет. Прокси модулей (/reports/*,
/caps/*, /rules/*, /postback/*, /finance/*, /integrations/*) описаны одной операцией на метод
без схем — маршруты модулей перечислены ниже в этом документе. Часть маршрутов модулей описана в
спецификации отдельно, со схемами и кодами ошибок: сырые клики (/reports/clicks, /reports/clicks.csv,
/reports/clicks/settings), шаблоны и предпросмотр правил (/rules/rule-presets, /rules/rules/preview)
и блеклист зон (/blacklist); новые маршруты модулей попадают в спецификацию так же. Ошибки модуля
правил приходят как {"error": "<текст>"} без message (схема RulesError).
# скачать спецификацию
curl -s https://<трекер>/api/v1/openapi.json -o inotrack-openapi.json
# список операций: метод, путь, operationId
jq -r '.paths | to_entries[] | .key as $p | .value | to_entries[]
| select(.key != "parameters") | "\(.key | ascii_upcase) \($p) \(.value.operationId)"' inotrack-openapi.json
# схема оффера
jq '.components.schemas.Offer' inotrack-openapi.json
# клиент, например на TypeScript (https://openapi-generator.tech; генератор должен понимать OpenAPI 3.1)
npx @openapitools/openapi-generator-cli generate -i inotrack-openapi.json -g typescript-fetch -o ./inotrack-client
servers в спецификации — относительный /api/v1: генератору клиента или Postman укажите базовый
адрес https://<трекер>/api/v1. Bearer-токен — схема bearerAuth; /tokens работают только под
сессионной кукой (sessionCookie).
Обе ручки отвечают без токена и сессии — авторизация на этих двух точных путях не
проверяется. Остальной /api/v1
по-прежнему требует авторизацию: без Authorization придёт 401 unauthorized.
Отчёты, капы, правила, постбеки, финансы, интеграции — через прокси
Эти шесть модулей не имеют собственных путей в /api/v1, а проксируются целиком:
/api/v1/reports/*, /api/v1/caps/*, /api/v1/rules/*, /api/v1/postback/*,
/api/v1/finance/*, /api/v1/integrations/*. Если модуль недоступен в вашей
установке — соответствующий префикс отвечает 503 module_unavailable.
⚠️ /api/v1/integrations/* сегодня отвечает 503 всегда, и это осознанное решение,
не баг: интеграции рекламных площадок управляются из админки под сессионной кукой
(/integrations, не под /api/v1), публичного API для них пока нет.
Прокси сам определяет нужный scope по HTTP-методу (GET → read, остальное → write),
кроме путей модуля отчётов, явно помеченных как read-only (например, экспорт CSV) — им
достаточно read, хотя метод у них POST.
Из финансового модуля стоит отдельно знать GET /api/v1/finance/fx/status (только роль
admin) — свежесть источника курсов валют (дата последнего известного курса ЦБ РФ/CoinGecko,
возраст, флаг stale), см. FAQ про то, как курс фиксируется на дату события.
Отчёты (/api/v1/reports/*)
Доступные маршруты:
| Путь | Метод | Назначение |
|---|---|---|
/query |
POST | универсальный отчёт: {dimensions, metrics, filters, date_from, date_to, timezone, currency, ...} |
/dims, /metrics |
GET | список доступных измерений/метрик |
/seo |
GET | отчёт «сайт → страница → доход» |
/export.csv |
POST | потоковый CSV-экспорт |
/drilldown |
POST | провал в детали (кампания → адсет → крео → площадка) |
/clicks |
POST | сырые клики по условиям строки отчёта: {filters, date_from, date_to, timezone, include_bots, status, limit, cursor} → {items, next_cursor, labels, meta}; курсорная пагинация (cursor = next_cursor прошлой страницы), до 500 кликов на страницу, таймаут 40 с (504 — сузьте период) |
/clicks.csv |
POST | те же клики CSV-файлом, потоково, до 100 000 строк (больше — 422 export_too_large); не больше двух выгрузок сразу — иначе 429 с Retry-After |
/clicks/settings |
GET/PUT | режим показа IP в сырых кликах: prefix (подсеть и хэш, по умолчанию), hash, hidden; менять — только admin |
/cohorts |
POST | когортный анализ и LTV |
/plans, /plans/{id}, /plans/fact |
POST/DELETE/GET | план-факт и линейный прогноз |
/shares, /shares/{id}/revoke |
POST/POST | управление секретными read-only ссылками на отчёт — требуют Bearer-токен, как и остальной прокси |
/shares/view/{token} |
GET | публичный просмотр по ссылке — не под /api/v1, открывается без авторизации (см. FAQ) |
/annotations, /annotations/{id} |
GET/POST/PATCH/DELETE | ручные и авто-аннотации на графиках |
/creatives, /creatives/{id}, /creatives/{id}/upload, /creatives/report |
GET/POST/PATCH/DELETE | библиотека креативов, метрики по креативу |
/sql |
POST | SQL-песочница, см. ниже |
Измерение brand_advertiser_id («Рекламодатель (бренд)») группирует клики и конверсии
по конечному рекламодателю оффера. Привязка читается из текущего
offers.brand_advertiser_id в Postgres, поэтому её изменение пересчитывает и прошлые
события. Оффер без бренда даёт 0 («Без рекламодателя»). Фильтр выбирает сущность
рекламодателя; подписи id приходят в labels. Расход по бренду недоступен.
Измерения advertiser_id и network сохраняют прежний смысл сети.
⚠️ include_bots и P&L. /query, /drilldown, /cohorts по умолчанию
(include_bots не передан или false) считают выручку и остальные денежные метрики без
конверсий с is_bot_click = 1 — это показатель "сколько заработали на людях", который видит
пользователь в отчётах. Финансовый P&L (/api/v1/finance/*) считает
выручку по всем approved-конверсиям независимо от is_bot_click — это полная сумма для
бухгалтерии и сверки с сетью, ей всё равно, кто нажал кнопку. Из-за этого выручка отчёта с
дефолтным include_bots=false и выручка P&L за один и тот же период не обязаны совпадать —
расхождение ровно на сумму approved-конверсий ботового трафика. Чтобы сравнить отчёт с P&L
"яблоки к яблокам", запрашивайте отчёт с include_bots=true.
SQL-песочница (POST /api/v1/reports/sql)
Произвольный SELECT (в т.ч. WITH ... SELECT) к ClickHouse напрямую — только для роли
admin. Защита:
- только один оператор
SELECT/WITHза вызов (лишняя;внутри текста отклоняется); - запрос выполняется отдельным пользователем ClickHouse с правом только
SELECTна базу —INSERT/DDL/DROPфизически невозможны на уровне прав, не только по фильтру текста; - жёсткий таймаут выполнения — 10 секунд;
- жёсткий лимит строк — 1000 (ClickHouse обрезает результат сам,
result_overflow_mode: break), а не отдаёт ошибку при превышении; - отдельный маленький пул соединений (до 3) — тяжёлый ad-hoc запрос не отъедает соединения у боевых отчётов;
- каждый запрос логируется (кто, текст запроса, длительность, число строк, успех/ошибка).
curl -s -X POST https://<трекер>/api/v1/reports/sql \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"query": "SELECT source, count() FROM clicks WHERE ts >= today() - 1 GROUP BY source ORDER BY count() DESC LIMIT 20"}'
Капы (/api/v1/caps/*)
Три метрики капы на оффер, каждая — по своим окнам день/неделя/месяц/total (в таймзоне
оффера, cap_timezone): conversions (как раньше, по offers.cap_event — lead/approve/ftd),
clicks и payout (сумма выплат в валюте оффера). Любая исчерпанная метрика блокирует оффер
по этому окну целиком — перелив на fallback общий для всех метрик.
GET /offers/{id}/status— счётчики капы по каждому окну:{window, count, limit, blocked, clicks, clicks_limit, payout, payout_limit, currency, blocked_by}(blocked_by— список метрик, из-за которых заблокировано именно это окно;currency— только если у окна задан лимит поpayout), плюсmanual_pause,broken,allowed,block_notesиhealthcheck(автопауза health-check). То же самое отдаётся полемcap_stateвGET /offersиGET /offers/{id}(витринные поля, см. выше), толькоnull, если Redis недоступен.POST /offers/{id}/reset-cap?window=day&metric=clicks— сброс счётчика окна вручную (эксплуатация), только рольadmin.window—day(по умолчанию)week/month/total;metric—conversions(по умолчанию) /clicks/payout; что-то другое —400.
Вебхуки
POST /api/v1/webhooks создаёт подписку на события: conversion, cap.reached,
rule.fired, anomaly, offer.down. Секрет подписи (secret) отдаётся один раз в
ответе на создание.
Событие cap.reached (капа выбрана) несёт offer_id, advertiser_id, window, metric
(conversions/clicks/payout), count/limit (для payout — округлённая до целого сумма
и лимит), fallback_offer_id (если задан) и, только для metric=payout, amount/amount_limit
(точная сумма и лимит) и currency. Предупреждение о приближении к капе (80% по умолчанию,
INOTRACK_CAPS_WARN_PERCENT) вебхуками не рассылается — это внутренний алерт (Telegram,
мобильный пуш, лента «Требует внимания», тот же AlertKind cap_reached с severity: warning
и отдельным ключом антиспама), у публичного API события для него нет.
Событие offer.down с полем Country сообщает о проблеме в дополнительной стране и не означает остановку оффера.
Поле alive_country (с confirmed: true и autopaused: false) означает обратное: оффер не
открывается по решающей проверке, но из этой страны гео он открывается, поэтому автопауза не
поставлена и трафик продолжается (правило агрегации стран, INOTRACK_CAPS_HEALTHCHECK_EDGE_COUNTRIES).
offer.down с confirmed: true приходит один раз на подтверждённую серию провалов, а не на
каждую проверку: без автопаузы (autopause_on_broken выключен) — на первой подтверждённой
проверке, с автопаузой — при её постановке, то есть раз в срок паузы
(INOTRACK_CAPS_BROKEN_TTL_MIN), если сайт лежит дольше. Серия обрывается, когда страница
снова открывается; истечение паузы и смена настройки автопаузы начинают отсчёт заново.
Серии проверки central и проверки с edge-ноды страны оффера считаются отдельно.
Очередь доставки — персистентная (переживает рестарт процесса): при ошибке доставки —
экспоненциальный бэкофф (5с, 10с, 20с... с потолком в 1 час) до max_attempts (фиксировано
8 попыток, полем API не настраивается), затем статус failed. Каждая попытка видна в
GET /api/v1/webhooks/{id}/deliveries (код ответа, ошибка, число попыток).
Подпись тела — заголовок:
X-Inotrack-Signature: sha256=<hex hmac-sha256(secret, тело_запроса)>
Сверяйте константным сравнением (hmac.Equal), а не строковым ==.
MCP-сервер (работа с трекером из Claude Code)
Отдельная программа inotrack-mcp поверх этого же публичного API — своей БД или Redis не
трогает, только HTTP до /api/v1. Запускается на вашей машине рядом с Claude Code, а не на
сервере трекера. Готовый бинарь для вашей ОС запросите в поддержке.
Переменные окружения: INOTRACK_API_TOKEN (обязательна, рекомендуется отдельный токен со
scope mcp+read, и write только если нужны write-инструменты) и INOTRACK_API_URL
(дефолт http://localhost:8081/api/v1).
Подключение:
claude mcp add inotrack \
--env INOTRACK_API_URL=https://<трекер>/api/v1 \
--env INOTRACK_API_TOKEN=<токен> \
-- /путь/до/bin/inotrack-mcp
Инструменты чтения
report_query, list_offers, list_sites, list_flows, get_anomalies,
get_finance_summary, alerts_feed, raw_clicks, site_consoles, site_guard_rules,
list_rank_projects, list_rank_keywords, rank_keyword_history, rank_regions.
alerts_feed— лента алертов из колокольчика панели (90 дней): фильтр по типу (kind), только непрочитанные, курсор страниц. Отметка прочитанным —mark_read(id пунктов) илиmark_all_read(все, можно сkindиthrough_id); прочитанность личная, для отметки токену нужен scopewrite. Баер видит алерты своих потоков и их офферов, как в панели.raw_clicks— сырые клики с фильтрами отчёта (filters,date_from/date_to, статус клика), до 500 на страницу и курсор. Баер — только клики своих потоков. IP — не больше, чем разрешает настройка приватности трекера (подсеть и короткий хэш, только хэш или ничего); аргументip_modeможет её только ужесточить.site_consoles— статус подключения поисковых консолей сайта (GSC / Яндекс.Вебмастер):connected,property,has_secrets/secret_mask(секреты не читаются),last_sync_at/last_error, плюсseo_last_import_atиrows_30dсайта.list_rank_projects/list_rank_keywords/rank_keyword_history/rank_regions— проект позиций сайта, его фразы с текущими позициями, история фразы и справочник регионов движка (код региона — Яндексlr, Googlegl).site_guard_rules— правила защиты сайта от скрейпинга (клоаки): условия (UA/CIDR/ASN/ referer/geo/вердикт бот-детектора), действие и приоритет проверки;include_globalдобавляет глобальные правила. Непустой список (или заглушка/guard_actionсайта) закрывает пункт чек-листа «guard».
Инструменты записи
pause_offer, resume_offer, set_weights, blacklist_add, blacklist_remove, bulk_update,
site_console_connect, site_console_disconnect, site_console_import, create_rank_project,
add_rank_keywords, delete_rank_keywords, site_guard_rule_add, site_guard_rule_remove,
site_stub_set. Требуют явного
"confirm": true в аргументах — без него сервер не выполняет действие, а возвращает
текст с описанием последствий (не ошибка вызова, а осознанный отказ). Повторите вызов с
confirm: true, чтобы выполнить. Все write-вызовы идут тем же токеном через публичный
API, поэтому запись в audit log делает сам трекер (актёр вида
api-token:<имя токена>) — отдельного аудита в MCP-сервере нет и не нужно.
bulk_update — пауза или включение сразу многих офферов, лендов или источников (до 500 id,
одна транзакция: меняются все или ни одна). Исключение — ответ cache_unavailable: изменения
уже сохранены, но потоки не перепубликованы; повторите тот же вызов с confirm: true. Без confirm: true (или с dry_run: true) трекер
прогоняет пакет с откатом, и инструмент показывает, что изменится: сколько записей, какие
именно и сколько потоков будет перепубликовано. С confirm: true пакет выполняется, каждая
изменённая запись попадает в audit log. Права — как у массовых операций в панели: viewer не
может, баер — только свои записи (чужая или командная запись в пакете отменяет весь пакет).
site_console_connect/site_console_disconnect/site_console_import — подключение, отключение
и ручной импорт поисковой консоли сайта (GSC / Яндекс.Вебмастер). Секреты (access_token,
service_account_json) только записываются — ни этот, ни другой инструмент их не читает
обратно; ответ показывает лишь has_secrets и маску.
create_rank_project — создать проект позиций сайта (пустые name/domain подставляются из
сайта). add_rank_keywords — пакетное добавление фраз (до 1000 за вызов с учётом поисковиков,
каждая фраза заводится в каждом указанном engines; дубли пропускаются — skipped).
delete_rank_keywords — пакетное удаление: ровно один из ids/phrases/all: true.
site_guard_rule_add/site_guard_rule_remove — создать или удалить правило защиты сайта
(scope/site_id подставляются автоматически); правило без единого условия требует
allow_wildcard: true, иначе отказ wildcard_not_confirmed. Действует на клик-пути сразу
(кэш обновляется синхронно), а не после фонового тика. site_stub_set — загрузить заглушку
сайта и/или включить её (enable: true выставляет guard_action=stub); только роль admin.
Известное ограничение MCP
report_query/get_anomalies (через модуль отчётов) и get_finance_summary (через
финансовый модуль) вернут isError: true с текстом о недоступном модуле, если в вашей
установке этот модуль недоступен (API отвечает 503 module_unavailable) — это ожидаемое
поведение, не ошибка MCP-сервера.
Любой отказ API (401, 403, 404, 409, 415, 5xx) инструмент возвращает как isError: true со
статусом и причиной (у 5xx без message — только статус: текст error от модуля
не показывается); токен из INOTRACK_API_TOKEN в текст ошибки не попадает, даже если ответ его повторяет.
Дальше
Обслуживание инстанса (бэкапы, обновления, доктор) — Обслуживание инстанса. Честные ответы на вопросы про надёжность — FAQ.
Импорт каталога сети: снятие сетевой паузы
При network_status=active импорт возвращает status=active только существующему
офферу на сетевой паузе: network_paused=true либо последняя смена статуса в
audit_log — offer.network_paused / offer.network_missing. Последующая ручная
смена статуса (включая повторную ручную паузу) запрещает автоматический возврат.
Активные ручные блокировки, capped, broken и паузы, поставленные сетью поверх
capped, сохраняются. Новые офферы создаются в paused: их включает оператор после
проверки маркировки и условий.
Возврат перепубликует потоки и пишет offer.network_reactivated с before/after и
причиной «сеть вернула оффер». Отчёт содержит network_reactivated (поле Reactivated)
со статусом active; панель показывает «Возвращены сетью: N». Отсутствие erid само
по себе не запрещает трафик: такой возврат отмечается «без erid» в отчёте и
without_erid=true в аудите. Предпросмотр рассчитывает те же изменения без записи.
Дерево вертикалей
Вертикали образуют дерево до 6 уровней с едиными латинскими кодами:
finance → banks → credit_cards. Старые коды и алиасы (мсб → sme,
рефинансирование → refinancing) принимаются API и приводятся к канону.
Фильтр ?vertical=<узел> у офферов, сайтов и потоков включает сам узел и всех
потомков; ?subvertical= у офферов выбирает строго указанный узел.
Справочники /verticals и /dictionaries/verticals возвращают parent_code,
path (коды от корня) и depth (корень — 1). PATCH /verticals/{code} меняет
parent_code и sort; циклы и глубина свыше 6 отклоняются.
DELETE требует replace_with для используемого узла; если есть только дети,
children=to_parent переносит их к родителю удаляемого узла (у корня — в корни).
Доп. поля, привязанные к узлу, действуют во всём поддереве. В потоках vertical
хранит корень, subvertical — выбранный узел любой глубины; автоподбор принимает
узлы любой глубины и включает их потомков. Формат условий Белки сохраняется.
В универсальном отчёте vertical группирует офферы по корню дерева,
subvertical — по второму уровню, а vertical_node — по точному узлу оффера.
Подпись vertical_node содержит полный путь, например «Финансы / Банки /
Кредитные карты». Фильтры eq/in этих измерений включают выбранные узлы
и их потомков; neq/not_in исключают соответствующие поддеревья.
Прогноз выплат в универсальном отчёте
POST /api/v1/reports/query принимает новые ключи в metrics:
| Ключ | Значение |
|---|---|
payout_pending |
Сумма выплат действующих pending-конверсий в валюте отчёта. |
approve_rate |
Апрув оффера: approved / (approved + rejected) × 100 за последние 30 дней от момента запроса. Без решённых — 100%. |
payout_forecast |
payout + сумма по офферам (payout_pending оффера × его апрув / 100). |
profit_forecast |
payout_forecast - cost. |
roi_forecast |
profit_forecast / cost × 100; при нулевом расходе — null (в панели «—»). |
Апрув считается по действующим решениям транзакций (clickid, txid) независимо от
дат, измерений и фильтров отчёта. В срезе нескольких офферов колонка approve_rate
показывает среднее их ставок, взвешенное числом действующих конверсий среза;
прогноз выплаты всегда использует отдельную ставку каждого оффера, а не среднюю.
При отсутствии конверсий справочный апрув — 100%.
Тестовые конверсии исключены; ручные исключения применяются и к истории апрува
(кроме явно запрошенного include_excluded=true, как у payout).
Pending после reject/approve не учитывается. Действующая approved-строка попадает
в существующую метрику payout. Финансы, P&L и формулы payout/profit/roi
не меняются: новые показатели — прогноз, а не полученные деньги.
Пример тела запроса:
{"dimensions":["source_id"],"metrics":["payout","payout_pending","approve_rate","payout_forecast","profit_forecast","roi_forecast"],"date_from":"2026-10-27","date_to":"2026-10-27","currency":"RUB","timezone":"Europe/Moscow"}
Расход из ссылки источника
В POST /api/v1/sources, PATCH /api/v1/sources/{id} и ответах источника доступны:
cost_param: имя query-параметра входящего клика с ценой сети, напримерcostилиcpc. По умолчанию пусто (выключено).cost_currency:USD(по умолчанию),RUBилиEUR.cost_mode:cpc(по умолчанию, цена клика) илиcpm(значение делится на 1000).
Пример: источник с cost_param=cost, cost_currency=USD, cost_mode=cpc, ссылка
/go/TOKEN?cost=0.0025&sub4=zone123. Каждый клик, включая повторный, получает свою цену.
Пустые, отрицательные, бесконечные и нечисловые значения считаются нулём и не мешают переходу.
Для POST /api/v1/clicks цена передаётся в query URL либо в поле тела
"query":{"cost":["0.0025"]}; query URL имеет приоритет. Цена не наследуется от родительского клика.
Цена сохраняется с валютой и пересчитывается в RUB/USD/EUR по курсу трекера на дату клика. Если курса нет, сохраняются исходная цена и доступные пересчёты; недоступные пересчёты равны нулю.
В универсальном отчёте cost складывает цены кликов и расходы кабинетов остальных источников.
Если у источника заполнен cost_param, расходы его кабинета и ручные расходы не учитываются:
приоритет действует для всего запрошенного периода, даже если параметр отдельного клика отсутствует
или содержит мусор. Это исключает двойной учёт. Изменение настройки влияет на выбор расходов
кабинета в исторических отчётах; уже записанные цены кликов сохраняются.
Для разрезов, отсутствующих в расходах кабинетов (например sub1–sub10, оффер, регион),
учитываются только цены кликов: распределять расходы кабинета по зонам без данных нельзя.
Запрос dimensions: ["source_id","sub4"] поддерживает cost, profit, roi,
profit_forecast и roi_forecast. Фильтры ботов и тестовых кликов применяются как к остальным
метрикам кликов. Для потоков и сайтов дополнительно учитываются расходы кабинетов с привязкой кампаний.