API поиска и получения товаров для Shopping Assistant
Если Shopping Assistant должен подбирать товары через поиск и ранжирование вашего магазина, реализуйте HTTPS API по контракту на этой странице. Нужны две ручки: поиск товаров по текстовому запросу и получение одного товара по SKU. Gravity Field вызывает ваш API во время диалога и передаёт полученные карточки ассистенту. Подключение и параметры вызова согласуются с командой Gravity Field.
Для подключения используется BYOA — Bring Your Own Algorithm. В этом сценарии BYOA связывает Shopping Assistant с вашим поисковым API. Ваша система определяет состав и порядок товаров-кандидатов; ассистент выбирает из них карточки для ответа покупателю. Настройки рекомендательных стратегий Gravity Field не применяются к этому поисковому ответу автоматически.
Если собственный поиск не нужен, используйте стандартный поисковый контур Shopping Assistant. Формат клиентского API ниже тогда не требуется.
📖 Подробнее о продукте: Как устроен Shopping Assistant. BYOA для рекомендаций описывает другой сценарий — подключение алгоритма к рекомендательной стратегии.
Как устроено подключение
Покупатель
│ реплика в диалоге
▼
Shopping Assistant
│ поисковый запрос или SKU товара, контекст
▼
Поисковый коннектор Gravity Field (BYOA)
│ POST на нужную ручку вашего API — в момент запроса
▼
API клиента
│ результаты поиска или карточка товара
▼
Shopping Assistant — ответ пользователю
Разделение ответственности:
Клиентский сайт или приложение продолжает работать с обычным POST /shopping/generate. Формат вашего поискового API относится к серверному вызову Gravity Field → API клиента; браузер покупателя этот endpoint не вызывает.
Общие требования к API
- HTTPS, метод
POST, тело и ответ в JSON. - Вызов идёт server-to-server, со стороны Gravity Field. Публичный доступ из браузера покупателя не предусматривается.
- Авторизация: заголовок
Authorization: Bearer <API key>. Ключ доступа к вашему API согласуется при подключении и хранится в защищённой конфигурации Gravity Field; в запросе он передаётся только в заголовке и не должен попадать в логи или ответ. - Обе ручки только читают данные и не меняют состояние каталога, корзины или профиля.
- Идемпотентность: повторный запрос с тем же
requestIdне должен приводить к побочным эффектам. - Устойчивость к неизвестным полям в запросе: новые опциональные поля могут добавляться без смены версии контракта.
Поиск товаров
Адрес ручки задаёт клиент и фиксирует при подключении. В примерах ниже — https://search.example.com/api/v1/assistant/search.
POST https://search.example.com/api/v1/assistant/search
Content-Type: application/json
Authorization: Bearer <API key>
X-Request-Id: <uuid>
type SearchFilter =
| { field: string; operator: "eq"; value: string | number | boolean }
| { field: string; operator: "in"; value: Array<string | number | boolean> }
| { field: string; operator: "gte" | "lte"; value: number };
type SearchQuery = {
id: string;
query: string;
limit: number;
filters?: SearchFilter[];
};
type AssistantSearchRequest = {
requestId: string;
sectionId: string;
lng?: string;
user?: { uid?: string; session?: string };
queries: SearchQuery[];
};
Поля запроса
Каждый элемент queries[] содержит query и limit. Для получения конкретного товара по SKU используйте
lng — код зоны обслуживания, не координата долготы. Если наличие или цена зависят от склада, клиентский канал передаёт код склада в runtimeContext.ctx.lng, а Gravity Field передаёт тот же код в ваш поисковый API. Все задачи одного батча выполняются для одного lng; для разных складов нужны отдельные вызовы. Если для каталога lng обязателен, при его отсутствии или неизвестном значении возвращайте 400 и не выбирайте произвольный склад. ctx.location содержит адрес страницы или ID экрана и не заменяет lng.
Если поиску нужен контекст конкретной страницы, экрана или товарного слота, согласуйте для него отдельное поле при подключении. Не подменяйте им sectionId: это идентификатор секции Gravity Field.
Batch и single
Предпочтительный формат — batch: один HTTP-запрос, до 12 текстовых запросов в queries[]. Это меньше раунд-трипов и проще соблюсти общий бюджет вызова.
Если ручка клиента умеет обрабатывать только одну поисковую задачу за раз, допустим single-режим: queries содержит ровно один элемент, а платформа вызывает ручку для нескольких задач параллельно. Режим согласовывается при подключении и не меняется без уведомления.
В batch-режиме задачи обрабатывайте параллельно, а не последовательно: бюджет вызова общий.
Фильтры
Gravity Field формирует фильтры на основе товарного фида. Поэтому field совпадает с именем поля в фиде. Для eq и in используются канонические значения этого поля из фида; для gte и lte — числовая граница в тех же единицах, что и в фиде. API поиска должен понимать эти же имена полей, типы и значения. Список фильтруемых полей и операторов согласуется при подключении.
- Операторы:
eq,in,gte,lte. - Одно поле — один фильтр в рамках задачи.
- Для
eqпередавайте одно значение, дляin— массив значений, дляgteиlte— число. - Если поля нет в фиде или значение для
eq/inнельзя однозначно сопоставить со значением фида, Gravity Field оставляет условие вqueryи не добавляет фильтр.
Контракт ответа
type SearchProduct = {
sku: string;
name: string;
price?: number | null;
old_price?: number | null;
brand?: string;
url?: string;
image_url?: string;
features?: string;
attributes?: Record<string, string | number | boolean | Array<string | number | boolean>>;
category?: string;
category_id?: string;
group_id?: string;
in_stock?: boolean;
};
type SearchQueryResult = {
id: string;
products: SearchProduct[];
totalHits?: number;
};
type AssistantSearchResponse = {
requestId: string;
results: SearchQueryResult[];
};
Поля карточки
Поля ответа
Правила ответа
- Порядок
productsотражает ранжирование вашего поиска. Сохраняйте его в ответе API: он помогает ассистенту оценить кандидатов. Ассистент может выбрать часть товаров и сгруппировать их в ответе, поэтому позиция товара в поисковой выдаче не гарантирует ту же позицию в интерфейсе покупателя. - Дедупликация вариантов — на вашей стороне. Если используете
group_id, возвращайте один карточный вариант на группу. Дубли, которые придётся схлопывать уже после ответа, искажают и порядок, и размер выдачи. - Наличие и ассортиментские ограничения — на вашей стороне. В результатах поиска отдавайте товары, доступные для переданного
lng: постфильтрация на платформе укорачивает страницу и ломает связь сtotalHits. - Не возвращайте карточки без
skuилиname: они не проходят проверку контракта и не могут использоваться в ответе ассистента. results[]обязан покрывать всеidиз запроса, порядок элементов не важен.- Не возвращайте в карточках внутренние счётчики, скрытые признаки ранжирования и отладочные поля.
Получение товара по SKU
Для запроса сведений о конкретном товаре реализуйте вторую ручку. Её адрес также задаётся при подключении; в примере — https://search.example.com/api/v1/assistant/product. Передавайте точный sku из товарного фида и код склада lng, если от него зависят цена и наличие. Ручка ищет точное совпадение SKU и не выполняет текстовый поиск.
POST https://search.example.com/api/v1/assistant/product
Content-Type: application/json
Authorization: Bearer <API key>
X-Request-Id: <uuid>
type AssistantProductRequest = {
requestId: string;
sectionId: string;
sku: string;
lng?: string;
};
type AssistantProductResponse = {
requestId: string;
product: (SearchProduct & { in_stock: boolean }) | null;
};
requestId, sectionId и lng имеют тот же смысл, что в поисковом запросе. requestId дублируется в X-Request-Id и возвращается в ответе. sku обязателен; пустое значение — ошибка запроса (400). Если для каталога обязателен lng, его отсутствие или неизвестное значение также возвращайте как 400. Если SKU не найден, верните 200 с product: null. Для найденного товара обязательно укажите in_stock с учётом переданного lng. Если он недоступен, верните карточку с in_stock: false: ассистент сможет распознать товар, не предлагая его как доступный к покупке.
Пример запроса товара
curl --request POST \
--url 'https://search.example.com/api/v1/assistant/product' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API key>' \
--header 'X-Request-Id: 192aff63-c11a-421f-a8cd-f66ed4431a67' \
--data '{
"requestId": "192aff63-c11a-421f-a8cd-f66ed4431a67",
"sectionId": "670ccaae56afcafaf808c146",
"sku": "LAPTOP-015",
"lng": "msk-01"
}'
Пример ответа о товаре
{
"requestId": "192aff63-c11a-421f-a8cd-f66ed4431a67",
"product": {
"sku": "LAPTOP-015",
"name": "Ноутбук Vector 14",
"price": 74990,
"brand": "Vector",
"url": "https://shop.example.com/products/laptop-015",
"image_url": "https://shop.example.com/images/laptop-015.jpg",
"features": "Экран 14 дюймов; 8 ГБ RAM; SSD 512 ГБ",
"category": "Ноутбуки",
"category_id": "laptops",
"group_id": "vector-14",
"in_stock": true
}
}
SKU и товарный фид
sku в ответах вашего API и sku в товарном фиде должны обозначать один и тот же товар. Это нужно для связки карточек с каталогом и корректной интерпретации показов, кликов и покупок в Gravity Field. Если у вас несколько идентификаторов товара, согласуйте правило сопоставления до запуска.
Даже когда карточки целиком приходят из вашего API, товарный фид синхронизировать нужно:
- он обеспечивает единый каталог для остальных контуров Gravity Field: рекомендаций, аудиторий, аналитики и A/B-тестов;
- без него показы, клики и покупки не сойдутся с товарами в отчётах;
- атрибуты фида могут использоваться в других сценариях платформы; применение правил к результатам именно вашего поиска согласовывается отдельно.
Ошибки, пустой ответ и fallback
Отдельные правила:
- Отсутствие товаров — это
200с пустым списком для поиска илиproduct: nullдля SKU, а не ошибка. Ошибка означает «API не ответил», пустой результат — «по вашей логике подходящего нет». Ассистент обрабатывает эти ситуации по-разному. - Резервный сценарий согласовывается при подключении. При техническом сбое ассистент может использовать другой источник товаров или сообщить, что поиск временно недоступен. Не рассчитывайте на автоматическое переключение без согласованной настройки. Публичный ответ
/shopping/generateне содержит признакаfallback: true; проверяйте источник выдачи и ошибки по диагностике запроса. - Бюджет вызова ограничен. Ориентир для API поиска — около 5 секунд; точный таймаут и SLO для обеих ручек фиксируются при подключении. Целевое время ответа должно оставлять запас для формирования ответа ассистента.
- Технический сбой не равен пустой выдаче. Если задачу батча не удалось обработать, верните ошибку всего вызова (
5xx), чтобы ассистент не счёл её результатом без товаров.
Подключение и проверка
После реализации API передайте команде Gravity Field адреса обеих тестовых ручек и согласуйте способ авторизации, секцию, режим batch или single для поиска, фильтры и таймаут. Команда настроит вызовы через BYOA и проверит их вместе с вами.
- Вызов выполняется во время диалога. API должен выдерживать согласованную нагрузку и возвращать актуальные товары в пределах таймаута.
- Проверяйте интеграцию целиком. Сначала проверьте ручку напрямую, затем проведите тестовые диалоги через
POST /shopping/generateсtrafficType: "test". Режимdebugвозвращает заглушку и не вызывает поиск. - Сверяйте результаты по
requestId. Проверьте, какие задачи получил ваш API, какие кандидаты он вернул и какие карточки вошли в ответ ассистента. Число и порядок карточек могут отличаться после выбора ассистентом. - Согласовывайте изменения контракта. Новые опциональные поля можно добавлять обратно совместимо. Удаление полей, смена типов и уменьшение допустимого
limitтребуют согласования с командой Gravity Field.
Пример
Запрос
В примере предполагается, что товарный фид содержит поле ram_gb со значением объёма памяти в гигабайтах.
curl --request POST \
--url 'https://search.example.com/api/v1/assistant/search' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API key>' \
--header 'X-Request-Id: 8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d' \
--data '{
"requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
"sectionId": "670ccaae56afcafaf808c146",
"lng": "msk-01",
"queries": [
{
"id": "q1",
"query": "ноутбук для работы 16 ГБ до 90 000 рублей",
"limit": 20,
"filters": [
{ "field": "ram_gb", "operator": "gte", "value": 16 },
{ "field": "price", "operator": "lte", "value": 90000 }
]
}
]
}'
Ответ
{
"requestId": "8f2b1c0a-6c1d-4a0e-9f3b-2d4e5a6b7c8d",
"results": [
{
"id": "q1",
"totalHits": 24,
"products": [
{
"sku": "LAPTOP-101",
"name": "Ноутбук Nova 14",
"price": 79990,
"old_price": 84990,
"brand": "Nova",
"url": "https://shop.example.com/products/laptop-101",
"image_url": "https://shop.example.com/images/laptop-101.jpg",
"features": "Экран 14 дюймов; 16 ГБ RAM; SSD 512 ГБ",
"attributes": { "ram_gb": 16, "storage_gb": 512 },
"category": "Ноутбуки",
"category_id": "laptops",
"group_id": "nova-14",
"in_stock": true
},
{
"sku": "LAPTOP-102",
"name": "Ноутбук Orion 15",
"price": 85990,
"brand": "Orion",
"url": "https://shop.example.com/products/laptop-102",
"image_url": "https://shop.example.com/images/laptop-102.jpg",
"features": "Экран 15,6 дюйма; 16 ГБ RAM; SSD 1 ТБ",
"category": "Ноутбуки",
"category_id": "laptops",
"group_id": "orion-15",
"in_stock": true
}
]
}
]
}
Чек-лист перед подключением
- Обе ручки доступны по HTTPS, принимают
POSTс JSON и отвечают JSON. - Реализована авторизация
Authorization: Bearer <API key>. - Принимается
requestId, он же продублирован вX-Request-Idи возвращается в ответе. - Поддерживается batch до 12 запросов, либо заявлен single-режим.
-
limitна запрос принимает значения до 100. - Поисковая ручка принимает
queryсlimit; отдельная ручка возвращает товар по точномуsku. - Для выдачи с региональными ценами и остатками учитывается
lngиз контекста Gravity Field; товары, цена и наличие соответствуют этому складу или региону. - Поисковый API принимает имена полей и канонические значения из товарного фида Gravity Field; числовые границы использует в единицах фида.
- Неизвестные поля и значения для
eq/inне превращаются в фильтр: условие остаётся вquery. - Возвращаются полные карточки с обязательными
skuиname. -
skuсовпадает сskuтоварного фида Gravity Field. - Если используется
group_id, варианты дедуплицируются до ответа; недоступные товары исключаются из поиска. - Порядок
productsотражает ранжирование вашего поиска; допускается последующий выбор карточек ассистентом. - Отсутствие товаров отвечается
200с пустымproducts, неизвестный SKU —200сproduct: null. -
results[]покрывает всеidзапроса, включая пустые. - Время ответа укладывается в согласованный бюджет с запасом.
-
requestIdпишется в логи для корреляции с платформой. - Секреты не возвращаются в ответах и не попадают в логи.
- Есть тестовая среда для smoke-проверки через
POST /shopping/generate.
FAQ
Кто определяет порядок товаров?
Ваш API ранжирует кандидатов внутри каждой поисковой задачи. Shopping Assistant выбирает из них карточки для ответа покупателю и может сгруппировать их под разные части задачи. Поэтому порядок в ответе ассистента не обязан совпадать с полным списком кандидатов.
Обязательно ли синхронизировать товарный фид, если карточки приходят из моего API?
Да. Фид обеспечивает единый каталог Gravity Field и позволяет сопоставить карточки из вашего API с товарами в событиях. Для корректной атрибуции также настройте трекинг Shopping Assistant. Подробнее о фиде: Требования к товарному фиду.
Нужна ли пагинация?
Нет. Ассистент работает с top-N результатов по каждой поисковой задаче. Поле totalHits возвращайте, если размер выдачи известен: он помогает диагностике, но от него не зависит рендеринг.
Как получить выдачу для конкретного склада?
Передайте код склада в runtimeContext.ctx.lng при вызове /shopping/generate. Gravity Field передаст тот же код в поле lng нужной ручки вашего API. Например, при lng: "msk-01" поиск вернёт товары, доступные на складе msk-01, а запрос по SKU — его цену и наличие на этом складе. Значение кода должно совпадать с настройками региональных данных товарного фида или с согласованным справочником складов вашего поиска.
Как получить товар по SKU?
Используйте sku и при необходимости lng. Она вернёт одну карточку в product, null для неизвестного SKU или карточку с in_stock: false, если товар найден, но недоступен для указанного склада.
Что делать, если часть каталога временно недоступна?
Если по конкретной задаче товаров действительно нет, верните для неё пустой products. Если задачу не удалось обработать из-за технического сбоя, верните 5xx для всего вызова; дальнейшее поведение ассистента зависит от резервного сценария, согласованного при подключении. Не подменяйте сбой пустой выдачей или выдуманными товарами.
Как проверить подключение до боевого трафика?
Разверните API на тестовой среде, подключите его к тестовой секции Gravity Field и прогоните сценарии через POST /shopping/generate с trafficType: "test". По requestId сверяйте запросы и ответы вашего API с диагностикой ассистента. Убедитесь, что ассистент использует ожидаемые товары; он может показать не все найденные карточки. Режим debug для этого не подходит: он возвращает заглушку и к каталогу не обращается.
Связанные материалы