# Начало работы с REST API

Sales Ninja предоставляет единый внешний REST API. Он покрывает три задачи:

  1. Применение вариантов стека «Сайт» (персонализации, A/B-тесты, действия по правилам) — runtime-методы из мобильного приложения, SSR или бэкенда: какой вариант показать пользователю. На сайте тот же контур закрывает JS API onPersonalization.
  2. Передача конверсий — runtime-метод, которым вы программно сообщаете Sales Ninja, что пользователь достиг бизнес-цели. Применяется там, где конверсия происходит не на сайте (CRM, мобильное приложение, бэкенд, импорт исторических данных).
  3. Управление сущностями — программный CRUD над персонализациями, A/B-тестами, опросами, rule-based действиями, моделируемыми конверсиями, аудиторными сегментами, антиботами, smart-фидами. Сюда же относится сборка условий показа V2 и запуск отчётов статистики.

Все три блока работают на одних и тех же «рельсах»: общий хост, общая авторизация (X-SN-TOKEN), общее версионирование. Формат ошибок различается между runtime-эндпоинтами (/public/{source}/v1.0/…) и management/analytics (/public/api/v1/…) — см. ниже. Один токен открывает доступ ко всем эндпоинтам в рамках одного проекта.

# Базовые URL

Группа Префикс
Runtime https://api.sales-ninja.me/public/{source}/v1.0/
Management https://api.sales-ninja.me/public/api/v1/manage/
Конструкторы https://api.sales-ninja.me/public/api/v1/conditions/ и analytics/
Статистика https://api.sales-ninja.me/public/api/v1/analytics/

Параметр {source} в runtime-маршрутах определяет контекст вызова:

Значение Назначение
mobile Мобильное приложение
backend Серверный код, SSR, CRM-импорт

Регистр не важен (Mobile = mobile). Любое другое значение → 400 Bad Request.

# Авторизация

Каждый запрос обязан содержать заголовок X-SN-TOKEN с токеном проекта:

X-SN-TOKEN: 9f5a4c1d-7b8e-4f3a-9d2c-1e7b0a4c5d6f

Токен уникален на уровне проекта и виден в настройках проекта в админке Sales Ninja:

Где найти токен

Правила:

  • Один токен = один проект. Все запросы с этим токеном считаются выполненными в контексте этого проекта.
  • projectId нигде не передаётся в теле или query. Он всегда выводится из токена; любое значение в теле игнорируется. Это исключает класс ошибок, когда токен от одного проекта используется для записи в другой.
  • Токен надо хранить в секрете. Не публиковать в клиентском JS, не коммитить в репозиторий — он даёт право на запись и удаление сущностей.
  • Если заголовок X-SN-TOKEN отсутствует или не парсится как GUID — 401 Unauthorized (текстовое тело).
  • Если токен передан, но не найден / не принадлежит проекту — 401 Unauthorized с JSON { exceptionType, message, ticketId } (см. Runtime-ошибки).
  • Если проект приостановлен (suspended/archived) — 403 Forbidden с code: "ProjectSuspended" (на management/analytics).

# Формат ошибок

# Management и Analytics (/public/api/v1/…)

Ошибки возвращаются по стандарту RFC 7807 (Problem Details) с Content-Type: application/problem+json:

{
  "type": "about:blank",
  "status": 422,
  "title": "Unprocessable Entity",
  "detail": "Title must be non-empty.",
  "instance": "/public/api/v1/manage/personalizations",
  "traceId": "0HMV3K9T6QJ4F:00000003",
  "code": "ValidationFailed",
  "hint": "Set request body 'title' to a non-empty string.",
  "paramName": "title",
  "jsonPath": "$.title",
  "validEnumValues": ["Equal", "NotEqual", "Greater", "Less", "Contains"]
}

Поля:

Поле Что внутри
status Числовой HTTP-статус.
title Краткая фраза ("Bad Request", "Unprocessable Entity", …).
detail Человекочитаемое описание конкретной проблемы.
instance Путь запроса.
traceId Идентификатор запроса. Скиньте его в поддержку, если нужна помощь — по нему быстро находят запрос в логах.
code Стабильный машинный код ошибки (см. ниже). Не меняется между релизами.
hint Подсказка, что сделать дальше.
paramName Имя параметра, если ошибка относится к одному полю.
jsonPath JSON-путь до проблемного узла, если тело сложное.
validEnumValues Если поле — enum, и значение вне списка, здесь перечислены допустимые варианты.

Стабильные значения code:

code Что значит
ValidationFailed Тело прошло парсер, но не прошло валидацию.
InvalidArgument Некорректный параметр (query/route).
InvalidJson Тело не парсится как JSON.
InvalidOperation Операция логически невозможна в текущем состоянии (например, удалять то, на что есть ссылки).
WriteRequiresAuthenticatedUser Эндпоинт требует авторизованного пользователя (внутренний случай, не должен встречаться на Public API).
WriteRequiresEditorRole Роль токена недостаточна (на Public API сейчас не встречается — один уровень доступа).
Canceled Запрос был отменён клиентом (closed connection).
InternalError Неожиданная ошибка сервера. Скиньте traceId в поддержку.
ProjectSuspended Проект не активен.

# Runtime-ошибки {#runtime-ошибки}

Эндпоинты POST /public/{source}/v1.0/personalizations/apply, …/ab-tests/apply, …/goals/reach возвращают ошибки в формате JSON (не RFC 7807):

{
  "ticketId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "exceptionType": "badrequest",
  "message": "Too many parameters. Maximum 48 keys allowed."
}
Поле Описание
ticketId Идентификатор для поддержки (аналог traceId)
exceptionType Тип ошибки (badrequest, forbidden, invalidjson, apierror, …)
message Описание проблемы

Типовые HTTP-статусы runtime:

Ситуация HTTP
Невалидный токен 401
Ошибка валидации тела / лимит params 400
Невалидный {source} 400
Необработанная ошибка сервера 500 (stackTrace только в dev)

# Версионирование

API версионируется через URL: …/v1.0/… (runtime) и …/v1/… (management). Внутри одной версии возможны только аддитивные изменения (новые опциональные поля, новые опциональные параметры, новые опциональные эндпоинты). Любое ломающее изменение — переименование, изменение типа, удаление поля, изменение семантики — выезжает только под новой версией (v2/). Старая версия будет жить параллельно, пока есть пользователи; о выводе из эксплуатации будет анонс заранее.

# Соглашения по форматам

  • camelCase во всём, что на проводе (request/response).
  • Перечисления (enum) передаются строками: "Equal", "And", "Web". Регистр имеет значение.
  • Идентификаторы — UUID в формате 8-4-4-4-12 (например, 8f3a1e4b-7c4d-4d5e-9aaf-2c1b6d3e5f01). Server-owned: при POST всегда генерируется сервером, тело id игнорируется. При PUT id берётся из URL.
  • Даты — ISO 8601 в UTC (2026-04-15T18:23:00Z).
  • Runtime params — плоский словарь строк ("revenue": "12350"), максимум 48 ключей. Используется в personalizations/apply, ab-tests/apply, goals/reach. Числа и вложенные объекты не поддерживаются.
  • Management customUserScopeParams / customPageScopeParams — отдельный контракт web-бандла; в runtime API не применяется — передавайте нужные ключи через params.
  • Поля, отсутствующие в теле, на PUT означают «обнулить / удалить вложенный объект». На POST отсутствующее поле = «значение по умолчанию» / «без вложений». Для частичного обновления используйте PATCH (там, где он поддерживается).

# Каталог эндпоинтов

# Блок 1. Применение персонализаций и A/B-тестов (runtime)

# Блок 2. Передача конверсий

Каталог сторонних платформ. Если вам нужно подключить Яндекс.Метрику / Директ / AppMetrica, AmoCRM, CallTouch, Smartis, UIS, VK Ads, Telegram или AI-агента через MCP — это отдельный раздел документации, Каталог интеграций. Там же — какие сторонние платформы можно использовать как сигнал для ML и как JS-действие в персонализациях.

# Блок 3. Управление сущностями

# Минимальный пример

curl -X GET "https://api.sales-ninja.me/public/api/v1/manage/personalizations?pageSize=5" \
  -H "X-SN-TOKEN: 9f5a4c1d-7b8e-4f3a-9d2c-1e7b0a4c5d6f"
const SN_TOKEN = process.env.SN_TOKEN;

const res = await fetch(
  'https://api.sales-ninja.me/public/api/v1/manage/personalizations?pageSize=5',
  { headers: { 'X-SN-TOKEN': SN_TOKEN } }
);
if (!res.ok) {
  const problem = await res.json();
  throw new Error(`${problem.code}: ${problem.detail} (traceId=${problem.traceId})`);
}
const { items } = await res.json();
import os, requests

SN_TOKEN = os.environ["SN_TOKEN"]
r = requests.get(
    "https://api.sales-ninja.me/public/api/v1/manage/personalizations",
    params={"pageSize": 5},
    headers={"X-SN-TOKEN": SN_TOKEN},
    timeout=15,
)
if not r.ok:
    p = r.json()
    raise RuntimeError(f"{p['code']}: {p['detail']} (traceId={p['traceId']})")
items = r.json()["items"]
using var http = new HttpClient { BaseAddress = new Uri("https://api.sales-ninja.me/") };
http.DefaultRequestHeaders.Add("X-SN-TOKEN", Environment.GetEnvironmentVariable("SN_TOKEN"));

using var res = await http.GetAsync("public/api/v1/manage/personalizations?pageSize=5");
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadAsStringAsync();