ДАРА АНАЛИТИКА
Разделы документации

Документация

API

API v1 ДАРА АНАЛИТИКИ — ключи и общие правила, конверсии и заказы с сервера, отчёты, атрибуты и данные посетителей. Примеры на curl, PHP и Python.

API нужен, чтобы передавать данные с сервера сайта и забирать отчёты в свои системы. Он работает только сервер-сервер: ключ API хранится на вашем сервере и в браузер не попадает.

Здесь — общие правила, методы и примеры. Полное описание методов и полей запросов — машинное: OpenAPI по адресу GET /api/v1/openapi.json, без авторизации. Его понимают генераторы клиентов и просмотрщики API, и оно всегда совпадает с тем, что сервис принимает на самом деле. Ключ API описан в нём схемой безопасности bearer, поэтому клиент, собранный генератором, авторизуется сам. Схемы ответов и ошибок в формате {"error": {"code", "message", "details"}} описаны у каждого метода — от POST /visitors/attributes до POST /reports; сами коды ошибок и пределы — в разделе Ошибки и лимиты.

Ключ API

Ключей два вида, и возможности у них одинаковые — отличаются они только тем, на что выписаны:

Вид ключа Строка На что выписан Какой сайт отвечает
ключ сайта ak_ и 40 букв и цифр один сайт тот, которому выдан ключ
ключ аккаунта aa_ и 40 букв и цифр все сайты вашего аккаунта тот, который назван заголовком X-Dara-Site
  1. Ключ сайта создаётся в настройках сайта, раздел «Ключи API»; ключ аккаунта — в кабинете, раздел «Ключи аккаунта».
  2. Полный ключ показывается один раз — сохраните его в секретах сервера. В списке ключей потом видны только имя, префикс, дата создания и дата последнего использования.
  3. Потерянный или раскрытый ключ отзовите и создайте новый. Отзыв ключа аккаунта закрывает доступ ко всем сайтам сразу.

В примерах вместо ключа заглушка ak_XXXXXXXX…. Приставки разные нарочно: по одной строке видно, один сайт она открывает или все сайты аккаунта. Прав и сроков жизни у ключей нет: любой ключ делает всё, что умеет API, и работает, пока его не отзовут. Кнопка «Выйти на всех устройствах» в кабинете ключи API не отзывает.

Агентству и тому, у кого много сайтов, удобнее один ключ аккаунта: заводить и менять сотню ключей на сотню сайтов не нужно, а какой сайт имеется в виду — говорит заголовок каждого запроса.

Ключ API — не то же самое, что ключ сайта as_… в коде тега: ключ сайта публичный и для API не подходит.

Этим же ключом сайт аналитики подключается к проекту рекомендаций ДАРА: ключ выпускаете вы и вводите его в настройках своего проекта core — Подключение к ДАРА. Для этого подключения держите отдельный ключ сайта ak_…: core принимает только такой ключ и ключ аккаунта отклоняет своей же проверкой формы, а лимиты частоты считаются на ключ — частый опрос API вашими же скриптами не должен мешать рекомендациям.

Общие правила

Что Как
Адрес https://analytics.daratech.ru/api/v1, только HTTPS
Ключ заголовок Authorization: Bearer ak_XXXXXXXX…; в адресе и параметрах ключ не принимается
Сайт запроса ключу аккаунта — заголовок X-Dara-Site с идентификатором сайта; ключу сайта заголовок не нужен
Формат JSON в UTF-8, заголовок Content-Type: application/json
Время ISO-8601 с Z: 2026-09-14T12:00:00Z
Размер тела до 1 МБ
Ответы с заголовками Cache-Control: no-store и X-Request-ID
Браузер CORS-заголовков нет: запросы из браузера не пройдут
Персональные данные API не отдаёт их ни в одном ответе
  • Отозванный ключ — ответ 401. Заблокированный или удалённый сайт, заблокированный владелец — 403.
  • X-Request-ID пишите в свои логи: по нему можно найти запрос при разборе.
  • Коды ошибок и лимиты запросов — в разделе Ошибки и лимиты.

Сайт запроса

Ключ аккаунта сам по себе не говорит, про какой сайт вы спрашиваете, поэтому сайт называется заголовком:

X-Dara-Site: 7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13

Идентификатор сайта — тот же, что в адресе его страницы в кабинете (/cabinet/sites/7b3c1e42-…). Из кода его отдают GET /sites (все сайты аккаунта) и GET /site (сайт запроса), поэтому заводить его копию в своих настройках не нужно. Номер сайта, его название и ключ сайта as_… в этом заголовке не принимаются.

Что вы прислали Ответ
ключ аккаунта без заголовка, а методу нужен сайт 422, в details — поле X-Dara-Site. Сервис не угадывает сайт и не берёт первый из ваших
ключ аккаунта с заголовком на ваш сайт обычный ответ метода — про этот сайт
ключ сайта с заголовком на его же сайт обычный ответ: один и тот же код работает с ключом любого вида
ключ сайта с заголовком на другой ваш сайт 403: ключ сайта распоряжается только своим сайтом. Заголовок не игнорируется молча
заголовок на сайт, которого нет или который не ваш 404 — одинаковый ответ в обоих случаях
заголовок дважды в одном запросе 422: сайт запроса должен быть один

Заголовок понимают все методы. GET /meta, GET /network и GET /categories отвечают одинаково для всех сайтов, поэтому им сайт не нужен: ключ аккаунта работает с ними и без заголовка.

curl https://analytics.daratech.ru/api/v1/goals \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "X-Dara-Site: 7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13"

Лимиты частоты считаются на ключ, а не на сайт, — держите это в виду, когда обходите свои сайты в несколько потоков: числа пределов для каждого вида ключа собраны в разделе Ошибки и лимиты.

Страницы списков

Списки объектов отдаются страницами и обходятся курсором, а не смещением. Правила одни у всех таких методов:

curl https://analytics.daratech.ru/api/v1/sites?limit=100 \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"

В ответе рядом со списком приходят next_cursor и total. Чтобы получить следующую страницу, передайте next_cursor параметром cursor; next_cursor: null значит, что строк больше нет и ходить ещё раз незачем.

Параметр Что делает
limit сколько строк отдать за раз: от 1 до 200, по умолчанию 100
cursor значение next_cursor предыдущего ответа того же метода с теми же параметрами отбора
  • Курсор непрозрачен: это строка, которую разбирать не нужно и не получится. Её формат может измениться, поэтому сохранять курсор надолго не стоит — он нужен только на время обхода.
  • Обход устойчив к изменениям: если во время обхода что-то добавили, оно встретится в конце; если удалили — просто не встретится. Ни одна оставшаяся строка не повторится и не потеряется. Поэтому total — число строк на момент запроса, а не обещание, что столько и придёт.
  • Курсор принадлежит одному списку и одному отбору. Курсор от другого метода, от другого сайта или от другого значения status — 422 с полем cursor: начните обход заново.

Идентификаторы

Идентификатор Что это Формат
visitor_id посетитель сайта — значение cookie _dara_a; в браузере его отдаёт команда dara('ready') UUID
user_id id пользователя на вашем сайте, переданный в аналитику командой dara('user', { user_id: '…' }) после входа или методом POST /users строка до 191 символа Unicode
fingerprint_uuid id устройства DARA Fingerprint — только если он подключён к сайту, см. DARA Fingerprint UUID

На сервере visitor_id удобно брать из cookie _dara_a запроса, в котором посетитель оформил заказ или заявку, — только если значение похоже на UUID. Сохраните его вместе с заказом: отправка в аналитику обычно идёт позже, из очереди.

Чтобы связать посетителя с пользователем сайта, после входа вызовите на странице:

dara('user', { user_id: 'u-501' });

Методы

