# Статистика и отчёты

Public API даёт доступ к той же системе аналитики, что и админка Sales Ninja: агрегированные отчёты по проекту в целом, по конкретной персонализации/A/B-тесту/rule-based действию/моделируемой конверсии/сегменту/антиботу.

Два эндпоинта:

HTTP Что делает
GET /public/api/v1/analytics/constructor?rootEntityType=…&rootEntityId=… Каталог допустимых полей фильтра, группировок, метрик, дефолтов и лимитов.
POST /public/api/v1/analytics/aggregated Построить отчёт.

# Когда какой rootEntityType

rootEntityType Что считаем
project По проекту в целом. rootEntityId игнорируется (он выводится из токена).
personalization По конкретной персонализации. Пресет учитывает её цели и атрибуцию.
abTest По конкретному A/B-тесту.
ruleBasedAction По конкретному rule-based действию.
modeledConversion По моделируемой конверсии. Пресет ставится автоматически (стандартный или сегментный).
audienceSegment По сегменту (атрибуция фиксирована на UserSegments).
antiBot По антибот-модели (профиль AntiBotPerformance).

Опросы и Яндекс.Директ в текущей версии Statistics V2 не поддерживаются — конструктор отдаст 404 с понятным hint.

# Конструктор аналитики

GET https://api.sales-ninja.me/public/api/v1/analytics/constructor?rootEntityType=personalization&rootEntityId=8f3a…
Headers:
  X-SN-TOKEN: <ваш токен>

Что возвращает (укрупнённо):

  • filterFields[] — поля фильтра: code, name, valueType, allowedOperators[], enumValues[].
  • groupByFields[] — поля, по которым можно группировать.
  • metrics[] — доступные метрики, с code/name/unit.
  • defaults — рекомендуемые groupBy, metrics, attributionType, distribution, granularity, samplingMode.
  • limits — снимок ограничений для текущего трафик-тира проекта:
Поле Что значит
maxGroupBy Максимальная длина groupBy[].
maxPeriodDays Максимум toUtc - fromUtc в днях.
maxFilterNodeCount Суммарное число узлов фильтра (групп + контейнеров).
maxFilterDepth Максимальная глубина вложенности фильтра.
maxFilterPredicateCount Суммарное число листовых предикатов.
cooldownSeconds Кулдаун между «тяжёлыми» вызовами того же проекта.
isExpensive Признак того, что текущий план считается «тяжёлым».

Лимиты — снимок на момент запроса; те же лимиты применяются сервером при сборе самого отчёта. Если запрос их нарушает — отчёт возвращается со status: "error" и reasonCode.

costMode (balanced, cheap) — параметр на конструкторе принимается для совместимости с MCP-вариантом API, но на Public API всегда трактуется как balanced. Cheap-режим зарезервирован для AI-агентов.

# Отчёт

POST https://api.sales-ninja.me/public/api/v1/analytics/aggregated
Headers:
  X-SN-TOKEN: <ваш токен>
  Content-Type: application/json

# Минимальный пример — по проекту в целом

{
  "rootEntityType": "project",
  "fromUtc": "2026-04-01T00:00:00Z",
  "toUtc":   "2026-04-30T23:59:59Z",
  "periodGranularity": "week",
  "attributionType": "allConversions",
  "groupBy": ["trafficSourceType", "device"],
  "metrics": ["sessions", "conversions", "revenue"],
  "filters": {
    "operator": "and",
    "predicates": [
      { "field": "trafficIsBrand", "operator": "equal", "value": "false" }
    ],
    "nodes": []
  },
  "samplingMode": "auto"
}

# Параметры запроса

Поле Описание
rootEntityType См. таблицу выше.
rootEntityId UUID конкретной сущности (если rootEntityTypeproject).
fromUtc / toUtc Границы периода (UTC).
periodGranularity Гранулярность периодов: day, week, month, quarter, year, none (агрегация без разбивки).
attributionType Тип атрибуции: allConversions, firstSession, userSegments, … (зависит от пресета).
groupBy[] Список кодов полей для группировки. Подмножество groupByFields[] конструктора.
metrics[] Список кодов метрик. Подмножество metrics[] конструктора.
filters N-арный фильтр (см. ниже).
samplingMode На Public API только auto.
costMode Принимается, но игнорируется (всегда balanced на Public API).

