# Получение персонализаций (runtime)

Метод применяется без веб-бандла: мобильное приложение или backend-интеграция.

Три продукта в админке — общий runtime-контур

В админке персонализации, A/B-тесты и действия по правилам — разные разделы (обзор).

На backend и в ответе personalizations/apply они обрабатываются единым контуром применения вариантов (те же поля personalizationId / optionId, subscriptionParams). Отдельный эндпоинт ab-tests/apply — для выборки только A/B без смешивания с персонализациями в одном ответе.

На сайте с бандлом всё это также идёт через onPersonalization.

Sales Ninja по переданному контексту (customerId, params, ip) возвращает, какие варианты персонализаций следует применить для указанных ID.

# Описание метода

POST https://api.sales-ninja.me/public/{source}/v1.0/personalizations/apply
Headers:
  X-SN-TOKEN: <ваш токен проекта>
  Content-Type: application/json

# Параметр {source}

Допустимые значения пути (регистр не важен):

Значение Когда использовать
mobile Запрос из мобильного приложения
backend Запрос из серверного кода, SSR, CRM-импорта и т.п.

Любое другое значение (server, web, …) → 400 Bad Request.

Поведение IP зависит от {source}:

  • mobile — если ip в теле не передан или пустой, берётся IP из заголовков соединения (X-Forwarded-For / remote address).
  • backend — используется только ip из тела; заголовки соединения не подставляются.

Если ip передан в JSON непустой строкой — он имеет приоритет над заголовками (для обоих source).

# Тело запроса

{
  "personalizationIds": [
    "50e62336-9b35-4ab6-b77a-a26b99c69883",
    "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  ],
  "customerId": "user-42-from-your-system",
  "ip": "185.237.80.128",
  "params": {
    "segment": "premium",
    "revenue": "12350"
  }
}

projectId в теле передавать не нужно — он выводится из токена.

# Пример минимального запроса

{
  "personalizationIds": ["50e62336-9b35-4ab6-b77a-a26b99c69883"],
  "customerId": "user-42-from-your-system"
}

# Поля запроса

# personalizationIds (обязательно)

Массив UUID персонализаций, для которых нужно получить варианты. Минимум один элемент.

Возвращаются только персонализации со скоупом Mobile или Backend, соответствующим {source}. Web-скоуп в этом эндпоинте не участвует.

# customerId (обязательно)

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

Если у вас уже есть веб-бандл — можно взять id через await ninja('getCustomerIdAsync'). Для чисто серверной интеграции используйте свой стабильный id (например, id пользователя в вашей CRM).

# ip (опционально)

IP клиента. Рекомендуется передавать явно для гео-таргетинга и обогащения аналитики. См. правила {source} выше.

# params (опционально)

Плоский словарь произвольных параметров ключ → значение для условий таргетинга (Custom Params) и обогащения статистики.

Правила:

  • не более 48 ключей в одном запросе;
  • значения — только строки ("12350", не 12350);
  • вложенные объекты не поддерживаются.

UTM-метки, тип устройства, номер сессии и прочий web-контекст не передаются отдельными полями — если они нужны для таргетинга, настройте соответствующие Custom Params в условиях показа персонализации и передайите их через params (например, "utmSource": "google").

# Ответ

{
  "items": [
    {
      "personalizationId": "50e62336-9b35-4ab6-b77a-a26b99c69883",
      "optionId": "47b6fd00-29be-4d8c-adc0-0d9fa525745f",
      "isControl": false,
      "version": 19,
      "validUntil": "2026-04-30T19:27:48Z",
      "subscriptionParams": [
        { "key": "fontSize", "value": "large" },
        { "key": "bannerColor", "value": "#00AA00" }
      ]
    }
  ]
}

Если ни одна персонализация не сработала — items будет пустым массивом [] (это нормальный ответ, не ошибка).

# Поля элемента items[]

Поле Тип Описание
personalizationId UUID ID персонализации
optionId UUID ID выбранного варианта (опции)
isControl bool Контрольный вариант
version int Версия персонализации на момент решения
validUntil datetime UTC До какого времени решение валидно
subscriptionParams array Параметры подписки из модификаций варианта (key / value)

subscriptionParams — это содержимое, которое ваш клиент должен применить (текст, цвет, конфиг и т.п.), если в варианте настроены действия «Параметр для подписки».

# Ошибки и валидация

Ситуация HTTP Что проверить
Нет или невалидный X-SN-TOKEN 401 Токен из настроек проекта
Неверный {source} в URL 400 Только mobile или backend
Нет personalizationIds / customerId 400 Оба поля обязательны, массив ids не пустой
Больше 48 ключей в params 400 Сократите словарь
Невалидный формат customerId 400 Непустая строка
Невалидный JSON / число вместо строки в params 400 Все значения params — строки

Формат тела ошибки runtime-эндпоинтов — см. Runtime-ошибки в разделе «Начало работы».

# Что делать с ответом

  1. Для каждого элемента items[] примените subscriptionParams на стороне клиента (UI, конфиг, push-payload и т.п.).
  2. Запомните optionId и version — они понадобятся для корректной атрибуции.
  3. При достижении бизнес-цели вызовите POST /goals/reach с тем же customerId — система привяжет конверсию к активным экспериментам.

# Примеры кода

curl -X POST "https://api.sales-ninja.me/public/mobile/v1.0/personalizations/apply" \
  -H "X-SN-TOKEN: $SN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "personalizationIds": ["50e62336-9b35-4ab6-b77a-a26b99c69883"],
    "customerId": "user-42",
    "ip": "185.237.80.128",
    "params": { "segment": "premium" }
  }'
import os, requests

SN_TOKEN = os.environ["SN_TOKEN"]
SOURCE = "mobile"

def fetch_personalizations(customer_id: str, personalization_ids: list[str], params: dict | None = None) -> dict:
    r = requests.post(
        f"https://api.sales-ninja.me/public/{SOURCE}/v1.0/personalizations/apply",
        headers={"X-SN-TOKEN": SN_TOKEN},
        json={
            "personalizationIds": personalization_ids,
            "customerId": customer_id,
            "params": params or {},
        },
        timeout=15,
    )
    r.raise_for_status()
    return r.json()
async function fetchPersonalizations({ customerId, personalizationIds, params = {} }) {
  const res = await fetch(
    `https://api.sales-ninja.me/public/mobile/v1.0/personalizations/apply`,
    {
      method: 'POST',
      headers: {
        'X-SN-TOKEN': process.env.SN_TOKEN,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ personalizationIds, customerId, params }),
    }
  );
  if (!res.ok) {
    throw new Error(`apply failed: ${res.status} ${await res.text()}`);
  }
  return res.json();
}