Метод Назначение
POST /conversions конверсии JS-целей с сервера
GET /goals цели сайта: коды, названия, типы, условия
POST /goals завести цель
PATCH /goals/{code} изменить цель: название, ценность, условия, включение
POST /orders заказы и возвраты с сервера
POST /reports отчёт по измерениям и метрикам
POST /reports/async тот же отчёт заданием очереди, без ожидания ответа
POST /audiences/export выгрузка аудитории сегмента в ваших идентификаторах — заданием очереди
GET /jobs/{job_id} состояние задания очереди, а у готового — и его результат
GET /segments сегменты сайта: названия и условия
POST /segments завести сегмент
PATCH /segments/{segment_id} изменить сегмент: название, условия
DELETE /segments/{segment_id} удалить сегмент
GET /funnels воронки сайта: названия, окно, порядок и шаги
POST /funnels завести воронку
PATCH /funnels/{funnel_id} изменить воронку
DELETE /funnels/{funnel_id} удалить воронку
POST /funnels/{funnel_id}/report посчитать воронку за период: посетители на каждом шаге, конверсия, отток
GET /categories справочник категорий сети: номер, путь и название
GET /category-mappings категории товаров сайта и их категории в справочнике
POST /category-mappings сопоставить категорию товара с категорией справочника
PATCH /category-mappings/{category_hash} сменить категорию справочника у известной строки
DELETE /category-mappings/{category_hash} снять ручное сопоставление
GET /ad-costs расходы на рекламу за период: день, UTM-метки, сумма
POST /ad-costs записать расходы на рекламу пачкой до 1000 строк
DELETE /ad-costs удалить расходы за период
GET /sites ваши сайты и те, где вы администратор по приглашению: идентификаторы, ключи тега, домены, состояние — только ключом аккаунта
POST /sites завести сайт — только ключом аккаунта
PATCH /sites/{site_uuid} изменить название и домены сайта — только ключом аккаунта
DELETE /sites/{site_uuid} удалить сайт (обратимо 30 суток) — только ключом аккаунта
POST /sites/{site_uuid}/restore восстановить удалённый сайт — только ключом аккаунта
GET /meta каталог измерений и метрик: идентификаторы, названия, группы, подписи значений, пределы запроса
GET /site сайт запроса: пояс, валюта, суточная квота и её расход, срок хранения, участие в сети
POST /visitors/attributes атрибуты до 100 посетителей: пол, возрастная группа, география, дети, компания, интересы, категории покупок, параметры — API: посетители
GET /visitors/{visitor_id} атрибуты одного посетителя, его первый и последний визит и число визитов — API: посетители
GET /network настройки сети: передаются ли чувствительные темы — API: посетители
POST /users данные сайта о посетителях: user_id, email, телефон, пол, возраст, город, дети, интересы, параметры — Данные о посетителях
POST /mcp MCP-сервер для ИИ-ассистентов: отчёты, воронки и описание сайта в разговоре — только чтение (раздел «MCP» ниже)
GET /openapi.json описание API в формате OpenAPI, без авторизации
GET /health состояние сервиса, отставание очереди приёма и поток, который ближе всех к своему порогу, — без авторизации

GET /health всегда отвечает 200, а состояние — в теле: {"status": "ok", "checks": {"mariadb": "ok", "clickhouse": "ok", "spool_lag_s": 2.1, "spool_lag_stream": "hits"}}. status — ok или degraded (degraded — сервис принимает данные, но что-то работает не так). spool_lag_s — наибольшее отставание очереди приёма по всем её потокам в секундах или null, если оно сейчас неизвестно; spool_lag_stream — поток, ближе всех подошедший к своему порогу (при degraded — виновник состояния). У разных потоков нормальное отставание разное, поэтому эти два поля могут относиться к разным потокам: число — у самого отставшего потока, имя — у самого тревожного, и судить о сервисе по одному числу нельзя. Ключ методу не нужен, поля в ответе со временем могут добавляться.

Конверсии

POST /conversions записывает достижение цели, которое подтвердилось на сервере: оплату, звонок из колл-центра, сделку в CRM.

export DARA_ANALYTICS_KEY='ak_XXXXXXXX…'

curl -X POST https://analytics.daratech.ru/api/v1/conversions \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversions": [
      {"id": "deal-7731", "goal": "crm_deal_won", "user_id": "u-501", "occurred_at": "2026-09-14T15:20:00Z", "value": 12000},
      {"id": "deal-7732", "goal": "crm_deal_won", "visitor_id": "0b7e6f8a-3c2d-4e5f-9a1b-2c3d4e5f6a7b"}
    ]
  }'

Ответ 202 Accepted:

{"accepted": 1, "duplicates": 0, "rejected": [{"index": 1, "code": "unknown_visitor"}]}
  • В одном запросе — до 1000 конверсий.
  • id — ваш id конверсии, до 64 символов ASCII, уникален в пределах сайта. Повторно присланная конверсия не учитывается и попадает в duplicates, поэтому пачку можно безопасно отправить ещё раз.
  • goal — код цели типа «JS-цель».
  • visitor_id или user_id — кто достиг цели, occurred_at — когда, value — ценность достижения: она важнее фиксированной суммы из настроек цели. Без occurred_at берётся время приёма запроса; время из будущего заменяется временем приёма.
  • Конверсия записывается в последний визит посетителя до occurred_at, не старше 90 дней. Если посетитель известен только по user_id, берётся последний визит связанного с ним посетителя.
  • Коды отказа: unknown_goal — у сайта нет включённой цели типа «JS-цель» с таким кодом, unknown_visitor — посетитель не найден, too_old — подходящий визит старше 90 дней.

Цели

Конверсия передаётся по коду цели, и этот код можно узнать и завести тем же ключом, не заходя в кабинет: GET /goals показывает цели сайта, POST /goals создаёт цель, PATCH /goals/{code} меняет её. Типы целей, их условия и правила подсчёта — в разделе Цели.

curl https://analytics.daratech.ru/api/v1/goals \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"

Ответ:

{"goals": [{"code": "crm_deal_won", "public_id": "3f2a1b7c-4d5e-4f60-8a1b-2c3d4e5f6a7b", "name": "Сделка в CRM",
            "type": "js", "checked_in": "tag", "is_active": true, "accepts_conversions": true, "value": 12000.0,
            "conditions": {"v": 1}, "created_at": "2026-09-14T12:00:00Z", "updated_at": "2026-09-14T12:00:00Z"}],
 "total": 1, "next_cursor": null}
  • code — код цели: его принимает POST /conversions, им же цель называется в метриках отчётов. public_id — постоянный идентификатор цели: код в кабинете сменить можно, public_id — нет.
  • accepts_conversions — примет ли POST /conversions код этой цели. С сервера засчитываются достижения только включённых целей типа «JS-цель»: остальные типы считает сам тег или отчёт по сохранённым хитам, и присланное достижение такой цели ничего бы не изменило.
  • checked_in — где проверяется цель: tag — тег присылает достижение сам, report — цель считается по хитам в момент запроса отчёта.
  • conditions — условия по типу цели в той же форме, которую принимает POST /goals: цель можно прочитать у одного сайта и такой же создать у другого. У составной цели шаги названы кодами целей-шагов.
  • Отключённые цели тоже в ответе, с is_active: false. Целей у сайта не больше 200, поэтому без параметра limit приходит весь каталог, а next_cursor в ответе — null. Меньший limit включает страницы — правила в разделе «Страницы списков» выше.
  • Код цели нужен не только POST /conversions: им называются метрики отчётов goal_reaches:{код} и conversion:{код} — их описание в разделе «Каталог измерений и метрик».

Новая цель — код, название, тип и условия по типу:

curl -X POST https://analytics.daratech.ru/api/v1/goals \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "crm_deal_won", "name": "Сделка в CRM", "type": "js", "value": 12000}'

curl -X POST https://analytics.daratech.ru/api/v1/goals \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "price_page", "name": "Страница цен", "type": "url",
       "conditions": {"match": [{"op": "contains", "value": "/price"}]}}'

Ответ 201 Created — созданная цель в поле goal:

{"goal": {"code": "crm_deal_won", "public_id": "3f2a1b7c-4d5e-4f60-8a1b-2c3d4e5f6a7b", "name": "Сделка в CRM",
          "type": "js", "checked_in": "tag", "is_active": true, "accepts_conversions": true, "value": 12000.0,
          "conditions": {"v": 1}, "created_at": "2026-09-14T12:00:00Z", "updated_at": "2026-09-14T12:00:00Z"}}
  • Обязательные поля — code, name и type. conditions зависят от типа цели (у «JS-цели» и «Покупки» условий нет), value — фиксированная ценность достижения, is_active — по умолчанию true. Условия и текст ошибок — те же, что в форме кабинета.
  • Повтор того же запроса безопасен: если такая цель уже есть и совпадает во всём, ответ — 200 и та же цель, второй цели не появляется. Если цель с этим кодом другая, ответ — 422 с полем code: посмотрите GET /goals и меняйте существующую цель методом PATCH.
  • Предел 200 целей на сайт — 422 с кодом too_many у поля goals. Неверный код, неизвестный тип или условия не по типу — 422, а какие именно поля неверны, написано в details.
  • Лишнее поле в теле — тоже 422 с кодом unknown_field: в методе управления опечатка в имени поля опаснее отказа.

Правка цели меняет только переданные поля:

curl -X PATCH https://analytics.daratech.ru/api/v1/goals/crm_deal_won \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": 15000, "is_active": false}'
  • Меняются name, value, conditions и is_active; value: null убирает фиксированную ценность. После правки отчёты за прошлые дни пересчитываются.
  • Код и тип цели через API не меняются: смена кода ломает интеграцию, которая этим кодом шлёт конверсии, а смена типа меняет способ подсчёта уже собранных достижений. И то и другое делается в кабинете.
  • Цели не удаляются ни в кабинете, ни через API: ненужную цель отключают — {"is_active": false}.
  • Нет цели с таким кодом — 404. Код, который сам выглядит ключом API (ak_ и 40 знаков), в адресе не принимается: такой адрес отклоняет защита «ключ API передаётся только заголовком» — правьте такую цель в кабинете. Коды, в которых ak_ просто встречается (kayak_booking), правятся как любые другие.

