Roistat Reports API
Overview
Навык для работы с отчетами Roistat только через API.
Он нужен, когда надо:
- снять список существующих отчетов в кабинете;
- собрать новый отчет с нуля, не редактируя старый;
- выгрузить отчет по атрибуциям, новым/повторным заявкам и продажам;
- проверить, не врет ли отчет из-за поздних CRM-событий;
- сохранить reproducible report-pack локально в файлы.
Path Contract
<plugin-root> = корень этого bundle, где лежат .codex-plugin/plugin.json, skills/, mcp/, scripts/.
- Repo-local пример:
./plugins/yandex-direct-for-all
- Home-compatible install пример:
~/.codex/plugins/yandex-direct-for-all или ~/.claude/plugins/yandex-direct-for-all
- Команды ниже должны использовать
<plugin-root>/..., а не ~/.codex/skills/....
Когда использовать
Используй этот навык, если задача относится к одному из сценариев:
- пользователь просит работать с отчетами
Roistat без UI;
- нужно понять, какие отчеты уже есть в кабинете и как они устроены;
- нужно собрать новый отчет через
analytics/data;
- нужно сравнить
default / first_click / last_click / last_paid_click;
- нужно выделить
leads, first_leads, repeated_leads, sales, new_sales, repeated_sales, clients, paid_clients;
- нужно выгрузить Excel-отчет через API;
- нужно сверить отчет с сырыми сделками через
integration/order/list.
Правила
- Никакого UI.
- Не редактируй существующие отчеты, если пользователь явно не попросил это отдельно.
POST /project/analytics/reports используй как discovery-layer для чтения уже сохраненных отчетов.
- Публично документированный канон для нового отчета:
POST /project/analytics/data и при необходимости POST /project/analytics/data/export/excel.
- Для saved report внутри кабинета используй только подтвержденный контракт:
POST /project/analytics/report с телом {"report": {...}}.
- Создание нового saved report: передай
report без id.
- Обновление уже созданного saved report: передай тот же объект с
report.id.
- Этот write-контракт подтвержден live на проекте
192319 2026-03-16. Не подменяй им существующие отчеты: создавай новый объект с уникальным title, затем отдельно обновляй только его.
- Для проверки “реально ли заявка относится к визиту источника в этом окне” не ограничивайся агрегатом: добавляй сверку через
integration/order/list c extend=["visit"].
- Для директорских отчетов все кастомные столбцы называй человеко-понятно на русском и в каждом заголовке явно указывай источник логики:
Roistat, системный расчет, CRM, ручная отметка менеджера, Диагностика расхождений.
- Не смешивай в одном заголовке технический жаргон и английские сокращения, если это можно объяснить простым русским названием.
- Если в отчете есть кастомные обертки над built-in метриками, убирай старые дубль-колонки без source-label, чтобы в интерфейсе не было смысловых повторов.
- Если пользователь просит “сделать колонки шире”, сначала проверь live saved-report JSON.
На проекте
192319 2026-03-16 в saved-report контракте не подтвердились поля ширины колонок или table layout.
Пока не найден отдельный hidden-contract, не обещай менять ширину колонок через API.
- Для lead-слоя всегда отдельно проверяй late CRM events: часть поздних неоплаченных сделок может быть привязана к более старым визитам и тогда
integration/order/list даст больше строк, чем analytics/data. Это не обязательно ошибка формулы; это может быть особенность движка Roistat.
- Если пользователь просит “разные атрибуции рядом”, не считай это выполненным, пока не проверишь live-значения через
analytics/data.
На проекте 192319 2026-03-16 подтвержден кейс, где saved-report принимал разные attributionModel, но значения в результатах оставались одинаковыми.
В таком случае:
- не оставляй ложные атрибуционные дубли в боевом отчете;
- честно фиксируй, что текущий тип отчета не дает рабочей развилки по атрибуции для этой задачи;
- не маскируй проблему переименованием колонок.
- Для built-in метрик мультиканального отчета на проекте
192319 2026-03-16 saved-report контракт подтвержден как
{"id","attributionModel","isAvailable"} без отдельного поля title или label на уровне колонки.
Следствие:
- через API нельзя честно переписать названия самих built-in атрибуционных колонок;
- если пользователю нужно объяснение моделей, давай легенду в названии отчета, в сопроводительном тексте или в отдельном поясняющем артефакте;
- не обещай “подписать каждую модель в названии столбца”, пока не найдешь live-контракт с editable label.
- Не пытайся обойти это ограничение кастомными обертками над built-in метриками без live-проверки.
На проекте
192319 2026-03-16 analytics/data подтвердил, что кастомные формульные обертки вроде {leads} и {first_leads} игнорировали attribution и возвращали значения стандартной модели.
Значит такие обертки годятся для human-readable названий только в одноатрибуционных отчетах, но не для честной мультиатрибуционной таблицы.
- Не обещай, что перенос в “Мультиканальную аналитику” автоматически исправит атрибуцию.
Сначала сними live saved-report мультиканального отчета и сравни контракт/результаты.
На проекте
192319 2026-03-16 мультиканальный отчет тоже имел date_filter_type=lead и не давал автоматического решения проблемы “старый визит -> поздняя CRM-реактивация”.
Для проверки продаж, которые попали в выбранный период по оплате, но были созданы раньше, отдельный safe-path на этом проекте:
- создать отдельный saved report в мультиканальной аналитике с
date_filter_type=payment;
- на проекте
192319 такой отчет был создан 2026-03-16 как id=38.
- Для любых кастомных метрик с
% в названии или долевой формулой проверяй type.
На проекте 192319 2026-03-16 кастомные метрики 80 и 81 были созданы как integer, хотя по смыслу были процентами.
Канон:
- найди все метрики с
%, доля, конверсия в названии или с долевой формулой;
- сверь
type через metrics/custom/list и при необходимости исправь на percent;
- затем перечитай тип назад из API, а не полагайся на успешный update-ответ.
3 уровня валидации
Для любого боевого saved report по Roistat используй именно трехуровневую валидацию:
- Формула.
Кастомная метрика должна существовать в проекте, читаться через API и иметь ожидаемый
title / type / formula.
- Агрегат.
analytics/data должен вернуть значения по нужному окну и срезам (search / context или глубже), а итоговые цифры должны быть сохранены в артефакт.
- Сырые сделки.
integration/order/list c extend=["visit"] должен подтвердить продажи и выручку напрямую, а по lead-слою нужно отдельно фиксировать, где raw-выборка расходится с analytics/data из-за старых визитов, reactivate-сценариев или других особенностей привязки.
- Формат и интерфейс.
Для боевого директорского отчета отдельно проверь:
- что процентные столбцы имеют
type=percent;
- что названия колонок не содержат ложных маркеров вроде “пользовательский”, если это мешает чтению;
- что одинаковые по названию колонки не скрывают разные модели или, наоборот, ложные дубли.
Truth Layer и автоматизация
Если задача не просто “показать стандартные лиды/продажи”, а построить честный директорский слой реально новый / реально старый / продажа из старого визита, используй такой канон:
- Не считай ручные поля CRM (
new_lead, Повторный клиент) истиной.
Они могут быть полезны только как диагностические сигналы.
- Сначала собери
identity coverage audit:
client_id
visit_id
roistat
_ym_uid
ym_client_id
Телефон
Доп. Телефон
ФИО
- любые другие контактные поля проекта
- Построй factual-history слой:
had_prior_lead
had_prior_paid_sale
had_prior_paid_sale_gt_2000
visit_older_than_30d / 90d / 180d
- Отдельно разделяй:
Roistat, системный расчет
CRM, ручная отметка менеджера
Факт по истории
Атрибуция старого визита
Что можно автоматизировать в самом Roistat
Публично документировано:
- список ручных показателей:
POST /project/analytics/metrics/custom/manual/list
- заливка значений ручного показателя:
POST /project/analytics/metrics/custom/manual/value/add
- просмотр значений:
POST /project/analytics/metrics/custom/manual/value/list
- удаление значения:
POST /project/analytics/metrics/custom/manual/value/delete
Это означает:
- Да, director truth-layer можно автоматически обновлять в
Roistat.
- Но считать его должен внешний job, а не сам движок
Roistat.
- Job должен по расписанию:
- вытянуть сырые сделки и историю;
- посчитать factual-метрики;
- залить агрегированные значения в ручные показатели;
- при необходимости поверх ручных показателей использовать формульные показатели через
manual_custom_N.
Ограничения автоматизации
- Публичная документация API описывает заливку значений ручных показателей, но не описывает явный API-метод создания самих ручных показателей.
- Live подтвержден hidden contract:
POST /project/analytics/metrics/custom/manual/create
POST /project/analytics/metrics/custom/manual/update
POST /project/analytics/metrics/custom/manual/delete
- Для формульных показателей подтверждены:
POST /project/analytics/metrics/custom/create
POST /project/analytics/metrics/custom/update
POST /project/analytics/metrics/custom/delete
manual/value/add не допускает пересекающиеся периоды для одного source и одного manual metric.
- Поэтому перед перезаписью rolling-window нужно сначала удалить старое значение за этот же период.
Важное ограничение по уровням источника
Ручное значение в Roistat привязывается к конкретному source и уровню источника.
Оно не распределяется автоматически на дочерние кампании.
Следствие:
- если нужен только верхний слой, можно писать значения на
direct1_search и direct1_context;
- если нужна truth-аналитика по кампаниям, внешний job обязан считать и писать значения по каждому
source отдельно.
- Live отдельно подтверждено:
manual/value/add принимает не только marker_level_1 или marker_level_3;
- можно писать значение по полному
visit.source.system_name, то есть вплоть до marker_level_6;
- после этого
analytics/data корректно разворачивает его по marker_level_4..6.
- Для источников без полноценного
visit.source можно писать и в marker_level_1-источники вроде telegram или nosource-crm, если они существуют в аналитическом дереве проекта.
Практическая стратегия записи
- Если нужен именно rolling-отчет “последние 30 дней”, fastest safe path: писать одно значение на весь 30-дневный период по каждому
source.
- Если нужен корректный пересчет для произвольных дат внутри кабинета, truth-layer нужно писать по дням.
- Суточная запись правильнее, но заметно тяжелее по API и быстрее упирается в
request_limit_error.
- Для large-scale sync по всем каналам и полной глубине закладывай:
- rate-limit backoff;
- ограничение числа параллельных запросов;
- фоновый job, а не интерактивный ручной прогон.
Workflow
1. Подтверди канон в документации
Перед live API-вызовами проверь официальные материалы:
API/methods/analytics
features/Analitika_i_otchety/Analitika/Postroenie_otchetov
features/Analitika_i_otchety/Polzovatelskie_pokazateli
features/Analitika_i_otchety/Specialnye_otchety/Novie_klienti
Краткий навигатор лежит в:
- references/reporting-endpoints.md
2. Сними discovery-layer кабинета
Сначала вытащи:
- список сохраненных отчетов:
POST /project/analytics/reports
- список кастомных метрик:
GET /project/analytics/metrics/custom/list
- список доступных CRM-полей заказа:
POST /project/analytics/order-custom-fields
- список моделей атрибуции:
POST /project/analytics/attribution-models
Это не “новый отчет”, а слой для понимания, какие метрики и формулы реально доступны в проекте.
Важно:
order-custom-fields подтверждает, какие поля реально приходят в проект из CRM, но сам по себе не отдает безопасный маппинг в order_field_N;
- в боевой отчет добавляй только те
order_field_N, чей маппинг доказан live:
- либо уже существующей формулой проекта;
- либо сверкой с raw
integration/order/list;
- если название поля CRM известно, а номер
order_field_N не доказан, не добавляй такую метрику в директорский отчет “на глаз”.
3. Собери новый отчет с нуля
Новый отчет строится отдельной JSON-спекой, а не правкой существующего отчета.
Базовые измерения по умолчанию:
marker_level_1
marker_level_2
marker_level_3
Это safe default для боевого проекта: полный срез до marker_level_6 часто упирается в Too many data.
Глубокий drill-down по marker_level_4..6 делай отдельной второй волной после успешного кампанийного среза.
Базовые метрики по умолчанию:
marketing_cost
visits
unique_visits
leads
first_leads
repeated_leads
sales
new_sales
repeated_sales
revenue
first_sales_revenue
repeated_sales_revenue
clients
paid_clients
cpl
cpo
cac
ltv
conversion_visits_to_leads
conversion_leads_to_sales
Если в проекте есть полезные кастомные метрики, добавляй их явно как custom_<id>.
4. Сними атрибуционные срезы
Если пользователь просит “все атрибуции”, не делай один мутный агрегат.
Добавляй отдельные значения для нужных моделей:
default
first_click
last_click
last_paid_click
Обычно имеет смысл дублировать по моделям как минимум:
leads
sales
revenue
clients
paid_clients
5. Проверь агрегат сделками
Для проверки проблем типа “в отчете в этом месяце всплывают старые лиды” собери отдельный audit-слой:
POST /project/integration/order/list
- фильтр по
creation_date
extend=["visit"]
Дальше проверь:
- дата создания сделки;
roistat визита;
- источник/маркеры визита;
- статус;
- выручку;
- повторность, если она кодируется полем/тегом/кастомной метрикой проекта.
6. При необходимости сохрани отчет в кабинет
После того как report-pack локально собран и проверен, saved report в кабинете сохраняется отдельным шагом:
- загрузи
new_report_spec.json;
- проверь, что
title уникален;
- отправь
POST /project/analytics/report с телом {"report": <spec>};
- затем перечитай
POST /project/analytics/reports и убедись, что появился новый id, а fingerprint остальных отчетов не изменился.
Для update уже созданного отчета:
- перечитай сохраненный объект;
- меняй только
title и settings нужного id;
- повторно отправляй
POST /project/analytics/report c {"report": <saved_report_with_id>}.
7. Сохрани report-pack
Итоговый pack должен содержать:
- snapshot сохраненных отчетов;
- snapshot кастомных метрик;
- новую JSON-спеку отчета;
- request body для
analytics/data;
- raw JSON ответа;
- flat TSV;
- Excel-экспорт через API;
- order-audit raw + flat TSV;
- короткое
summary.md.
Скрипты
scripts/build_roistat_report_pack.py
Главный reusable path.
Он:
- читает saved reports и кастомные метрики;
- собирает новую report-spec с нуля;
- вызывает
analytics/data;
- сохраняет TSV и raw JSON;
- по желанию тянет
export/excel;
- снимает order audit через
integration/order/list.
Минимальный запуск:
python3 <plugin-root>/skills/roistat-reports-api/scripts/build_roistat_report_pack.py \
--project 192319 \
--api-key-env ROISTAT_API_KEY \
--from 2026-03-01 \
--to 2026-03-16 \
--report-name "API | Direct attribution MTD | new vs repeat" \
--marker-level-1 direct1 \
--marker-level-1 direct9 \
--marker-level-1 direct10 \
--marker-level-1 direct11 \
--marker-level-1 direct13 \
--output-dir ./output/roistat_reports/direct-attribution-mtd-20260316
scripts/save_roistat_report.py
Reusable скрипт для create/update saved report внутри кабинета через API.
Он автоматически нормализует saved-report фильтры под формат кабинета:
- для
operator="in" не оставляет строковые массивы;
- для source-like значений подтягивает
label через project/analytics/source/list;
- добавляет безопасные служебные поля, которые ожидаются живыми отчетами.
Пример create из уже собранной спеки:
python3 <plugin-root>/skills/roistat-reports-api/scripts/save_roistat_report.py \
--project 192319 \
--api-key-env ROISTAT_API_KEY \
--report-spec ./output/roistat_reports/direct-attribution-mtd-20260316-v3/new_report_spec.json
Пример update уже созданного отчета:
python3 <plugin-root>/skills/roistat-reports-api/scripts/save_roistat_report.py \
--project 192319 \
--api-key-env ROISTAT_API_KEY \
--report-spec ./output/roistat_reports/direct-attribution-mtd-20260316-v3/new_report_spec.json \
--report-id 36 \
--title "API | Директ | Атрибуция MTD | новые/повторные"
Что скрипты не делают
- не меняет существующие отчеты;
- не трогает настройки проекта;
- не пишет новые кастомные метрики.
Выбор стратегии
Если задача пользователя звучит как:
- “пойми, почему основной отчет врет”,
- “собери новый отчет по новым/повторным лидам”,
- “дай выгрузку по атрибуциям”,
то канонический путь такой:
- discovery saved reports;
- новая report-spec с нуля;
analytics/data;
export/excel;
integration/order/list для верификации.
Если пользователь отдельно требует “сохранить новый отчет прямо в Roistat”, сначала исследуй write-endpoint и зафиксируй контракт в skill/reference, а уже потом выполняй запись.
1---2name: roistat-reports-api3description: Use when the task is to inspect existing Roistat saved reports through API, assemble a new report from scratch via analytics/data, fetch attribution slices, split new vs repeated leads/sales/clients, validate report logic against orders, or export a reproducible report pack without using the UI.4---56# Roistat Reports API78## Overview910Навык для работы с отчетами `Roistat` только через API.11Он нужен, когда надо:1213- снять список существующих отчетов в кабинете;14- собрать новый отчет с нуля, не редактируя старый;15- выгрузить отчет по атрибуциям, новым/повторным заявкам и продажам;16- проверить, не врет ли отчет из-за поздних CRM-событий;17- сохранить reproducible report-pack локально в файлы.1819## Path Contract2021- `<plugin-root>` = корень этого bundle, где лежат `.codex-plugin/plugin.json`, `skills/`, `mcp/`, `scripts/`.22- Repo-local пример: `./plugins/yandex-direct-for-all`23- Home-compatible install пример: `~/.codex/plugins/yandex-direct-for-all` или `~/.claude/plugins/yandex-direct-for-all`24- Команды ниже должны использовать `<plugin-root>/...`, а не `~/.codex/skills/...`.2526## Когда использовать2728Используй этот навык, если задача относится к одному из сценариев:2930- пользователь просит работать с отчетами `Roistat` без UI;31- нужно понять, какие отчеты уже есть в кабинете и как они устроены;32- нужно собрать новый отчет через `analytics/data`;33- нужно сравнить `default / first_click / last_click / last_paid_click`;34- нужно выделить `leads`, `first_leads`, `repeated_leads`, `sales`, `new_sales`, `repeated_sales`, `clients`, `paid_clients`;35- нужно выгрузить Excel-отчет через API;36- нужно сверить отчет с сырыми сделками через `integration/order/list`.3738## Правила3940- Никакого UI.41- Не редактируй существующие отчеты, если пользователь явно не попросил это отдельно.42- `POST /project/analytics/reports` используй как discovery-layer для чтения уже сохраненных отчетов.43- Публично документированный канон для нового отчета: `POST /project/analytics/data` и при необходимости `POST /project/analytics/data/export/excel`.44- Для saved report внутри кабинета используй только подтвержденный контракт:45 `POST /project/analytics/report` с телом `{"report": {...}}`.46- Создание нового saved report: передай `report` без `id`.47- Обновление уже созданного saved report: передай тот же объект с `report.id`.48- Этот write-контракт подтвержден live на проекте `192319` 2026-03-16. Не подменяй им существующие отчеты: создавай новый объект с уникальным `title`, затем отдельно обновляй только его.49- Для проверки “реально ли заявка относится к визиту источника в этом окне” не ограничивайся агрегатом: добавляй сверку через `integration/order/list` c `extend=["visit"]`.50- Для директорских отчетов все кастомные столбцы называй человеко-понятно на русском и в каждом заголовке явно указывай источник логики:51 `Roistat, системный расчет`, `CRM, ручная отметка менеджера`, `Диагностика расхождений`.52- Не смешивай в одном заголовке технический жаргон и английские сокращения, если это можно объяснить простым русским названием.53- Если в отчете есть кастомные обертки над built-in метриками, убирай старые дубль-колонки без source-label, чтобы в интерфейсе не было смысловых повторов.54- Если пользователь просит “сделать колонки шире”, сначала проверь live saved-report JSON.55 На проекте `192319` 2026-03-16 в saved-report контракте не подтвердились поля ширины колонок или table layout.56 Пока не найден отдельный hidden-contract, не обещай менять ширину колонок через API.57- Для lead-слоя всегда отдельно проверяй late CRM events: часть поздних неоплаченных сделок может быть привязана к более старым визитам и тогда `integration/order/list` даст больше строк, чем `analytics/data`. Это не обязательно ошибка формулы; это может быть особенность движка Roistat.58- Если пользователь просит “разные атрибуции рядом”, не считай это выполненным, пока не проверишь live-значения через `analytics/data`.59 На проекте `192319` 2026-03-16 подтвержден кейс, где saved-report принимал разные `attributionModel`, но значения в результатах оставались одинаковыми.60 В таком случае:61 - не оставляй ложные атрибуционные дубли в боевом отчете;62 - честно фиксируй, что текущий тип отчета не дает рабочей развилки по атрибуции для этой задачи;63 - не маскируй проблему переименованием колонок.64- Для built-in метрик мультиканального отчета на проекте `192319` 2026-03-16 saved-report контракт подтвержден как65 `{"id","attributionModel","isAvailable"}` без отдельного поля `title` или `label` на уровне колонки.66 Следствие:67 - через API нельзя честно переписать названия самих built-in атрибуционных колонок;68 - если пользователю нужно объяснение моделей, давай легенду в названии отчета, в сопроводительном тексте или в отдельном поясняющем артефакте;69 - не обещай “подписать каждую модель в названии столбца”, пока не найдешь live-контракт с editable label.70- Не пытайся обойти это ограничение кастомными обертками над built-in метриками без live-проверки.71 На проекте `192319` 2026-03-16 `analytics/data` подтвердил, что кастомные формульные обертки вроде `{leads}` и `{first_leads}` игнорировали `attribution` и возвращали значения стандартной модели.72 Значит такие обертки годятся для human-readable названий только в одноатрибуционных отчетах, но не для честной мультиатрибуционной таблицы.73- Не обещай, что перенос в “Мультиканальную аналитику” автоматически исправит атрибуцию.74 Сначала сними live saved-report мультиканального отчета и сравни контракт/результаты.75 На проекте `192319` 2026-03-16 мультиканальный отчет тоже имел `date_filter_type=lead` и не давал автоматического решения проблемы “старый визит -> поздняя CRM-реактивация”.76 Для проверки продаж, которые попали в выбранный период по оплате, но были созданы раньше, отдельный safe-path на этом проекте:77 - создать отдельный saved report в мультиканальной аналитике с `date_filter_type=payment`;78 - на проекте `192319` такой отчет был создан 2026-03-16 как `id=38`.79- Для любых кастомных метрик с `%` в названии или долевой формулой проверяй `type`.80 На проекте `192319` 2026-03-16 кастомные метрики `80` и `81` были созданы как `integer`, хотя по смыслу были процентами.81 Канон:82 - найди все метрики с `%`, `доля`, `конверсия` в названии или с долевой формулой;83 - сверь `type` через `metrics/custom/list` и при необходимости исправь на `percent`;84 - затем перечитай тип назад из API, а не полагайся на успешный update-ответ.8586## 3 уровня валидации8788Для любого боевого saved report по `Roistat` используй именно трехуровневую валидацию:89901. Формула.91 Кастомная метрика должна существовать в проекте, читаться через API и иметь ожидаемый `title / type / formula`.922. Агрегат.93 `analytics/data` должен вернуть значения по нужному окну и срезам (`search / context` или глубже), а итоговые цифры должны быть сохранены в артефакт.943. Сырые сделки.95 `integration/order/list` c `extend=["visit"]` должен подтвердить продажи и выручку напрямую, а по lead-слою нужно отдельно фиксировать, где raw-выборка расходится с `analytics/data` из-за старых визитов, reactivate-сценариев или других особенностей привязки.964. Формат и интерфейс.97 Для боевого директорского отчета отдельно проверь:98 - что процентные столбцы имеют `type=percent`;99 - что названия колонок не содержат ложных маркеров вроде “пользовательский”, если это мешает чтению;100 - что одинаковые по названию колонки не скрывают разные модели или, наоборот, ложные дубли.101102## Truth Layer и автоматизация103104Если задача не просто “показать стандартные лиды/продажи”, а построить честный директорский слой `реально новый / реально старый / продажа из старого визита`, используй такой канон:1051061. Не считай ручные поля CRM (`new_lead`, `Повторный клиент`) истиной.107 Они могут быть полезны только как диагностические сигналы.1082. Сначала собери `identity coverage audit`:109 - `client_id`110 - `visit_id`111 - `roistat`112 - `_ym_uid`113 - `ym_client_id`114 - `Телефон`115 - `Доп. Телефон`116 - `ФИО`117 - любые другие контактные поля проекта1183. Построй factual-history слой:119 - `had_prior_lead`120 - `had_prior_paid_sale`121 - `had_prior_paid_sale_gt_2000`122 - `visit_older_than_30d / 90d / 180d`1234. Отдельно разделяй:124 - `Roistat, системный расчет`125 - `CRM, ручная отметка менеджера`126 - `Факт по истории`127 - `Атрибуция старого визита`128129### Что можно автоматизировать в самом Roistat130131Публично документировано:132133- список ручных показателей: `POST /project/analytics/metrics/custom/manual/list`134- заливка значений ручного показателя: `POST /project/analytics/metrics/custom/manual/value/add`135- просмотр значений: `POST /project/analytics/metrics/custom/manual/value/list`136- удаление значения: `POST /project/analytics/metrics/custom/manual/value/delete`137138Это означает:139140- Да, director truth-layer можно автоматически обновлять в `Roistat`.141- Но считать его должен внешний job, а не сам движок `Roistat`.142- Job должен по расписанию:143 1. вытянуть сырые сделки и историю;144 2. посчитать factual-метрики;145 3. залить агрегированные значения в ручные показатели;146 4. при необходимости поверх ручных показателей использовать формульные показатели через `manual_custom_N`.147148### Ограничения автоматизации149150- Публичная документация API описывает заливку значений ручных показателей, но не описывает явный API-метод создания самих ручных показателей.151- Live подтвержден hidden contract:152 - `POST /project/analytics/metrics/custom/manual/create`153 - `POST /project/analytics/metrics/custom/manual/update`154 - `POST /project/analytics/metrics/custom/manual/delete`155- Для формульных показателей подтверждены:156 - `POST /project/analytics/metrics/custom/create`157 - `POST /project/analytics/metrics/custom/update`158 - `POST /project/analytics/metrics/custom/delete`159- `manual/value/add` не допускает пересекающиеся периоды для одного `source` и одного manual metric.160- Поэтому перед перезаписью rolling-window нужно сначала удалить старое значение за этот же период.161162### Важное ограничение по уровням источника163164Ручное значение в `Roistat` привязывается к конкретному `source` и уровню источника.165Оно не распределяется автоматически на дочерние кампании.166167Следствие:168169- если нужен только верхний слой, можно писать значения на `direct1_search` и `direct1_context`;170- если нужна truth-аналитика по кампаниям, внешний job обязан считать и писать значения по каждому `source` отдельно.171- Live отдельно подтверждено:172 - `manual/value/add` принимает не только `marker_level_1` или `marker_level_3`;173 - можно писать значение по полному `visit.source.system_name`, то есть вплоть до `marker_level_6`;174 - после этого `analytics/data` корректно разворачивает его по `marker_level_4..6`.175- Для источников без полноценного `visit.source` можно писать и в `marker_level_1`-источники вроде `telegram` или `nosource-crm`, если они существуют в аналитическом дереве проекта.176177### Практическая стратегия записи178179- Если нужен именно rolling-отчет “последние 30 дней”, fastest safe path: писать одно значение на весь 30-дневный период по каждому `source`.180- Если нужен корректный пересчет для произвольных дат внутри кабинета, truth-layer нужно писать по дням.181- Суточная запись правильнее, но заметно тяжелее по API и быстрее упирается в `request_limit_error`.182- Для large-scale sync по всем каналам и полной глубине закладывай:183 - rate-limit backoff;184 - ограничение числа параллельных запросов;185 - фоновый job, а не интерактивный ручной прогон.186187## Workflow188189### 1. Подтверди канон в документации190191Перед live API-вызовами проверь официальные материалы:192193- `API/methods/analytics`194- `features/Analitika_i_otchety/Analitika/Postroenie_otchetov`195- `features/Analitika_i_otchety/Polzovatelskie_pokazateli`196- `features/Analitika_i_otchety/Specialnye_otchety/Novie_klienti`197198Краткий навигатор лежит в:199200- [references/reporting-endpoints.md](references/reporting-endpoints.md)201202### 2. Сними discovery-layer кабинета203204Сначала вытащи:205206- список сохраненных отчетов: `POST /project/analytics/reports`207- список кастомных метрик: `GET /project/analytics/metrics/custom/list`208- список доступных CRM-полей заказа: `POST /project/analytics/order-custom-fields`209- список моделей атрибуции: `POST /project/analytics/attribution-models`210211Это не “новый отчет”, а слой для понимания, какие метрики и формулы реально доступны в проекте.212213Важно:214215- `order-custom-fields` подтверждает, какие поля реально приходят в проект из CRM, но сам по себе не отдает безопасный маппинг в `order_field_N`;216- в боевой отчет добавляй только те `order_field_N`, чей маппинг доказан live:217 - либо уже существующей формулой проекта;218 - либо сверкой с raw `integration/order/list`;219- если название поля CRM известно, а номер `order_field_N` не доказан, не добавляй такую метрику в директорский отчет “на глаз”.220221### 3. Собери новый отчет с нуля222223Новый отчет строится отдельной JSON-спекой, а не правкой существующего отчета.224225Базовые измерения по умолчанию:226227- `marker_level_1`228- `marker_level_2`229- `marker_level_3`230231Это safe default для боевого проекта: полный срез до `marker_level_6` часто упирается в `Too many data`.232Глубокий drill-down по `marker_level_4..6` делай отдельной второй волной после успешного кампанийного среза.233234Базовые метрики по умолчанию:235236- `marketing_cost`237- `visits`238- `unique_visits`239- `leads`240- `first_leads`241- `repeated_leads`242- `sales`243- `new_sales`244- `repeated_sales`245- `revenue`246- `first_sales_revenue`247- `repeated_sales_revenue`248- `clients`249- `paid_clients`250- `cpl`251- `cpo`252- `cac`253- `ltv`254- `conversion_visits_to_leads`255- `conversion_leads_to_sales`256257Если в проекте есть полезные кастомные метрики, добавляй их явно как `custom_<id>`.258259### 4. Сними атрибуционные срезы260261Если пользователь просит “все атрибуции”, не делай один мутный агрегат.262Добавляй отдельные значения для нужных моделей:263264- `default`265- `first_click`266- `last_click`267- `last_paid_click`268269Обычно имеет смысл дублировать по моделям как минимум:270271- `leads`272- `sales`273- `revenue`274- `clients`275- `paid_clients`276277### 5. Проверь агрегат сделками278279Для проверки проблем типа “в отчете в этом месяце всплывают старые лиды” собери отдельный audit-слой:280281- `POST /project/integration/order/list`282- фильтр по `creation_date`283- `extend=["visit"]`284285Дальше проверь:286287- дата создания сделки;288- `roistat` визита;289- источник/маркеры визита;290- статус;291- выручку;292- повторность, если она кодируется полем/тегом/кастомной метрикой проекта.293294### 6. При необходимости сохрани отчет в кабинет295296После того как report-pack локально собран и проверен, saved report в кабинете сохраняется отдельным шагом:297298- загрузи `new_report_spec.json`;299- проверь, что `title` уникален;300- отправь `POST /project/analytics/report` с телом `{"report": <spec>}`;301- затем перечитай `POST /project/analytics/reports` и убедись, что появился новый `id`, а fingerprint остальных отчетов не изменился.302303Для update уже созданного отчета:304305- перечитай сохраненный объект;306- меняй только `title` и `settings` нужного `id`;307- повторно отправляй `POST /project/analytics/report` c `{"report": <saved_report_with_id>}`.308309### 7. Сохрани report-pack310311Итоговый pack должен содержать:312313- snapshot сохраненных отчетов;314- snapshot кастомных метрик;315- новую JSON-спеку отчета;316- request body для `analytics/data`;317- raw JSON ответа;318- flat TSV;319- Excel-экспорт через API;320- order-audit raw + flat TSV;321- короткое `summary.md`.322323## Скрипты324325### `scripts/build_roistat_report_pack.py`326327Главный reusable path.328Он:329330- читает saved reports и кастомные метрики;331- собирает новую report-spec с нуля;332- вызывает `analytics/data`;333- сохраняет TSV и raw JSON;334- по желанию тянет `export/excel`;335- снимает order audit через `integration/order/list`.336337Минимальный запуск:338339```bash340python3 <plugin-root>/skills/roistat-reports-api/scripts/build_roistat_report_pack.py \341 --project 192319 \342 --api-key-env ROISTAT_API_KEY \343 --from 2026-03-01 \344 --to 2026-03-16 \345 --report-name "API | Direct attribution MTD | new vs repeat" \346 --marker-level-1 direct1 \347 --marker-level-1 direct9 \348 --marker-level-1 direct10 \349 --marker-level-1 direct11 \350 --marker-level-1 direct13 \351 --output-dir ./output/roistat_reports/direct-attribution-mtd-20260316352```353354### `scripts/save_roistat_report.py`355356Reusable скрипт для create/update saved report внутри кабинета через API.357Он автоматически нормализует saved-report фильтры под формат кабинета:358359- для `operator="in"` не оставляет строковые массивы;360- для source-like значений подтягивает `label` через `project/analytics/source/list`;361- добавляет безопасные служебные поля, которые ожидаются живыми отчетами.362363Пример create из уже собранной спеки:364365```bash366python3 <plugin-root>/skills/roistat-reports-api/scripts/save_roistat_report.py \367 --project 192319 \368 --api-key-env ROISTAT_API_KEY \369 --report-spec ./output/roistat_reports/direct-attribution-mtd-20260316-v3/new_report_spec.json370```371372Пример update уже созданного отчета:373374```bash375python3 <plugin-root>/skills/roistat-reports-api/scripts/save_roistat_report.py \376 --project 192319 \377 --api-key-env ROISTAT_API_KEY \378 --report-spec ./output/roistat_reports/direct-attribution-mtd-20260316-v3/new_report_spec.json \379 --report-id 36 \380 --title "API | Директ | Атрибуция MTD | новые/повторные"381```382383### Что скрипты не делают384385- не меняет существующие отчеты;386- не трогает настройки проекта;387- не пишет новые кастомные метрики.388389## Выбор стратегии390391Если задача пользователя звучит как:392393- “пойми, почему основной отчет врет”,394- “собери новый отчет по новым/повторным лидам”,395- “дай выгрузку по атрибуциям”,396397то канонический путь такой:3983991. discovery saved reports;4002. новая report-spec с нуля;4013. `analytics/data`;4024. `export/excel`;4035. `integration/order/list` для верификации.404405Если пользователь отдельно требует “сохранить новый отчет прямо в Roistat”, сначала исследуй write-endpoint и зафиксируй контракт в skill/reference, а уже потом выполняй запись.