Формула ИИ для разработчиков Справочник API Каталог ошибок OpenAPI
Разделы

#API событий

Статус: Планируется. Метод POST /analytics/events проектируется и пока не доступен. Страница фиксирует согласованный контракт; значения, помеченные «предварительно», уточнятся в OpenAPI 3.1. До выхода используйте JS-трекер и модули.

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

#Содержание

  1. Когда использовать
  2. Доступ
  3. Запрос
  4. Типы событий
  5. Поля события
  6. Ответ
  7. Ошибки
  8. Дедупликация и время
  9. Примеры
  10. Рецепты
  11. Связь с диалогами чата и атрибуцией лидов
  12. Заметки
  13. Открытые вопросы

#Когда использовать

СитуацияЧто выбрать
Одностраничное приложение (SPA)этот метод; JS-трекер SPA не поддерживает
Мобильное приложениеэтот метод через ваш сервер
Нужны события: заявка, регистрация, покупкаэтот метод
Только посещения страниц на обычном сайтеJS-трекер проще
Оплаты и сделки в CRM, не связанные с браузеромофлайн-конверсии

#Доступ

Метод и путьPOST https://<домен ячейки>/api/public/v1/analytics/events
АутентификацияAuthorization: Bearer <ключ> (Аутентификация)
Областьanalytics:write (Аутентификация); платных областей здесь нет
Платнонет
Предел120 запросов в минуту на ключ и арендатора; в пакете не более 100 событий (Соглашения)
Персональные данныене принимаются. Область analytics:write допускает только псевдонимные идентификаторы. Почта и телефон в properties отклоняются с validation_failed
Идемпотентностьзаголовок Idempotency-Key (срок 24 часа, Соглашения) и поле event_id у каждого события
Ключ в браузеренельзя. Ключ — серверный секрет; отправляйте события через свой сервер

Адреса ячеек: RU https://formula-cream.pro, EN https://wfacademy.org, ID https://wfacademy.id. Ключ действует только в ячейке, где выпущен.

#Запрос

Заголовки

ЗаголовокОбязателенЗначение
AuthorizationдаBearer <ключ>
Content-Typeдаapplication/json
Idempotency-Keyдауникальная строка (обычно UUID v4); повтор с тем же ключом и телом возвращает прежний результат

Тело

ПолеТипОбязательноОграниченияПример
eventsarrayдаот 1 до 100 событий; тело ≤ 256 КБсм. ниже

Один пакет может смешивать типы событий.

{
  "events": [
    {
      "event_id": "evt_2026_10_11_0001",
      "type": "page_view",
      "session_id": "sess_EXAMPLE_01",
      "anonymous_id": "anon_EXAMPLE_01",
      "page_key": "/blog/emulsions",
      "title": "Как сделать эмульсию",
      "referrer": "https://www.google.com/",
      "utm": { "source": "google", "medium": "cpc", "campaign": "emulsion-course", "content": "banner-a", "term": "эмульсия курс" },
      "occurred_at": "2026-10-11T08:15:30Z",
      "consent": true
    }
  ]
}

#Типы событий

typeСмыслОсобые поля
page_viewпросмотр страницы или экранаpage_key, title, referrer
eventцель или действие (клик по кнопке, регистрация)properties (имя цели — в properties.name, предварительно)
lead_formотправка формы заявкиproperties без контактов
conversionдействие с суммой (оплата, подписка)value в формате денег

#Поля события

ПолеТипОбязательноОграниченияПример
event_idstringдауникален в пределах интеграции; служит ключом дедупликацииevt_2026_10_11_0001
typestringдаpage_view | event | lead_form | conversionpage_view
session_idstringдаидентификатор сессии на стороне клиента; длина уточнитсяsess_EXAMPLE_01
anonymous_idstringдапсевдоним посетителя; не почта и не телефонanon_EXAMPLE_01
user_idstringнетваш внутренний псевдонимный id; не контактuser_EXAMPLE_42
utmobjectнетsource, medium, campaign, content, term — пять меток UTM{"source":"google"}
click_idsobjectнетметки кликов рекламных систем, например gclid, yclid, fbclid; точный перечень уточнится{"yclid":"123"}
page_keystringдля page_viewпуть без языкового префикса, например /blog/emulsions; главная — /home/blog/emulsions
titlestringнетзаголовок страницыКак сделать эмульсию
referrerstringнетадрес источникаhttps://www.google.com/
occurred_atstringнетISO 8601 UTC; по умолчанию время приёма; не раньше, чем 7 дней назад (предварительно)2026-10-11T08:15:30Z
consentbooleanдасогласие посетителя на аналитику на момент событияtrue
propertiesobjectнетпроизвольные пары «ключ — скаляр»; без персональных данных{"plan":"pro"}
valueobjectдля conversion{"amount":"4990.00","currency":"RUB"}; сумма строкой{"amount":"4990.00","currency":"RUB"}

События с consent: false принимаются, но не используются для пользовательской атрибуции (как именно, уточнится — см. открытые вопросы).

#Ответ

Успех — 202 Accepted: пакет принят, агрегация данных идёт после ответа. Это не асинхронная операция с объектом operation: итог приёма по каждому событию уже в теле ответа, в том же порядке, что и в запросе.

{
  "accepted": 2,
  "rejected": 1,
  "results": [
    { "event_id": "evt_2026_10_11_0001", "status": "accepted" },
    { "event_id": "evt_2026_10_11_0002", "status": "duplicate" },
    {
      "event_id": "evt_2026_10_11_0003",
      "status": "rejected",
      "error": { "code": "validation_failed", "field": "/events/2/properties/email", "message": "Контактные данные не принимаются" }
    }
  ]
}
ПолеТипОписание
acceptedintegerколичество принятых событий (дубликаты не считаются)
rejectedintegerколичество отклонённых
results[].statusstringaccepted, duplicate, rejected
results[].errorobjectдля отклонённых: code, field (JSON Pointer), message — тот же вид, что у элемента errors[] в Соглашениях