Сегменты

Сегмент — набор условий, по которым отбираются посетители; отчёт с сегментом показывает только их визиты. Что такое сегмент и какие бывают условия — в разделе Конструктор отчётов. Через API сегмент заводится и правится тем же ключом, а его segment_id сразу годится для POST /reports.

curl -X POST https://analytics.daratech.ru/api/v1/segments \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Смотрели цены и не купили",
       "definition": {"op": "and", "groups": [
         {"conditions": [{"kind": "page", "match": [{"op": "contains", "value": "/price"}], "days": 30}]},
         {"conditions": [{"kind": "goal", "goal": "crm_deal_won", "days": 30}]}]}}'

Ответ 201 Created — созданный сегмент в поле segment:

{"segment": {"segment_id": "9d4c1a2b-7e35-4f18-9a60-1b2c3d4e5f60", "name": "Смотрели цены и не купили",
             "definition": {"v": 1, "op": "and", "groups": [{"op": "and", "conditions": [{"kind": "page", "match": [{"op": "contains", "value": "/price"}], "days": 30}]}]},
             "created_at": "2026-09-28T12:00:00Z", "updated_at": "2026-09-28T12:00:00Z"}}
  • Условия проверяются теми же правилами, что и форма в кабинете. Неверное условие — 422, а в details названо точное поле внутри определения: definition.groups[0].conditions[1].values[0]. Свою схему условий описывать не нужно: что принимает кабинет, то принимает и API.
  • Цель в условии называется кодом: {"kind": "goal", "goal": "crm_deal_won", "days": 30}. Числовой goal_id не принимается — 422 с кодом unknown_field. Коды целей отдаёт GET /goals.
  • Сегменты API — общие для всех, у кого есть доступ к сайту. Личные сегменты сотрудников через API не видны и не правятся: в списке их нет, а PATCH и DELETE по такому segment_id отвечают 404 — ровно так же, как кабинет.
  • PATCH меняет только переданные поля (name, definition); правка теми же значениями ничего не пишет и не сбрасывает кэш отчётов.
  • Повтор того же запроса безопасен: если общий сегмент с таким названием и такими же условиями уже есть, ответ — 200 и он же, второго не появляется.
  • Удаление необратимо — восстановления у сегментов нет ни в кабинете, ни в API. Поэтому DELETE отдаёт удалённый сегмент целиком, вместе с условиями: если он ещё понадобится, создайте его заново тем же телом. Отчёты, сохранённые с этим сегментом, после удаления перестанут строиться.
  • DELETE работает любым вашим ключом — и ключом сайта ak_…, и ключом аккаунта aa_… с заголовком X-Dara-Site. Это данные одного сайта, и ключ этого сайта ими распоряжается целиком: заводит, правит и убирает. Удаление здесь необратимо, поэтому ответ отдаёт удалённую строку целиком — по ней её возвращает повторный POST с тем же телом.
  • Предел через API — 200 сегментов на сайт (считаются и личные сегменты сотрудников): 422 с кодом too_many у поля segments.
  • Список отдаётся страницами и обходится курсором — правила в разделе «Страницы списков» выше.

Воронки

Воронка — последовательность шагов, по которой видно, где посетители уходят. Что такое воронка и как считаются её шаги — в разделе Конструктор отчётов. Методы устроены как у сегментов.

curl -X POST https://analytics.daratech.ru/api/v1/funnels \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Корзина → покупка",
       "definition": {"window": {"days": 7}, "order": "strict", "steps": [
         {"kind": "page", "match": [{"op": "contains", "value": "/cart"}]},
         {"kind": "goal", "goal": "crm_deal_won"},
         {"kind": "ecommerce", "action": "purchase"}]}}'
  • Шагов от 2 до 10; window — "visit" (в пределах визита) или {"days": N} от 1 до 90; order — strict (шаги подряд) или any (все шаги в окне без требования порядка). Умолчания — "visit" и strict.
  • Шаг по цели называет цель кодом: {"kind": "goal", "goal": "crm_deal_won"}. Числовой goal_id — 422.
  • Воронки API — общие; личные воронки сотрудников через API не видны и не правятся.
  • DELETE отдаёт удалённую воронку целиком: восстановления у воронок нет.
  • DELETE работает любым вашим ключом — и ключом сайта ak_…, и ключом аккаунта aa_… с заголовком X-Dara-Site. Это данные одного сайта, и ключ этого сайта ими распоряжается целиком: заводит, правит и убирает. Удаление здесь необратимо, поэтому ответ отдаёт удалённую строку целиком — по ней её возвращает повторный POST с тем же телом.
  • Предел через API — 200 воронок на сайт.

Посчитать воронку за период — POST /funnels/{funnel_id}/report:

curl -X POST https://analytics.daratech.ru/api/v1/funnels/3c9e2a71-5b04-4f6d-9e18-0a7b2c4d6e85/report \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date_from": "2026-09-01", "date_to": "2026-09-14"}'
{"funnel_id": "3c9e2a71-5b04-4f6d-9e18-0a7b2c4d6e85", "name": "Корзина → покупка",
 "window": {"days": 7}, "order": "strict", "visitors": 1930, "completed": 260, "conversion": 13.47,
 "steps": [{"index": 1, "step": {"kind": "page", "match": [{"op": "contains", "value": "/cart"}]},
            "visitors": 1930, "conversion": 100.0, "step_conversion": 100.0, "dropped": 0, "drop_rate": null,
            "avg_seconds_to_next": 88.25},
           {"index": 2, "step": {"kind": "goal", "goal": "crm_deal_won"},
            "visitors": 610, "conversion": 31.61, "step_conversion": 31.61, "dropped": 1320, "drop_rate": 68.39,
            "avg_seconds_to_next": 35.0},
           {"index": 3, "step": {"kind": "ecommerce", "action": "purchase"},
            "visitors": 260, "conversion": 13.47, "step_conversion": 42.62, "dropped": 350, "drop_rate": 57.38,
            "avg_seconds_to_next": null}],
 "timezone": "Europe/Moscow", "date_from": "2026-09-01", "date_to": "2026-09-14"}
  • Считается так же, как в кабинете: шаги — по посетителям, цепочка целиком лежит внутри периода; проценты — от первого шага (conversion) и от предыдущего (step_conversion); деление на ноль — null.
  • robots — exclude (по умолчанию), include или only. Период — те же правила, что у отчёта: не позже сегодняшнего дня сайта и не длиннее 25 месяцев.
  • Бюджет — как у отчёта: тот же лимит POST /reports и те же одновременные отчёты ключа или аккаунта.

Категории товаров

Категории товаров у каждого магазина свои, а покупки, интересы и ценовые сегменты считаются по общему справочнику категорий ДАРА. Строку категории сайта сервис заводит сам, когда встречает её в заказах, и подбирает ей категорию справочника; выбор можно задать самим — из кабинета по одной или отсюда выгрузкой из товарного учёта.

Справочник читается методом GET /categories — он один на всю сеть, ключ подойдёт любой:

curl https://analytics.daratech.ru/api/v1/categories?limit=200 \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"
{"categories": [{"id": 1604, "parent_id": 166, "level": 2, "path": "Одежда и аксессуары > Одежда", "title": "Одежда"}],
 "next_cursor": "…", "total": 5443}

Свои категории и их сопоставление — GET /category-mappings:

{"category_mappings": [{"category_hash": "623621428df845ca971fc6dfe1b2e59f", "category": "Одежда/Женская/Платья",
                        "category_id": 1604, "category_path": "Одежда и аксессуары > Одежда",
                        "is_manual": false, "updated_at": "2026-09-28T03:10:00Z"}],
 "next_cursor": null, "total": 1}
  • category_hash — ключ строки: md5 приведённой строки категории (обрезанные пробелы, схлопнутые пробелы внутри, нижний регистр). Один и тот же ключ получается и у сервиса, и у вас, поэтому разное написание одной категории не заводит второй строки.
  • is_manual: false — категорию подобрал сервис; true — её задали руками, и сервис такую строку не трогает.
  • Отбор: only_manual=true — только заданные руками, only_unmapped=true — только без категории справочника (с них и стоит начинать).

Задать сопоставление:

curl -X POST https://analytics.daratech.ru/api/v1/category-mappings \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"category": "Одежда/Женская/Платья", "category_id": 1604}'
  • Строка заводится, если её ещё нет, и получает is_manual: true. Ответ — 201, а если строка уже была — 200.
  • category_id: null — «не сопоставлять»: строка остаётся заданной руками и без категории, и сервис больше не будет подбирать ей категорию сам.
  • Номера, которого нет в справочнике, — 422 с полем category_id.
  • PATCH /category-mappings/{category_hash} меняет только категорию справочника, саму строку категории пересылать не нужно.
  • DELETE /category-mappings/{category_hash} снимает ручное сопоставление и возвращает строку автоматическому подбору; в ответе — снятая строка целиком. Строку, которую подобрал сервис, снимать нечего — 404.
  • DELETE работает любым вашим ключом — и ключом сайта ak_…, и ключом аккаунта aa_… с заголовком X-Dara-Site. Сопоставление — данные одного сайта, и ключ этого сайта ими распоряжается: заводит, правит и снимает. Снятое сопоставление возвращается новым POST, поэтому ответ отдаёт снятую строку целиком.
  • После изменения сопоставлений отчёты электронной коммерции за прошлые дни пересчитываются.

Расходы на рекламу

Окупаемость рекламы — ad_cost, cost_per_visit, cost_per_order, cpa:{код цели}, roi, crr в отчётах — считается по расходу, которого у сервиса нет: его знает ваш рекламный кабинет. Расход вносят в кабинете («Настройки → Расходы на рекламу», строкой или файлом CSV) или этим методом — например, раз в сутки скриптом из выгрузки Директа. Как считаются метрики — Отчёты и метрики.

Строка расхода — день, три UTM-метки и сумма:

curl -X POST https://analytics.daratech.ru/api/v1/ad-costs \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"costs": [
        {"date": "2026-10-01", "utm_source": "yandex", "utm_medium": "cpc", "utm_campaign": "autumn_sale", "amount": 12500.50},
        {"date": "2026-10-01", "utm_source": "vk", "utm_medium": "cpm", "utm_campaign": "", "amount": 99.90, "currency": "USD"}
      ]}'
{"created": 2, "updated": 0, "unchanged": 0, "deleted": 0}
  • Сопоставление — только по utm_source, utm_medium и utm_campaign, без учёта регистра. «Yandex» и «yandex» — одна строка. utm_content и utm_term в расчёт не идут. Визиты по yclid и gclid без UTM-меток расходу не сопоставляются: размечайте объявления UTM-метками.
  • utm_source обязателен. Пустые utm_medium и utm_campaign значат буквально «метка не задана», а не «весь источник»: расход на кампанию записывайте с её utm_campaign.
  • date — день расхода ГГГГ-ММ-ДД в поясе сайта: не в будущем и не старше 25 месяцев. Расход за месяц одной суммой разложите по дням или запишите на первое число.
  • amount — от 0, не больше двух знаков после запятой. currency — код валюты из списка валют сайта; без него — валюта сайта. Сумма в другой валюте пересчитывается в валюту сайта по курсу ЦБ на дату расхода в момент записи и задним числом не меняется. Курса на эту дату нет — 422 с кодом no_rate у поля costs[N].currency.
  • Повтор строки с теми же датой и метками заменяет сумму. Повтор той же пачки ничего не меняет: "unchanged": 2.
  • Пачка — всё или ничего: до 1000 строк, ошибки всех строк приходят сразу (costs[3].amount), и не пишется ни одна. Одна и та же строка дважды в пачке — код duplicate.
  • "replace_dates": true в теле — сначала удалить все расходы сайта за даты пачки. Так повторная выгрузка из рекламного кабинета не оставляет кампаний, которых в ней уже нет; число удалённых строк — в поле deleted.
  • На сайт — до 200 000 строк расходов.

Прочитать записанное — GET /ad-costs?date_from=2026-10-01&date_to=2026-10-31, с отбором utm_source, utm_medium, utm_campaign (без учёта регистра) и обходом страницами:

{"ad_costs": [{"date": "2026-10-01", "utm_source": "yandex", "utm_medium": "cpc", "utm_campaign": "autumn_sale",
               "amount": 12500.5, "currency": "RUB", "amount_site": 12500.5, "origin": "api",
               "updated_at": "2026-10-02T03:00:00Z"}],
 "next_cursor": null, "total": 1, "total_amount_site": 12500.5, "currency": "RUB"}

Метки в ответе — в том виде, в каком хранятся: в нижнем регистре, с одним пробелом между словами. origin — откуда строка: cabinet или api.

Удалить расходы за период — DELETE /ad-costs?date_from=…&date_to=…, можно с теми же метками; в ответе — {"deleted": N}. Работает любым вашим ключом, как и запись. После любого изменения расходов отчёты за прошлые дни пересчитываются.

Заказы

POST /orders передаёт заказы и возвраты. Правила учёта — в разделе Электронная коммерция.

curl -X POST https://analytics.daratech.ru/api/v1/orders \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orders": [{
      "transaction_id": "T-20931",
      "visitor_id": "0b7e6f8a-3c2d-4e5f-9a1b-2c3d4e5f6a7b",
      "occurred_at": "2026-09-14T12:30:00Z",
      "status": "purchased",
      "currency": "RUB",
      "revenue": 5480,
      "products": [
        {"id": "SKU-1017", "name": "Платье миди", "category": "Одежда/Женская/Платья", "brand": "Пример", "variant": "синее", "price": 2490, "qty": 2},
        {"id": "SKU-2044", "name": "Ремень", "category": "Одежда/Аксессуары", "price": 500, "qty": 1}
      ]
    }]
  }'

Ответ 202 Accepted:

{"accepted": 1, "duplicates": 0, "rejected": []}
  • В одном запросе — до 500 заказов.
  • status — purchased (по умолчанию) или refunded. Заказ со статусом refunded — возврат: он уменьшает выручку и заказы того периода, в который пришёл, а прошлые отчёты задним числом не меняет. Правила учёта — в разделе Электронная коммерция.
  • Заказ узнаётся по transaction_id: покупка из тега и из API учитывается один раз.
  • Заказ приписывается визиту по тем же правилам, что конверсия.
  • Коды отказа: unknown_visitor, too_old, invalid_products — в заказе нет товаров, их больше 100 или у товара неверное поле.

Отчёты

POST /reports строит отчёт тем же движком, что конструктор в кабинете. Права и пределы — как у пользователя с ролью «наблюдатель».

curl -X POST https://analytics.daratech.ru/api/v1/reports \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dimensions": ["source_type", "source_name"],
    "metrics": ["visits", "visitors", "pageviews"],
    "date_from": "2026-09-01",
    "date_to": "2026-09-14",
    "filters": [{"dimension": "device_type", "op": "eq", "values": ["phone"]}],
    "attribution": "last_significant",
    "robots": "exclude",
    "sort": [{"field": "visits", "dir": "desc"}],
    "limit": 100
  }'

Ответ:

{"data": [
   {"dimensions": ["search", "Яндекс"], "metrics": [1840, 1522, 5210]},
   {"dimensions": ["search", "Google"], "metrics": [620, 541, 1733]}
 ],
 "totals": [3920, 3311, 11042],
 "sampled": false,
 "timezone": "Europe/Moscow",
 "labels": {"source_type": {"search": "Поисковые системы"}},
 "total_rows": 57,
 "sample_share": 1.0,
 "quota_sampled": [],
 "date_from": "2026-09-01",
 "date_to": "2026-09-14",
 "cached": false,
 "generated_at": "2026-09-14T21:15:30.120Z"}

В примере показаны две строки из 57 (total_rows) — остальные приходят так же, страницами по limit.

