# Начало работы с 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), общий формат ошибок (RFC 7807), общее версионирование. Один токен открывает доступ ко всем эндпоинтам в рамках одного проекта.

# Базовые 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-маршрутах — это идентификатор источника вызова, чтобы Sales Ninja понимал, откуда пришла информация (мобильное приложение, серверный код, импорт из CRM и т.п.). Допустимые значения возвращает служба поддержки; неизвестный source отдаёт 400.

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

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

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

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

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

Правила:

  • Один токен = один проект. Все запросы с этим токеном считаются выполненными в контексте этого проекта.
  • projectId нигде не передаётся в теле или query. Он всегда выводится из токена; любое значение в теле игнорируется. Это исключает класс ошибок, когда токен от одного проекта используется для записи в другой.
  • Токен надо хранить в секрете. Не публиковать в клиентском JS, не коммитить в репозиторий — он даёт право на запись и удаление сущностей.
  • Если токен невалиден или отсутствует — 401 Unauthorized с application/problem+json.
  • Если проект приостановлен (suspended/archived) — 403 Forbidden с code: "ProjectSuspended".

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

Все ошибки возвращаются по стандарту 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 Проект не активен.

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

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).
  • Денежные суммы и метрики в customUserScopeParams / customPageScopeParams передавайте строками ("12350", не 12350) — это исторический контракт runtime-эндпоинтов.
  • Поля, отсутствующие в теле, на 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();