# Структура фильтра

Фильтр — N-арное дерево (в отличие от условий показа, которые бинарные):

{
  "operator": "and",
  "predicates": [
    { "field": "trafficSourceType", "operator": "equal", "value": "ad" },
    { "field": "device",            "operator": "in",    "value": "Mobile,Tablet" }
  ],
  "nodes": [
    {
      "operator": "or",
      "predicates": [
        { "field": "utmCampaign", "operator": "equal", "value": "spring_sale" },
        { "field": "utmCampaign", "operator": "equal", "value": "summer_sale" }
      ],
      "nodes": []
    }
  ]
}

Операторы группы: and, or, not. Допустимые operator каждого predicate берутся из allowedOperators поля в конструкторе.

# Ответ

{
  "status": "calculated",
  "trafficTier": "medium",
  "isExpensive": true,
  "averageSessionsPerDay": 12345,
  "requestedRootEntityType": "project",
  "effectiveRootEntityType": "project",
  "guardrails": {
    "trafficTier": "medium",
    "maxGroupByCount": 3,
    "maxPeriodDays": 45,
    "...": "..."
  },
  "report": {
    "reportId": "…",
    "result": {
      "periods": [
        { "fromUtc": "2026-04-01T00:00:00Z", "toUtc": "2026-04-07T23:59:59Z", "label": "Apr 1–7" }
      ],
      "rows": [
        {
          "groupKeys": [
            { "fieldCode": "trafficSourceType", "value": "ad",     "displayValue": "Ad"     },
            { "fieldCode": "device",            "value": "mobile", "displayValue": "Mobile" }
          ],
          "periods": [
            { "metricValues": [
              { "metricCode": "sessions",    "value": 1234 },
              { "metricCode": "conversions", "value":   42 },
              { "metricCode": "revenue",     "value":  555 }
            ] }
          ],
          "total": { "metricValues": [ "..." ] }
        }
      ],
      "grandTotal": { "metricValues": [ "..." ] },
      "dictionaries": { "...": "..." }
    }
  }
}

# Правила соответствия

  • rows[*].groupKeys[i].fieldCode == request.groupBy[i] — порядок ключей совпадает с порядком в запросе.
  • rows[*].periods[i].metricValues[k].metricCode == request.metrics[k] — порядок и количество метрик совпадают с запросом.
  • rows[*].periods — длина = длина result.periods[].

# Статусы

status Что значит Что делать
calculated Отчёт готов. Использовать report.
queued Отчёт в очереди. Повторить тот же POST через retryAfterSeconds, или забрать по reportId.
error Не удалось построить. Смотреть error, reasonCode, hint. Типичные причины: запрос нарушает limits, неподдерживаемый rootEntityType, плохие параметры.

# Алгоритм

  1. GET /analytics/constructor?rootEntityType=<x>&rootEntityId=<id> — узнать, что вообще можно запросить и какие лимиты.
  2. Взять defaults как стартовую точку, при необходимости — заменить groupBy[] / metrics[] / attributionType своими значениями (из соответствующих каталогов конструктора).
  3. Собрать filters из filterFields[]. Каждый предикат — {field, operator, value}; группировать через {operator, predicates[], nodes[]}.
  4. Соблюсти лимиты из limits (длина groupBy, период, глубина и размер фильтра).
  5. POST /analytics/aggregated. На status: "queued" — повторить запрос или забрать отчёт по reportId.

# cURL

# 1. Каталог
curl "https://api.sales-ninja.me/public/api/v1/analytics/constructor?rootEntityType=project" \
  -H "X-SN-TOKEN: $SN_TOKEN"

# 2. Отчёт
curl -X POST "https://api.sales-ninja.me/public/api/v1/analytics/aggregated" \
  -H "X-SN-TOKEN: $SN_TOKEN" \
  -H "Content-Type: application/json" \
  -d @report.json