Поле запроса Значение
dimensions до 5 измерений
metrics от 1 до 10 метрик
date_from, date_to даты ГГГГ-ММ-ДД в часовом поясе сайта, включительно; период — до 25 месяцев
filters до 20 условий, объединяются через «и»: {"dimension", "op", "values"}. op — eq (равно), neq (не равно), contains (содержит), regex (регулярное выражение RE2), in (входит в список). У in в values до 100 значений, у остальных — ровно одно
segment_id UUID сохранённого сегмента сайта — он же в адресе страницы сегмента в кабинете (…/segments/{UUID}). Числовой номер не принимается
segment условия сегмента прямо в запросе, вместо segment_id; оба поля сразу — ошибка
attribution модель атрибуции: last_significant — последний значимый источник (по умолчанию), first — первый, last — последний
robots exclude — без роботов (по умолчанию), include — все, only — только роботы
sort до 3 условий {"field", "dir"}: field — метрика или измерение из запроса, dir — asc или desc. По умолчанию — первая метрика по убыванию, а если в запросе есть измерение времени — по нему по возрастанию
limit, offset limit — от 1 до 10 000, по умолчанию 100; offset — сколько строк пропустить
compare период сравнения {"date_from", "date_to"}; тогда в ответе появляется поле compare
totals false — без итогов: totals в ответе будет null
accuracy точность: full — по всем данным (по умолчанию), low — по выборке 10 %. Грубый отчёт считается примерно в десять раз дешевле и годится для панелей, которые обновляются часто; что получилось, видно в sampled и sample_share
  • Измерения и метрики — те же, что в разделе Отчёты и метрики. Полный список идентификаторов с названиями, группами, допустимыми операциями фильтров и пределами одного запроса отдаёт GET /meta — ниже, раздел «Каталог измерений и метрик». В описании OpenAPI перечня идентификаторов нет: тело отчёта там описано формой, а состав измерений и метрик меняется без выпуска новой схемы.
  • Измерения аудитории — gender, age_group, interest, has_children, children_age, company_type, industry, company_region, purchase_category, price_segment, geo_person_country, geo_person_region, geo_person_city, visitor_param:ключ, value_source:атрибут — и метрики people, people_share, undefined_share, network_share, affinity_index считаются по правилам раздела Сеть ДАРА. Значения — коды, как в POST /visitors/attributes; у интересов, категорий и мест — id справочника, а название — в labels ответа.
  • totals — итоги по метрикам в том же порядке, что metrics.
  • labels — подписи значений: {измерение: {значение: подпись}}. Значения в строках отчёта не меняются, подписи лежат отдельно и не повторяются в каждой строке. Чего в labels нет, то само себе подпись: у адресов страниц и поисковых фраз подписывать нечего, и такие измерения в словаре не появляются вовсе.
  • total_rows — сколько строк у отчёта всего, без limit и offset: по нему видно, когда страницы кончились. У отчёта по выборке это строки выборки — ровно те, которые обойдут следующие страницы с той же точностью.
  • sampled: true — отчёт посчитан по выборке: либо запрос не уложился в 30 секунд, либо клиент попросил accuracy: low. sample_share — доля данных в ответе (1.0 или 0.1); счётные метрики выборки умножены на 10, то есть это оценка. Дни из quota_sampled в эту долю не входят: они собирались по выборке ещё при приёме, и их числа — оценка сборщика при любом sample_share.
  • quota_sampled — дни периода, в которые сайт вышел за суточную квоту хитов и данные собирались по выборке (см. Отчёты и метрики). Пустой список — таких дней в периоде не было. Если отчёт пришёл по выборке, а список пуст, дело во времени запроса: повтор или более узкий период помогут.
  • date_from, date_to — разобранный период отчёта, cached и generated_at — взят ли ответ из кэша и когда он посчитан. Поэтому одинаковые числа в двух ответах — это кэш, а не остановка сбора данных.
  • compare в запросе — compare в ответе: у того же отчёта считается второй период, и строки сравниваются строка к строке. Остальные поля ответа те же, поэтому в примере ниже показаны не все.
{"data": [{"dimensions": ["search", "Яндекс"], "metrics": [1840, 1522, 5210]},
          {"dimensions": ["search", "Google"], "metrics": [620, 541, 1733]}],
 "totals": [3920, 3311, 11042],
 "sampled": false,
 "timezone": "Europe/Moscow",
 "compare": {"date_from": "2026-08-18", "date_to": "2026-08-31",
             "data": [{"metrics": [1710, 1402, 4980]}, null],
             "totals": [3640, 3080, 10310]}}

compare.data идёт строка к строке с data: та же длина и тот же порядок, а null значит, что в периоде сравнения такой строки не было. compare.totals ведёт себя как totals. Без compare в запросе поля compare в ответе нет.

  • Одновременно — до 2 запросов отчётов на ключ и до 60 в минуту; ключу аккаунта — 4 одновременных запроса на аккаунт, общих для всех его ключей, и 240 в минуту. Запрос сверх этого сразу получает 429 с кодом rate_limited и заголовком Retry-After.
  • Неизвестное измерение или метрику, неверные даты и фильтры отчёт отклоняет ответом 422 с кодом validation_failed — подробности в details, коды — в разделе Ошибки и лимиты.

Задания: отчёт без ожидания и выгрузка аудитории

Тяжёлый запрос не должен держать соединение. Вы ставите задание, получаете job_id, спрашиваете состояние — и по тому же адресу забираете результат, когда он готов.

Метод Что делает
POST /reports/async ставит в очередь отчёт: тело — то же определение, что у POST /reports
POST /audiences/export ставит в очередь выгрузку аудитории сегмента
GET /jobs/{job_id} состояние задания, а у готового — и его результат
curl -X POST https://analytics.daratech.ru/api/v1/reports/async \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dimensions": ["source_type", "source_name"],
    "metrics": ["visits", "visitors"],
    "date_from": "2026-01-01",
    "date_to": "2026-09-14"
  }'

Ответ:

{"job": {"job_id": "0f2d6b18-3c47-4e9a-8b52-7d1e0a4c6f83",
         "kind": "report",
         "status": "queued",
         "created_at": "2026-09-14T12:00:00Z",
         "started_at": null,
         "finished_at": null,
         "rows": null,
         "expires_at": null,
         "error": null},
 "report": null,
 "audience": null,
 "next_cursor": null}

Дальше спрашивайте состояние — по тому же адресу придёт и результат:

curl https://analytics.daratech.ru/api/v1/jobs/0f2d6b18-3c47-4e9a-8b52-7d1e0a4c6f83 \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"
{"job": {"job_id": "0f2d6b18-3c47-4e9a-8b52-7d1e0a4c6f83",
         "kind": "report",
         "status": "done",
         "created_at": "2026-09-14T12:00:00Z",
         "started_at": "2026-09-14T12:00:05Z",
         "finished_at": "2026-09-14T12:00:20Z",
         "rows": 1,
         "expires_at": "2026-09-14T13:00:20Z",
         "error": null},
 "report": {"data": [{"dimensions": ["search", "Яндекс"], "metrics": [1840, 1522]}],
            "totals": [1840, 1522],
            "sampled": false,
            "timezone": "Europe/Moscow",
            "labels": {"source_type": {"search": "Поисковые системы"}},
            "total_rows": 1,
            "sample_share": 1.0,
            "quota_sampled": [],
            "date_from": "2026-01-01",
            "date_to": "2026-09-14",
            "cached": false,
            "generated_at": "2026-09-14T12:00:20Z"},
 "audience": null,
 "next_cursor": null}
Поле задания Значение
job_id идентификатор задания; он же в адресе GET /jobs/{job_id}
kind report — отчёт, audience — выгрузка аудитории
status queued — ждёт очереди, running — считается, done — результат в этом же ответе, failed — причина в error
created_at, started_at, finished_at когда задание поставлено, взято в работу и кончилось
rows строк в готовом результате; null — результата ещё нет
expires_at до какого времени результат доступен
error у failed — объект с полями code и message. Значения code: validation_failed (условие задания перестало быть верным), too_slow, unavailable, not_found, interrupted, internal
  • Результат приходит там же, где состояние. У готового отчёта поле report — это ровно тот же ответ, что отдаёт POST /reports, поле в поле: переписывать разбор ответа не нужно. У готовой выгрузки заполнено audience.
  • Очередь просыпается раз в 15 секунд, и считается в ней одно задание за раз. Спрашивать состояние чаще, чем раз в несколько секунд, смысла не имеет.
  • Определение проверяется сразу. Неизвестное измерение, неверная дата, неизвестный segment_id — 422 в ответ на постановку, а не готовое задание с отказом через минуту.
  • Повтор того же запроса не ставит второе задание: если такое задание уже в очереди, придёт оно же, а код ответа будет 200 вместо 202.
  • Ключом аккаунта спрашивайте с тем же X-Dara-Site, что при постановке: без заголовка — 422, с другим сайтом — 404.
  • Срок результата: у отчёта — час, у выгрузки аудитории — сутки. После него задание отвечает 404 — как неизвестное и как чужое: по ответу нельзя узнать, существовало ли оно.

Пределы. Очередь не даёт обойти пределы синхронных отчётов, она ими и ограничена:

Предел Ключ сайта Ключ аккаунта
постановка задания — то же ведро, что у POST /reports 60 в минуту 240 в минуту
невыполненных заданий (queued и running) 2 на ключ 4 на аккаунт
опрос состояния — ведро чтения 50 в секунду 200 в секунду

Невыполненных заданий столько же, сколько одновременных отчётов у POST /reports: очередь — это способ не ждать ответа, а не способ заказать больше работы. Сверх предела — 429 с кодом rate_limited и заголовком Retry-After.

Выгрузка аудитории сегмента

POST /audiences/export отвечает на вопрос «кто из ваших посетителей попал в сегмент за период». Это самый дорогой запрос сервиса, поэтому он работает только заданием очереди.

curl -X POST https://analytics.daratech.ru/api/v1/audiences/export \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "9d4c1a2b-7e35-4f18-9a60-1b2c3d4e5f60",
    "date_from": "2026-06-01",
    "date_to": "2026-09-14"
  }'
Поле запроса Значение
segment_id UUID сохранённого сегмента сайта
segment условия сегмента прямо в запросе, вместо segment_id; оба поля сразу — ошибка
date_from, date_to период в часовом поясе сайта, включительно
filters условия сверх сегмента — та же форма, что у POST /reports
robots exclude (по умолчанию), include, only
limit потолок строк выгрузки: до 100 000 (по умолчанию столько же)