Если невалидно сам пакет (не JSON, нет events, больше 100 событий), возвращается ошибка всего запроса, и ни одно событие не принимается.

#Ошибки

Формат — application/problem+json; общий каталог кодов в Соглашениях API.

HTTPКодКогда
401invalid_api_keyключ неверен, отозван или истёк
403scope_missingу ключа нет analytics:write
403ip_not_allowedзапрос с адреса вне списка IP интеграции
403feature_not_in_planлицензия не содержит api_access
409idempotency_conflictтот же Idempotency-Key с другим телом
413payload_too_largeтело больше 256 КБ
422validation_failedпакет не прошёл проверку: не JSON, нет events, больше 100 событий (список полей — в errors)
429rate_limitedпревышен предел частоты
503service_unavailableхранилище пределов временно недоступно

#Дедупликация и время

#Примеры

#curl

curl -X POST 'https://formula-cream.pro/api/public/v1/analytics/events' \
  -H 'Authorization: Bearer sap_ru_live_EXAMPLE0KEYI_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: batch-2026-10-11-0001' \
  -d '{
    "events": [
      {
        "event_id": "evt_2026_10_11_0001",
        "type": "event",
        "session_id": "sess_EXAMPLE_01",
        "anonymous_id": "anon_EXAMPLE_01",
        "page_key": "/pricing",
        "properties": { "name": "signup_clicked" },
        "occurred_at": "2026-10-11T08:15:30Z",
        "consent": true
      }
    ]
  }'

#JavaScript (на вашем сервере)

// Node.js 18+. Ключ хранится в переменной окружения сервера, не в браузере.
const response = await fetch('https://wfacademy.org/api/public/v1/analytics/events', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    events: [{
      event_id: crypto.randomUUID(),
      type: 'conversion',
      session_id: 'sess_EXAMPLE_01',
      anonymous_id: 'anon_EXAMPLE_01',
      value: { amount: '49.00', currency: 'GBP' },
      consent: true,
    }],
  }),
});
if (response.status !== 202) throw new Error(`API: ${response.status}`);
console.log(await response.json());

#Python

import os, uuid, requests

response = requests.post(
    "https://wfacademy.id/api/public/v1/analytics/events",
    headers={
        "Authorization": f"Bearer {os.environ['API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"events": [{
        "event_id": str(uuid.uuid4()),
        "type": "page_view",
        "session_id": "sess_EXAMPLE_01",
        "anonymous_id": "anon_EXAMPLE_01",
        "page_key": "/home",
        "consent": True,
    }]},
    timeout=5,
)
response.raise_for_status()

#PHP

<?php
$payload = json_encode(['events' => [[
    'event_id'     => bin2hex(random_bytes(16)),
    'type'         => 'lead_form',
    'session_id'   => 'sess_EXAMPLE_01',
    'anonymous_id' => 'anon_EXAMPLE_01',
    'properties'   => ['form' => 'callback'],
    'consent'      => true,
]]], JSON_UNESCAPED_UNICODE);

$ch = curl_init('https://formula-cream.pro/api/public/v1/analytics/events');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 5,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

#Рецепты

#Одностраничное приложение

  1. В браузере при загрузке создайте anonymous_id (случайная строка) и сохраняйте его на стороне клиента; на каждые 30 минут неактивности заводите новый session_id.
  2. При каждой смене маршрута отправляйте событие page_view на ваш серверный адрес (например, /analytics), а не напрямую в Формула ИИ.
  3. Ваш сервер добавляет event_id, проверяет consent и пересылает пакет в Формула ИИ с ключом.
  4. UTM читайте один раз при первой загрузке и передавайте во всех событиях сессии.

Пересылайте события пакетами до 100 штук раз в несколько секунд — это дешевле, чем по одному.

#Мобильное приложение

  1. Приложение хранит anonymous_id в защищённом хранилище и ведёт сессии по правилам платформы.
  2. Экраны — page_view с page_key вида /screen/checkout.
  3. Оффлайн-очередь на устройстве допускается до 7 дней (окно occurred_at); event_id делает повторную отправку безопасной.
  4. События уходят на ваш бэкенд, который пересылает их в Формула ИИ. Ключ в приложение не встраивайте.

#Связь с диалогами чата и атрибуцией лидов

Источник лида Формула ИИ определяет по сессии. Когда посетитель пишет в чат сайта, сессия связывается с диалогом, и в карточке лида сохраняется атрибуция: источник, UTM, страница входа, число визитов и просмотров страниц, основа расчёта (basis: funnel, form или none).

Планируется передавать session_id из API событий в виджет, чтобы события из ваших систем вошли в историю визита и в атрибуцию лида (basis: funnel). Сейчас связь строится по сессии JS-трекера; для событий API она ещё не определена (см. открытые вопросы).

#Заметки

#Открытые вопросы

  1. К владельцу. Окно времени. «Не старше 7 дней» взято из задания на документацию; проектные документы его не фиксируют.
  2. К владельцу. Семантика consent: false: отбрасывать событие или хранить без идентификаторов.
  3. Техническое. Формат event_id, длина session_id, перечень click_ids, имя цели в типе event не зафиксированы.
  4. Техническое. Связь session_id с диалогом виджета. По проекту планируется «sid в iframe → атрибуция basis: funnel», но контракт не определён.
  5. Техническое. Формат ответа (accepted/rejected/results) — предварительный.
  6. Техническое. Место в этапах: запись событий нигде не отнесена к конкретному этапу выпуска.