# Получение персонализаций (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-ошибки в разделе «Начало работы».
# Что делать с ответом
- Для каждого элемента
items[]применитеsubscriptionParamsна стороне клиента (UI, конфиг, push-payload и т.п.). - Запомните
optionIdиversion— они понадобятся для корректной атрибуции. - При достижении бизнес-цели вызовите 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();
}