Условие обязательно: без segment_id или segment метод отвечает 422. Выгрузка аудитории — это выгрузка сегмента, а не «отдайте всех посетителей сайта».

Готовая выгрузка приходит страницами того же GET /jobs/{job_id}: параметры limit (до 10 000 строк, по умолчанию 1000) и cursor, а next_cursor ведёт на следующую страницу — как во всех списках API.

{"job": {"job_id": "3a71c0d5-8e62-4b19-9f04-5c7d2e8a1b60", "kind": "audience", "status": "done",
         "created_at": "2026-09-14T12:00:00Z", "started_at": "2026-09-14T12:00:05Z",
         "finished_at": "2026-09-14T12:01:40Z", "rows": 24180,
         "expires_at": "2026-09-15T12:01:40Z", "error": null},
 "report": null,
 "audience": {"date_from": "2026-06-01",
              "date_to": "2026-09-14",
              "total": 24180,
              "truncated": false,
              "visitors": [
                {"visitor_id": "5f0c6c9e-2b8a-4c1e-9a57-0d3b7e41c2a8", "user_id": "u-501",
                 "last_visit_at": "2026-09-14T09:12:30Z", "visits": 7},
                {"visitor_id": "8a1d4b02-6c73-4e59-b0f1-2d6a9c3e7f45", "user_id": null,
                 "last_visit_at": "2026-09-13T18:44:02Z", "visits": 2}]},
 "next_cursor": "QkxNVjJHN0FaWFA0S0RSNldTM0VZTjVUQ0pINDJVTTdGOVFCVjhYRA"}
Поле строки Значение
visitor_id посетитель — значение cookie _dara_a на вашем домене
user_id ваш идентификатор пользователя, который вы сами передали — командой тега dara('user', …) или методом POST /users; null — этого посетителя вы не называли; * — вы прислали в нём адрес почты или телефон, и таким мы его не храним
last_visit_at последний визит в периоде
visits сколько визитов у посетителя в периоде

total — сколько посетителей в выгрузке всего; truncated: true значит, что сегмент шире потолка limit и в выгрузку попали самые недавние посетители.

Что выгрузка отдаёт и чего не отдаёт. В ней только идентификаторы — ваши и общий с вами visitor_id. Ни адреса почты, ни телефона, ни имени, ни даты рождения в ней нет и появиться не может: персональные данные посетителей зашифрованы и наружу не выходят никогда — ни этим методом, ни любым другим. Это не «выгрузка в рекламные системы»: ни один рекламный кабинет не умеет сопоставлять чужой visitor_id, и обещать вам подачу аудитории напрямую мы не будем.

Как ей пользоваться. Вы получаете ответ «вот ваши идентификаторы тех, кто попал в сегмент», и дальше работаете с ними у себя: поднимаете этих людей в своей CRM и сами отправляете в свой рекламный кабинет то, что и так ваше; персонализируете сайт по visitor_id из cookie _dara_a; передаёте по ним конверсии с сервера. Наша роль — ответить, кто из ваших людей в сегменте, а не что это за люди.

Отсюда и предусловие: если сайт никогда не присылал user_id, в выгрузке будут только visitor_id — они полезны, если вы храните значение cookie _dara_a у себя (см. «Идентификаторы» выше и Данные о посетителях).

Каталог измерений и метрик

GET /meta отдаёт всё, что принимает POST /reports: идентификаторы измерений и метрик с названиями и группами, подписи значений и пределы одного запроса. В кабинете видны названия, а идентификаторы — нет, поэтому каталог нужен любому, кто составляет отчёты не руками. Ответ одинаков у всех сайтов, ключ нужен только для авторизации.

export DARA_ANALYTICS_KEY='ak_XXXXXXXX…'

curl https://analytics.daratech.ru/api/v1/meta \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"

Ответ (здесь сокращён: измерений 65, метрик 31, наборов подписей 21):

{"registry_version": "2bcfcc0ddf1b9e64",
 "groups": [{"id": "audience", "title": "Аудитория"}, {"id": "time", "title": "Время"},
            {"id": "source", "title": "Источник"}, {"id": "page", "title": "Страница"},
            {"id": "actions", "title": "Действия"}, {"id": "tech", "title": "Технологии"},
            {"id": "geo", "title": "География"}, {"id": "visitor", "title": "Посетитель"},
            {"id": "service", "title": "Служебные"}],
 "dimensions": [
   {"id": "source_type", "title": "Тип источника", "group": "source", "level": "visit", "value_type": "text",
    "labels": "source_type", "empty_label": "Не определено", "filter_ops": ["eq", "neq", "in"], "param": null},
   {"id": "url", "title": "Страница", "group": "page", "level": "hit", "value_type": "text",
    "labels": null, "empty_label": "Не определено",
    "filter_ops": ["eq", "neq", "contains", "regex", "in"], "param": null},
   {"id": "event_param", "title": "Параметр события", "group": "actions", "level": "hit", "value_type": "text",
    "labels": null, "empty_label": "Не определено",
    "filter_ops": ["eq", "neq", "contains", "regex", "in"],
    "param": {"title": "Имя параметра", "max_length": 128}},
   {"id": "interest", "title": "Интерес", "group": "audience", "level": "both", "value_type": "catalog_id",
    "labels": null, "empty_label": "Не определено", "filter_ops": ["eq", "neq", "in"], "param": null}],
 "metrics": [
   {"id": "visits", "title": "Визиты", "type": "int", "levels": ["visit", "hit"], "additive": true, "param": null},
   {"id": "bounce_rate", "title": "Отказы", "type": "percent", "levels": ["visit"], "additive": false,
    "param": null},
   {"id": "revenue", "title": "Выручка", "type": "money", "levels": ["hit"], "additive": true, "param": null}],
 "labels": {"source_type": {"direct": "Прямые заходы", "search": "Поисковые системы", "ad": "Реклама"}},
 "limits": {"dimensions": 5, "metrics": 10, "filters": 20, "filter_values": 100, "filter_value_length": 2048,
            "sort": 3, "limit": 10000, "limit_default": 100, "period_months": 25},
 "attribution": ["last_significant", "first", "last"],
 "robots": ["exclude", "include", "only"]}
Поле Значение
registry_version версия каталога: меняется вместе с составом измерений и метрик. Каталог можно держать у себя и обновлять, когда версия сменилась
groups[] группы измерений: id и название. На id ссылается dimensions[].group
dimensions[] id — для dimensions, filters[].dimension и sort[].field; title — название по-русски, то же, что в кабинете; empty_label — подпись пустого значения; filter_ops — операции filters, которые есть у этого измерения
dimensions[].level где живёт измерение: visit — у визита, hit — у хита, both — у обоих
dimensions[].value_type text — значение строкой (код, адрес, дата); catalog_id — номер строки общего справочника: темы, категории товаров, города, регионы
dimensions[].labels имя набора подписей в labels или null, если значения не кодовые
metrics[] id — для metrics и sort[].field; type — как показывать значение (int, float, percent, seconds, money); additive — значение можно складывать по строкам (у долей и средних нельзя)
metrics[].levels уровни запроса, на которых метрика считается
labels наборы подписей по имени: код значения → название
limits пределы одного запроса POST /reports — те же, что в таблице полей запроса выше
attribution, robots значения одноимённых полей запроса
param у измерения или метрики с параметром: что значит параметр и его наибольшая длина. Такое измерение просится токеном id:параметр — event_param:plan, goal_reaches:order

Коды целей для метрик goal_reaches:{код} и conversion:{код} каталог не перечисляет — они свои у каждого сайта: их отдаёт GET /goals (раздел «Цели» выше).

Какие измерения и метрики можно просить вместе, каталог говорит прямо, подбирать не нужно:

  • если в запросе есть измерение с level: "hit" или метрика без visit в levels, весь отчёт считается по хитам;
  • в таком отчёте каждая метрика должна поддерживать hit, иначе 422 с кодом incompatible на поле метрики. Например url и bounce_rate вместе не считаются: отказы — метрика визита;
  • измерения между собой совместимы всегда: измерение визита в отчёте по хитам берётся из визита этого хита.

Сайты

Сайты аккаунта заводятся, перечисляются и удаляются тем же ключом — заходить в кабинет за каждым сайтом не нужно. Эти методы работают только ключом аккаунта aa_…: ключ сайта распоряжается лишь своим сайтом и получает 403. Заголовок X-Dara-Site здесь не нужен и не принимается — сайт называет сам адрес.

curl https://analytics.daratech.ru/api/v1/sites?limit=100 \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"

Ответ:

{"sites": [{"site_uuid": "7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13", "site_key": "as_XXXXXXXXXXXXXXXXXXXX",
            "name": "Магазин", "domains": [{"host": "shop.example.ru", "subdomains": true}],
            "timezone": "Europe/Moscow", "currency": "RUB", "status": "active", "role": "owner",
            "created_at": "2026-09-14T12:00:00Z", "deleted_at": null, "restorable": false,
            "restore_until": null,
            "first_hit_at": "2026-01-14T09:20:11Z", "last_hit_at": "2026-09-26T11:59:03Z"}],
 "next_cursor": null, "total": 1}
