# Свой алгоритм рекомендаций через API (BYOA)

Если у вас есть собственный алгоритм рекомендаций, подключите его к Gravity Field через **BYOA — Bring Your Own Algorithm**. В этом сценарии Gravity Field вызывает ваш API в момент запроса рекомендаций, получает список товаров и использует его в рекомендательной стратегии.

Эта инструкция поможет разработчикам подготовить API и данные для подключения. Формат запроса и ответа согласуется с командой Gravity Field: можно подключить существующий API, настроив соответствие полей. Примеры ниже — рекомендуемый формат, а не обязательная схема для всех интеграций.

Подключение проходит вместе с командой Gravity Field. Заранее рассчитанные рекомендации и обучение модели в этой инструкции не рассматриваются.

## Как работает подключение

```text
Сайт или приложение запрашивает рекомендации
                    │
                    ▼
Gravity Field вызывает API вашего алгоритма
                    │  контекст, идентификаторы, количество кандидатов
                    ▼
Ваш API возвращает упорядоченный список SKU
                    │
                    ▼
Gravity Field загружает карточки из фида,
применяет фильтры и правила стратегии
                    │
                    ▼
Сайт или приложение получает рекомендации
```

| Участник | Ответственность |
| :--- | :--- |
| Команда клиента | Алгоритм, работа API под нагрузкой, соответствие идентификаторов и обновление товарного фида |
| Команда Gravity Field | Согласование формата, настройка вызова API, регистрация алгоритма и подключение к нужной секции и стратегиям |
| Gravity Field | Получение кандидатов, загрузка карточек из каталога, применение настроек стратегии и стандартная обработка событий для аналитики |

Запрос к вашему API идёт с серверов Gravity Field. Браузер покупателя не обращается к нему напрямую. Рекомендации можно использовать в Web, API, Hybrid и SDK-интеграциях; доступность нужного контекста проверяется при подключении.

## Идентификаторы товаров и фид

### SKU должен совпадать с фидом

Каждый товар в ответе вашего API должен иметь идентификатор, **точно совпадающий с `sku` в товарном фиде нужной секции Gravity Field**. Тот же SKU используйте в контексте страницы, событиях просмотра, корзины и покупки.

- Передавайте ID строкой: `"001234"`, а не `1234`, чтобы сохранить ведущие нули.
- Сохраняйте регистр и значение целиком: `"SKU-123"` и `"sku-123"` — разные ключи.
- Не возвращайте вместо SKU внутренний ID модели, ID группы или код другого каталога.
- Если модель работает с другим ключом, преобразуйте его в SKU фида перед отправкой ответа.

Например, модель использует числовой ID `987`, а в фиде этот товар имеет `sku: "shoe-00987"`. В ответе API нужен `"shoe-00987"`. Настройка соответствия полей позволяет прочитать ID из поля `product_id` вместо `sku_id`, но **не преобразует его значение**.

Неизвестный SKU пропускается при загрузке карточек. Если все кандидаты отсутствуют в фиде, алгоритм не даст товаров для показа.

### Группа товара не заменяет SKU

`group_id` объединяет варианты одного товара: например, размеры или цвета. API должен возвращать конкретный SKU, даже если модель ранжирует группы. Возвращать `group_id` дополнительно можно, но для группировки Gravity Field использует значение из фида.

Внешняя выдача проходит удаление повторов по группе: из нескольких кандидатов с одним `group_id` остаётся один. В товарном контексте также исключаются товары из группы текущей карточки, а в корзине — из групп товаров корзины. Учитывайте это при подготовке кандидатов.

### Фид нужен даже при собственном алгоритме

Ваш API выбирает товары и задаёт их порядок. **Карточки для показа Gravity Field получает из своего каталога**: название, URL, изображение, цену, наличие и другие настроенные поля.

Синхронизируйте фид до подключения и поддерживайте его актуальность. Дополнительные поля карточки в ответе алгоритма не заменяют данные фида. Товары без наличия и кандидаты, не прошедшие правила стратегии, могут быть исключены из выдачи.

📖 [Синхронизация товарного фида](./products_catalogues/general_reqs.md), [региональные данные](./products_catalogues/multi_language.md).

## Идентификаторы пользователей

Для персональных рекомендаций согласуйте **один и тот же ключ пользователя для обучения модели и вызова API**. Если модель знает пользователя как `"customer-42"`, именно этот ключ должен поступать в её API. Наличие другого идентификатора в Gravity Field само по себе не создаёт соответствие с пользователем в вашей системе.

| Идентификатор | Назначение | Что учесть для BYOA |
| :--- | :--- | :--- |
| Внешний ID клиента (`extUid`) | Пользователь в вашей системе | В API V2 задаётся через `user.custom`; в общей серверной интеграции встречается как `user.id`. В примерах ниже передаётся как `external_user_id` |
| UID Gravity Field | Внутренний профиль пользователя | Это другое пространство идентификаторов. Используйте его в модели только при согласованном сопоставлении |
| CUID | Объединение профилей между устройствами и каналами | Задаётся через события идентификации. Текущий адаптер BYOA не передаёт CUID автоматически |
| ID сессии | Контекст текущей сессии | Может быть полезен для сессионных рекомендаций, но не заменяет постоянный ключ клиента |
| ID устройства или браузера | Анонимный контекст устройства | Передача согласуется отдельно; не считайте его постоянным ID покупателя между устройствами |

### Как выбрать поле пользователя

В текущем адаптере доступно поле `user_id`, которое сначала выбирает внешний ID, а при его отсутствии — UID Gravity Field. Поэтому **не считайте любой `user_id` идентификатором из вашей CRM или обучающей выборки**.

Для новых подключений рекомендуем отдельное поле `external_user_id`, связанное только с внешним ID клиента. Если нужен и UID Gravity Field, согласуйте для него отдельное поле. Это позволяет различать ключи без догадок по формату строки.

Проверьте передачу внешнего ID в каждом нужном канале: Web, API или SDK. Отправка `login-v1` с CUID для склейки профиля не означает, что этот CUID автоматически появится в запросе к алгоритму. Если модель использует именно CUID, согласуйте способ его передачи или сопоставления до запуска.

### Если пользователь неизвестен

API должен корректно работать, когда внешний ID отсутствует или пользователь ещё неизвестен модели. Предусмотрите подбор по товару, корзине или другой неперсональной логике. Если рекомендаций нет, верните пустой список.

Не подставляйте один фиктивный ID для всех анонимных посетителей. Передачу ID сессии или устройства, если они нужны модели, согласуйте отдельно. Полная история действий и признаки профиля не передаются автоматически вместе с ID.

📖 [Идентификация и омниканальность](./identification.md), [поля пользователя в API V2](./api_integration/v2/personalization/user.md).

## Запрос к вашему API

Для нового подключения подготовьте HTTPS endpoint, принимающий `POST` с JSON. Адрес, имена полей, вложенность и поддерживаемые контексты согласуйте с командой Gravity Field.

API должен только рассчитывать или читать рекомендации, без изменения корзины, заказов или профиля покупателя. Согласуйте способ авторизации. Текущий адаптер поддерживает Bearer-токен в заголовке `Authorization`; при такой настройке ключ хранится в серверной конфигурации Gravity Field.

### Пример для карточки товара

```http
POST https://recommendations.example.com/api/v1/recommend
Content-Type: application/json
Authorization: Bearer <API key>
```

```json
{
  "request_id": "d7cf9d35-1c81-46bd-98ef-5c9e44281015",
  "section_id": "665f0a000000000000000001",
  "surface": "pdp",
  "limit": 12,
  "external_user_id": "customer-42",
  "session_id": "session-abc",
  "item_id": {
    "sku_id": "shoe-00987",
    "group_id": "shoe-009"
  },
  "region": "msk-01",
  "platform": "desktop"
}
```

### Пример для корзины без внешнего ID пользователя

```json
{
  "request_id": "8e6920e7-0fa4-47eb-a987-45160e8db2ce",
  "section_id": "665f0a000000000000000001",
  "surface": "cart_pdp",
  "limit": 6,
  "session_id": "session-def",
  "item_ids": [
    { "sku_id": "shoe-00987", "group_id": "shoe-009" },
    { "sku_id": "bag-00456", "group_id": "bag-004" }
  ],
  "region": "msk-01",
  "platform": "mobile"
}
```

В этих примерах `item_id` — объект с идентификаторами, а `item_ids` — массив таких объектов. Если вашему API нужны строковый SKU или массив строк, их можно передать в согласованных полях вместо объектов.

### Поля рекомендуемого запроса

В таблице описаны поля приведённых примеров. Состав запроса согласуется при подключении; наличие контекстных полей зависит от данных исходного запроса.

| Поле | Тип | Назначение и доступность |
| :--- | :--- | :--- |
| `request_id` | string | ID вызова, созданный Gravity Field. Записывайте его в логи для сопоставления с диагностикой платформы |
| `section_id` | string | Секция Gravity Field, для которой запрашиваются рекомендации |
| `surface` | string | Место применения алгоритма. Для карточки по умолчанию `pdp`, для корзины — `cart_pdp`; другие значения согласуются отдельно |
| `limit` | integer | Запрошенное количество кандидатов. После правил стратегии показанных товаров может быть меньше |
| `external_user_id` | string | Внешний ID клиента, если он доступен в исходной интеграции |
| `session_id` | string | ID сессии, доступный адаптеру. Формат и соответствие сессиям вашей системы согласуются отдельно |
| `item_id` | object | SKU и группа текущего товара для товарной карточки |
| `item_ids` | object[] | SKU и группы товаров корзины |
| `region` | string | Код региона, склада или зоны из контекста `lng`, если передан |
| `platform` | string | Платформа: например, `desktop`, `mobile` или явно настроенная `app` |

По умолчанию адаптер убирает пустые строки, `null` и пустые массивы. Поэтому для корзины может не быть `item_id`, а для анонимного пользователя — `external_user_id`. Принимайте отсутствие таких полей; перечень обязательных полей для вашего API согласуйте до подключения.

Имена `surface`, `region`, `item_id` и остальных полей можно изменить. Адаптер передаёт только настроенные данные, а не весь исходный запрос пользователя. Устойчивость к новым необязательным полям поможет расширять формат без поломки интеграции.

### Регион и платформа

`region` в примерах содержит значение `lng` из контекста Gravity Field. Это код зоны каталога, а не географическая долгота. Используйте тот же код, что в региональных данных фида: тогда модель и Gravity Field будут работать с одним ассортиментом.

Если алгоритму нужен регион, заранее определите поведение при отсутствии или неизвестном значении: например, общая выдача или ошибка запроса. Не выбирайте произвольный склад без согласования.

Значения платформы также настраиваются. По умолчанию `tablet` преобразуется в `mobile`, а `unknown` — в `desktop`. Значение `app` требует явной передачи и настройки; оно не определяется только по факту использования мобильного SDK.

## Ответ вашего API

Верните HTTP `200` и JSON с упорядоченным массивом кандидатов:

```json
{
  "items": [
    { "sku_id": "socks-00123", "group_id": "socks-001", "score": 0.94 },
    { "sku_id": "care-00456", "group_id": "care-004", "score": 0.87 }
  ]
}
```

| Поле | Требование |
| :--- | :--- |
| `items` | Массив кандидатов в порядке ранжирования. При отсутствии рекомендаций — пустой массив |
| `items[].sku_id` | SKU из фида нужной секции. Обязателен для каждого кандидата; рекомендуемый тип — строка |
| `items[].group_id` | Необязательный ID группы. Не заменяет группировку из фида |
| `items[].score` | Необязательная числовая оценка модели. Сама по себе не меняет порядок кандидатов |

Если ответ вашего API уже имеет другую структуру, передайте пример команде Gravity Field. Можно настроить чтение массива из вложенного поля, например `data.products`, а SKU — из `product_id`. Поддерживается и список строковых SKU: `{"items": ["socks-00123", "care-00456"]}`.

### Что происходит с порядком и количеством

Порядок массива — ранжирование вашего алгоритма. Gravity Field сохраняет его при загрузке карточек, затем применяет правила стратегии:

- исключает неизвестные SKU, товары без наличия и группы текущего контекста;
- применяет фильтры и убирает повторы групп;
- учитывает правила мерчандайзинга и закреплённые товары;
- может перемешать результат, если это включено в стратегии;
- ограничивает итоговое количество товаров.

Поэтому состав и порядок показанных товаров могут отличаться от ответа вашего API. Чтобы после фильтров хватало товаров, согласуйте размер списка кандидатов и допустимый запас. Не считайте `limit` гарантией числа показанных карточек.

📖 [Фильтры и правила мерчандайзинга](../personalization/Recs/filters_and_rules.md).

## Ошибки, время ответа и нагрузка

Если подходящих товаров нет, верните корректный пустой результат:

```json
{
  "items": []
}
```

Технический сбой обозначайте HTTP-кодом ошибки, а не пустой выдачей: так его можно отличить от отсутствия рекомендаций.

| Ситуация | Обработка в Gravity Field |
| :--- | :--- |
| `200` с пустым массивом | Корректный ответ без кандидатов |
| `2xx` с невалидным JSON | Ошибка разбора; кандидаты не используются |
| `2xx`, но согласованное поле списка отсутствует или не является массивом | Ошибка структуры ответа; кандидаты не используются |
| Кандидат без пригодного SKU или с неизвестным SKU | Такой кандидат пропускается |
| `401`, `403`, `429`, `5xx` и другие ответы вне `2xx` | Ошибка HTTP-вызова; кандидаты не используются |
| Таймаут или ошибка соединения | Вызов завершается без кандидатов |

**Резервную выдачу нужно согласовать и проверить отдельно.** Ошибка или недостаточное число кандидатов передают управление логике стратегии, но для нового BYOA-ключа автоматический переход на популярные товары не гарантирован. Без подходящего резервного сценария блок может получить неполную или пустую выдачу. В текущем адаптере нет автоматических повторных вызовов и кеширования внешнего ответа.