Поле Значение
site_uuid идентификатор сайта: его ставят в заголовок X-Dara-Site и в адрес /sites/{site_uuid}
site_key ключ сайта as_… — тот же, что в коде тега; он публичный, и без него тег не поставить
name, domains название и домены сайта; первый домен основной, subdomains — считать ли его поддомены тем же сайтом
timezone, currency пояс и валюта сайта: в них считаются даты и деньги отчётов
status active — сайт работает; blocked — заблокирован поддержкой, данные не принимаются; deleted — удалён и ждёт очистки
role чем вы приходитесь сайту: owner — сайт ваш, admin — вас позвали администратором в чужой аккаунт
restorable сайт ещё можно вернуть методом восстановления: срок не истёк и очистка данных не началась
deleted_at, restore_until когда сайт удалён и до какого времени его ещё можно восстановить; restore_until — null, когда вернуть сайт уже нельзя
first_hit_at, last_hit_at первый и последний хит: по ним видно, какой из сайтов перестал присылать данные
  • По умолчанию список показывает действующие сайты; свои из них (role: owner) принимает заголовок X-Dara-Site, сайты с role: admin — нет (см. ниже). Заблокированные показывает ?status=blocked, удалённые и ждущие очистки — ?status=deleted, все три — ?status=all.
  • Заблокированный сайт занимает слот в пределе числа сайтов, поэтому он виден в списке: иначе сайтов было бы видно меньше, чем занято слотов. Данные такой сайт не принимает, и заголовок X-Dara-Site отвечает по нему 403.
  • В списке ?status=deleted поле restorable говорит, можно ли ещё вернуть сайт. false — срок вышел или очистка уже началась; такой сайт отвечает на восстановление 422, и проверять это самому не нужно.
  • Список отдаётся страницами, обход — курсором: правила одни на все списки и описаны выше, в общих правилах.
  • В списке есть и сайты, где вы администратор по приглашению (role: admin), а не только ваши собственные (role: owner): иначе сайтов клиента, позвавшего вас в свой кабинет, из кода было бы не видно вовсе. Разница важна: удалить и восстановить можно только свой сайт, чужой отвечает на это 404 — так же, как несуществующий. Приглашение «наблюдателем» сайт в список не добавляет, и удалённые сайты в списке только ваши собственные.
  • Заголовок X-Dara-Site шире не стал: сайт, где вы только администратор, по нему отвечает 404, и отчёты, цели и посетителей по нему пока не спросить. Чтобы работать с сайтом из кода целиком, попросите владельца передать вам сайт или выпустите ключ в его аккаунте.

Завести сайт

curl -X POST https://analytics.daratech.ru/api/v1/sites \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Магазин", "domains": [{"host": "shop.example.ru", "subdomains": true}],
       "timezone": "Europe/Moscow", "currency": "RUB"}'

Ответ 201 Created — созданный сайт в поле site, вместе с site_uuid и site_key: этого хватает, чтобы сразу поставить тег и начать спрашивать отчёты.

  • Обязательные поля — name и domains (хотя бы один домен). timezone не задан — Europe/Moscow, currency не задана — RUB. Хост приводится так же, как в кабинете: нижний регистр, punycode, без https://, порта, пути и ведущего www..
  • Срок хранения данных при создании не задаётся: он берётся из квот вашего аккаунта, и действующее значение показывает GET /site (retention_months).
  • Повтор того же запроса безопасен: если такой сайт уже есть и совпадает во всём, ответ — 200 и тот же сайт, второго не появляется. Повтор после таймаута не заводит близнеца. Если же отличается хоть одно поле, создаётся новый сайт: один домен вправе иметь, например, боевой и тестовый счётчик.
  • Число ДЕЙСТВУЮЩИХ сайтов в аккаунте ограничено (по умолчанию 30). Предел достигнут — 422 с кодом too_many у поля sites. Удаление освобождает слот сразу. Сайты, где вы администратор по приглашению, слотов не занимают: предел считает только ваши собственные.
  • Суточного предела на создание сайтов нет. Подключайте хоть сотню в день: мешать этому вправе только предел действующих сайтов и общий предел частоты запросов ключа.
  • Пояс, валюту и квоты сайта через API не меняют — только в кабинете: смена пояса и валюты меняет смысл уже посчитанных дат и денег.

Изменить сайт

curl -X PATCH https://analytics.daratech.ru/api/v1/sites/7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13 \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": [{"host": "shop.example.ru", "subdomains": true}, {"host": "mirror.example.ru"}]}'
  • Меняются только name и domains, по одному или оба; ответ — сайт после правки в поле site. Домены приводятся и проверяются так же, как при создании; domains заменяет список целиком, первый — основной.
  • Тег узнаёт новые домены сам в течение 30 секунд — переустанавливать код не нужно.
  • Только ключом аккаунта; сайт, где вы администратор по приглашению, изменить нельзя — 404, как и чужой.

Удалить и восстановить сайт

curl -X DELETE https://analytics.daratech.ru/api/v1/sites/7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13 \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "shop.example.ru"}'
  • Удаление подтверждается основным доменом в поле domain — так же, как в кабинете его вводят руками. Не тот домен — 422, и сайт остаётся на месте: одна опечатка в идентификаторе не должна снести живой сайт.
  • Данные не стираются сразу: сайт получает status: "deleted", приём хитов прекращается в течение минуты, а отчёты по нему больше не отдаются. В ответе приходит restore_until — до этого времени сайт можно вернуть. Потом данные удаляются, и вернуть их уже нельзя.
  • Повтор удаления безопасен: свой уже удалённый сайт отвечает 200 и той же строкой, срок восстановления не продлевается. Запрос, потерянный на таймауте, можно просто повторить.
  • Сайт, которого нет, и сайт чужого аккаунта отвечают одинаково — 404.
  • Заблокированный сайт через API не удаляется (403): сначала разберитесь с блокировкой в поддержке.
curl -X POST https://analytics.daratech.ru/api/v1/sites/7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13/restore \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"
  • Восстановление возвращает сайту status: "active" вместе со всеми данными, которые ещё не удалены. Тело запроса не нужно.
  • Сайт не удалён, срок восстановления истёк или очистка уже началась — 422 с пояснением в message.
  • Восстановление занимает слот в пределе числа сайтов: если после удаления вы завели новые сайты и упёрлись в предел, сначала освободите место.

Сайт запроса

GET /site отвечает про сайт запроса: ключу сайта — про его сайт, ключу аккаунта — про сайт из заголовка X-Dara-Site. Часовой пояс и валюта нужны, чтобы показывать даты и деньги отчётов так же, как их считает сервис, и не держать их копию в своих настройках; срок хранения — чтобы не просить период, за который данных уже нет; расход квоты объясняет sampled в отчёте.

curl https://analytics.daratech.ru/api/v1/site \
  -H "Authorization: Bearer $DARA_ANALYTICS_KEY"
{"site_uuid": "7b3c1e42-9f58-4d0a-8b61-2c4e5f7a9d13",
 "site_key": "as_XXXXXXXXXXXXXXXXXXXX",
 "name": "Магазин",
 "timezone": "Europe/Moscow",
 "currency": "RUB",
 "first_hit_at": "2026-01-14T09:20:11Z",
 "last_hit_at": "2026-09-26T11:59:03Z",
 "retention_months": 13,
 "quota": {"date": "2026-09-26", "hits_per_day": 1000000, "hits_today": 154302, "sampled_since": null},
 "network": {"state": "member", "open": true, "since": "2026-02-01T00:00:00Z"}}
Поле Значение
site_uuid идентификатор сайта: его ставят в заголовок X-Dara-Site и в адрес /sites/{site_uuid}
site_key ключ сайта as_… — тот же, что в коде тега; он публичный. По нему видно, про какой сайт пришёл ответ
name название сайта в кабинете
timezone пояс сайта: в нём считаются date_from и date_to отчётов и сутки в измерениях времени
currency валюта сайта, ISO 4217: в ней метрики денег
first_hit_at, last_hit_at первый и последний хит сайта; last_hit_at обновляется не чаще раза в минуту, null — данных ещё нет
retention_months сколько месяцев живут хиты и визиты. Период запроса можно просить до limits.period_months из GET /meta, но глубже этого срока данных нет — отчёт честно вернёт пустоту
quota.date сутки квоты, ГГГГ-ММ-ДД по Москве: квоты считаются московскими сутками, а не сутками сайта
quota.hits_per_day, quota.hits_today предел хитов в сутки и сколько их принято за эти сутки (роботы тоже)
quota.sampled_since с какого времени отчёты сайта считаются по выборке из-за квоты; null — выборки по квоте нет. Если отчёт пришёл с sampled: true, а здесь null, дело не в квоте, и повтор запроса может помочь
network.state участие в сети: member — участник, quarantine — карантин
network.open открыты ли сайту данные сети сейчас: участник и есть свежие хиты
network.since с какого времени сайт участник; null — ещё не был

Смена срока хранения задним числом не действует: у каждой записи свой срок, поставленный при приёме. После увеличения срока старые данные не возвращаются, после уменьшения — исчезают по своему сроку. Условия участия в сети и что делать с карантином — в разделе Сеть ДАРА.

MCP: подключить ИИ-ассистента

https://analytics.daratech.ru/api/v1/mcp — сервер MCP (Model Context Protocol): ИИ-ассистент, который его поддерживает, сам строит отчёты по вашему вопросу — «откуда пришли покупатели в сентябре и как это сравнить с августом» — и отвечает цифрами из ваших данных. Своих данных у MCP нет: каждый инструмент — это запрос к этому же API вашим ключом, с теми же правилами, лимитами и без персональных данных.

Как подключить. В настройках ассистента добавьте удалённый сервер MCP (транспорт Streamable HTTP) с адресом https://analytics.daratech.ru/api/v1/mcp. Дальше — один из двух способов входа.

  • Через кабинет (OAuth) — так подключаются коннекторы Claude, ChatGPT и другие ассистенты, где вставить ключ некуда. Ассистент сам откроет страницу ДАРА АНАЛИТИКИ: войдите в кабинет, проверьте название ассистента и адрес, куда он вернётся, и нажмите «Разрешить». Ассистент получит доступ на чтение ко всем сайтам вашего аккаунта, как ключ аккаунта. В кабинете, в разделе «Аккаунт → Ключи аккаунта», появится ключ «MCP: <название ассистента>» — отзовите его, и ассистент отключится сразу. Токены доступа живут час и обновляются ассистентом сам; если ассистентом не пользоваться 60 дней, подключите его заново.
  • По ключу — для ассистентов и программ, где можно задать заголовок: Authorization: Bearer ak_… (ключ сайта: ассистент работает с одним сайтом) или Authorization: Bearer aa_… (ключ аккаунта: ассистент видит ваши сайты и выбирает нужный). Лучше выпустить для ассистента отдельный ключ: его можно отозвать, не трогая остальных интеграций.

Токен OAuth открывает только то, что нужно ассистенту: инструменты MCP и методы чтения, которые они вызывают. Для остальных методов API он не подходит — нужен ключ.

Инструменты — только чтение:

Инструмент Что делает
list_sites сайты, доступные ключу: UUID, название, домены, пояс, валюта
get_site пояс, валюта, суточная квота и её расход, срок хранения
describe измерения и метрики для отчётов, коды целей, сегменты и воронки сайта
run_report отчёт по измерениям и метрикам за период, со сравнением периодов
run_funnel сохранённая воронка за период

Пределы строже, чем у API: ассистент за минуту способен заказать больше отчётов, чем человек за день, а сервер общий.

  • Не больше 15 вызовов инструментов в минуту на ключ сайта (60 — на ключ аккаунта) и один вызов за раз. Вызов тратит и обычные лимиты методов API, как любой запрос.
  • В отчёте — 20 строк по умолчанию, не больше 100; итоги и общее число строк приходят всегда, чтобы ассистент честно говорил «показаны 20 из 412».
  • Если числа посчитаны по выборке, ответ это помечает, и ассистенту прямо сказано предупредить вас.
  • Записи нет никакой: ни конверсий и заказов, ни сайтов, целей и сегментов. Выгрузки аудитории и карточки посетителя в MCP тоже нет: длинный список людей разговору не помогает, а портрет посетителя уехал бы в модель ассистента.

О данных в разговоре. Ответы инструментов попадают в контекст ассистента, а что он хранит и на чём учится — решают его правила, а не наши. Значения измерений — адреса и заголовки страниц, UTM-метки, рефереры — пишут посторонние люди; ассистент получает их как данные, а не как указания.

Обмен на уровне протокола — для тех, кто пишет свой клиент:

POST /api/v1/mcp HTTP/1.1
Host: analytics.daratech.ru
Authorization: Bearer ak_…
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
 "params": {"name": "run_report", "arguments": {"date_from": "2026-09-01", "date_to": "2026-09-30",
            "dimensions": ["source_type"], "metrics": ["visits", "revenue"]}}}
  • Одно сообщение JSON-RPC 2.0 на запрос, ответ — application/json; потока событий и сессий нет. Методы: initialize, ping, tools/list, tools/call. Редакции протокола — 2025-11-25, 2025-06-18 и 2025-03-26.
  • С ключом аккаунта сайт называется аргументом site инструмента — UUID сайта из list_sites.
  • Отказ API (неверная метрика, лимит) приходит ответом инструмента с isError: true и текстом причины — ассистент его прочитает и поправит запрос.

Примеры на PHP

<?php
function dara_analytics(string $method, string $path, ?array $body = null): array
{
    $ch = curl_init('https://analytics.daratech.ru/api/v1' . $path);
    $options = [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . getenv('DARA_ANALYTICS_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 3,
        CURLOPT_TIMEOUT => 30,
    ];
    if ($body !== null) {
        $options[CURLOPT_POSTFIELDS] = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    }
    curl_setopt_array($ch, $options);
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    if ($response === false || $status >= 400) {
        throw new RuntimeException('ДАРА АНАЛИТИКА: HTTP ' . $status . ' ' . ($response ?: curl_error($ch)));
    }
    return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
}

$result = dara_analytics('POST', '/conversions', ['conversions' => [
    ['id' => 'deal-7731', 'goal' => 'crm_deal_won', 'user_id' => 'u-501', 'value' => 12000],
]]);
echo 'Принято: ', $result['accepted'], "\n";

// При оформлении заказа, в запросе посетителя: id посетителя сохраняется вместе с заказом
$cookie = $_COOKIE['_dara_a'] ?? '';
$visitorId = is_string($cookie)
    && preg_match('/\A[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i', $cookie)
    ? strtolower($cookie)
    : null;

// Позже, в фоновой задаче или обработчике очереди, — отправка заказа
$order = [
    'transaction_id' => 'T-20931',
    'user_id' => 'u-501',
    'currency' => 'RUB',
    'revenue' => 5480,
    'products' => [
        ['id' => 'SKU-1017', 'name' => 'Платье миди', 'price' => 2490, 'qty' => 2],
        ['id' => 'SKU-2044', 'name' => 'Ремень', 'price' => 500, 'qty' => 1],
    ],
];
if ($visitorId !== null) {
    $order['visitor_id'] = $visitorId;
}
dara_analytics('POST', '/orders', ['orders' => [$order]]);

$report = dara_analytics('POST', '/reports', [
    'dimensions' => ['source_type'],
    'metrics' => ['visits'],
    'date_from' => '2026-09-01',
    'date_to' => '2026-09-14',
]);
foreach ($report['data'] as $row) {
    echo $row['dimensions'][0], ': ', $row['metrics'][0], "\n";
}

Примеры на Python

import os

import httpx

api = httpx.Client(
    base_url="https://analytics.daratech.ru/api/v1",
    headers={"Authorization": f"Bearer {os.environ['DARA_ANALYTICS_KEY']}"},
    timeout=30,
)

result = api.post("/conversions", json={"conversions": [
    {"id": "deal-7731", "goal": "crm_deal_won", "user_id": "u-501", "value": 12000},
]})
result.raise_for_status()
print("Принято:", result.json()["accepted"])

api.post("/orders", json={"orders": [{
    "transaction_id": "T-20931",
    "visitor_id": "0b7e6f8a-3c2d-4e5f-9a1b-2c3d4e5f6a7b",
    "currency": "RUB",
    "revenue": 5480,
    "products": [
        {"id": "SKU-1017", "name": "Платье миди", "price": 2490, "qty": 2},
        {"id": "SKU-2044", "name": "Ремень", "price": 500, "qty": 1},
    ],
}]}).raise_for_status()

report = api.post("/reports", json={
    "dimensions": ["source_type"],
    "metrics": ["visits"],
    "date_from": "2026-09-01",
    "date_to": "2026-09-14",
})
report.raise_for_status()
for row in report.json()["data"]:
    print(row["dimensions"][0], row["metrics"][0])

Как встроить

  • Очередь на сервере. Копите конверсии и заказы в очереди и отправляйте пачками. При сетевой ошибке, 429 или 5xx повторяйте ту же пачку с паузой: дубли по id конверсии и transaction_id заказа не учитываются.
  • Таймауты. Не держите ответ посетителю в ожидании API: отправка в аналитику не должна задерживать оформление заказа.
  • Ключ. Храните ключ в секретах сервера и не пишите в логи целиком — достаточно префикса.

Машинное описание методов, полей и схем — OpenAPI: GET https://analytics.daratech.ru/api/v1/openapi.json, без авторизации. Его отдаёт сам API, поэтому оно всегда совпадает с тем, что сервис принимает.