Текущие таймауты адаптера по умолчанию:

- **800 мс** — на весь HTTP-вызов;
- **200 мс** — на установление соединения, внутри общего бюджета.

Значения можно настроить для конкретного подключения. Укладывайтесь в согласованный бюджет с запасом на сеть, в том числе под пиковой нагрузкой. До запуска согласуйте ожидаемое число запросов в секунду, одновременные вызовы, лимиты API и поведение при `429`. Не считайте один просмотр страницы одним вызовом: число вызовов зависит от блоков и стратегий.

Для диагностики записывайте `request_id`, код ответа, время обработки и число кандидатов. Не записывайте токен авторизации в логи. Команда Gravity Field может сопоставить вызов с диагностикой стратегии: статусом, временем вызова и количеством полученных и найденных в фиде товаров.

📖 [Расшифровка ответа стратегии](../personalization/Recs/strategy_debug.md).

## Порядок подключения

1. **Передайте описание алгоритма.** Укажите, где он должен работать, какие контексты и данные ему нужны: карточка товара, корзина, персональные рекомендации, регион.
2. **Согласуйте идентификаторы.** Проверьте SKU на реальных примерах из фида. Для персональной модели определите общий ключ пользователя и способ его передачи в нужных каналах.
3. **Передайте параметры API.** Нужны тестовый URL, метод, примеры запросов и ответов, обязательные поля, способ авторизации и ожидаемая нагрузка. Ключ доступа передайте по согласованному с командой способу, отдельно от публичного описания.
4. **Согласуйте время ответа и резервный сценарий.** Определите поведение для анонимного пользователя, неизвестного региона, пустого результата, ошибок и превышения таймаута.
5. **Проверьте тестовую секцию.** Команда Gravity Field зарегистрирует алгоритм и подключит его к стратегии. Сверьте вызовы API, итоговые товары, события показов, кликов и покупок.
6. **Запустите на ограниченном трафике.** Проверьте задержки, ошибки и качество выдачи. Для оценки результата согласуйте A/B-тест, затем расширяйте применение.

### Чеклист проверки

- [ ] HTTPS API доступен с серверов Gravity Field; авторизация проверена.
- [ ] Все SKU точно совпадают с фидом нужной секции, включая ведущие нули и регистр.
- [ ] Неизвестный SKU пропускается; остальные кандидаты продолжают участвовать в выдаче.
- [ ] Несколько SKU одной группы не заполняют весь блок; группы текущего товара и корзины исключаются.
- [ ] Фид обновляется, а показанные карточки, цены и наличие соответствуют его данным.
- [ ] Модель получает согласованный ключ пользователя; UID и внешний ID не смешиваются.
- [ ] Проверены отсутствие внешнего ID и пользователь, неизвестный модели.
- [ ] Переданные SKU карточки и корзины соответствуют текущему контексту.
- [ ] Проверены региональные значения, а также отсутствующий и неизвестный регион.
- [ ] Порядок кандидатов проверен с учётом фильтров и правил стратегии.
- [ ] Пустая выдача возвращается как `200` с `items: []`.
- [ ] Проверены неправильный JSON, структура ответа, `401`/`403`, `429`, `5xx`, таймаут и недоступность API.
- [ ] Резервный сценарий проверен на итоговой выдаче, включая частично заполненный блок.
- [ ] Время ответа укладывается в бюджет под согласованной нагрузкой.
- [ ] По `request_id` можно сопоставить логи клиента и диагностику Gravity Field.
- [ ] Показы, клики и покупки используют согласованные SKU и попадают в аналитику.

## FAQ

### Нужно ли переделывать существующий API?

Не обязательно. Передайте примеры команде Gravity Field: имена полей и вложенность можно адаптировать при подключении. Если API требует данные, которые текущий адаптер не передаёт, способ подключения нужно согласовать отдельно.

### Можно ли вернуть только ID группы?

Нет, для загрузки карточки нужен конкретный SKU из фида. Если модель ранжирует группы, выберите SKU внутри каждой группы на стороне вашего API.

### Обязательны ли персональные рекомендации?

Нет. Алгоритм может подбирать товары по карточке, корзине или другой согласованной логике. ID пользователя нужен только для сценариев, которые его используют.

### Будут ли показаны все товары в том же порядке?

Не обязательно: после ответа API работают фильтры и правила стратегии. Проверьте итоговую выдачу на тестовой секции, а не только JSON алгоритма.

## Связанные материалы

[!ref](./products_catalogues/general_reqs.md)

[!ref](./identification.md)

[!ref](../personalization/Recs/manage_recs.md)
