Shopping Assistant API
На этой странице описаны контракты Shopping Assistant API: отправка сообщений, обработка структурированного ответа, сбор обратной связи, восстановление истории и tracking товарных подборок.
Перед работой с endpoint-ами выполните шаги из гайда по API-интеграции. Настройка авторизации, пользователей, контекста и API-кампании выполняется через Gravity Field API V2.
POST /shopping/generate
POST https://shopping-assistant-api.gravityfield.ai/shopping/generate
Content-Type: application/json
Endpoint принимает новое сообщение пользователя и возвращает текстовые блоки, товарные карточки, кнопки действий и быстрые ответы. Готовый UI чата API не возвращает.
Для всех сообщений одного диалога используйте одинаковые threadId и resourceId. В resourceId передавайте user.uid, полученный через Gravity Field API V2. Историю диалога повторно отправлять не нужно: Shopping Assistant сохраняет контекст по этим идентификаторам.
Для обычной интеграции используйте роль user.
Контракт запроса
type ShoppingTextContentPart = {
type: "text";
text: string;
};
type ShoppingImageContentPart = {
type: "image";
source:
| {
type: "url";
url: string;
}
| {
type: "base64";
mediaType: "image/jpeg" | "image/png" | "image/webp";
data: string;
};
};
type ShoppingGenerateRequest = {
threadId: string;
resourceId: string;
trafficType?: "real" | "test";
messages: Array<{
role: "user";
content:
| string
| Array<ShoppingTextContentPart | ShoppingImageContentPart>;
}>;
runtimeContext: {
tenant_id: string;
ctx?: {
type?: string;
data?: unknown;
lng?: string | null;
location?: string | null;
};
};
};
Поля запроса
Как передать изображение
Чтобы отправить изображение, передайте в content массив блоков. В одном пользовательском сообщении поддерживается только одно изображение. Вместе с ним можно передать текстовый блок с пояснением:
{
"threadId": "thread-8f2b1c",
"resourceId": "665f0a000000000000000001",
"trafficType": "test",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Найдите похожий диван в более светлом цвете"
},
{
"type": "image",
"source": {
"type": "base64",
"mediaType": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQ..."
}
}
]
}
],
"runtimeContext": {
"tenant_id": "670ccaae56afcafaf808c146"
}
}
В поле data передавайте только Base64-содержимое файла, без префикса data:image/jpeg;base64,. Значение mediaType должно соответствовать фактическому формату файла.
Изображение также можно передать по URL. Замените блок image в примере выше:
{
"type": "image",
"source": {
"type": "url",
"url": "https://cdn.example.com/user-photo.webp"
}
}
Текст необязателен: для запроса только по изображению передайте в content массив с одним блоком image.
Ограничения для изображения
После проверки Shopping Assistant учитывает EXIF-ориентацию, уменьшает изображение до размера не более 2048 × 2048 пикселей без увеличения и преобразует его в JPEG для обработки.
Как заполнять ctx
runtimeContext.ctx использует модель контекста API V2. Передавайте актуальный контекст, согласованный с /visit и /choose. Поддерживаемые типы и формат data описаны в разделе Контекст API V2.
Если чат открыт на карточке товара, передайте SKU товара в контексте:
{
"runtimeContext": {
"tenant_id": "670ccaae56afcafaf808c146",
"ctx": {
"type": "PRODUCT",
"data": ["1066109"]
}
}
}
Минимальный пример
Запрос
curl --request POST \
--url 'https://shopping-assistant-api.gravityfield.ai/shopping/generate' \
--header 'Content-Type: application/json' \
--data '{
"threadId": "thread-8f2b1c",
"resourceId": "665f0a000000000000000001",
"trafficType": "test",
"messages": [
{
"role": "user",
"content": "Помогите выбрать корм для взрослой кошки"
}
],
"runtimeContext": {
"tenant_id": "670ccaae56afcafaf808c146"
}
}'
Ответ
{
"threadId": "thread-8f2b1c",
"resourceId": "665f0a000000000000000001",
"assistantMessageId": "shopping:assistant:8a21...",
"response": {
"contents": [
{
"type": "message",
"value": "Подберу корм, но сначала уточню пару деталей. Кошка стерилизована и есть ли особенности по здоровью?"
}
],
"ui": {
"suggests": [
"Стерилизована, без особенностей",
"Не стерилизована, чувствительное пищеварение",
"Пожилая кошка, нужен мягкий корм"
]
}
}
}
Пример ответа с товарами
{
"threadId": "thread-8f2b1c",
"resourceId": "665f0a000000000000000001",
"response": {
"contents": [
{
"type": "message",
"value": "Вот несколько подходящих вариантов. Смотрите на возраст, стерилизацию и чувствительность пищеварения."
},
{
"type": "products",
"title": "Подходящие корма для взрослых кошек",
"value": [
{
"sku": "1066109",
"name": "Сухой корм для взрослых кошек",
"price": 1299,
"features": "Для взрослых кошек; повседневный рацион",
"is_stm": false,
"brand": "Demo Brand",
"url": "https://example.com/product/1066109",
"image_url": "https://example.com/product/1066109.jpg",
"events": [
{
"type": "visible_impression",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?...",
"https://evs-02.gravityfield.ai/v2/engagement?..."
]
},
{
"type": "click",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?..."
]
}
]
},
{
"sku": "1023814",
"name": "Корм для стерилизованных кошек",
"price": 1599,
"features": "Для стерилизованных кошек; контроль веса",
"is_stm": true
}
]
},
{
"type": "message",
"value": "Если кошка стерилизована, лучше начать со второго варианта. Если нет, подойдет первый."
}
],
"ui": {
"suggests": [
"Показать варианты дешевле",
"Нужен влажный корм",
"У кошки чувствительное пищеварение"
]
}
},
"events": [
{
"type": "WRIMP",
"urls": [
"https://evs-01.gravityfield.ai/v2/engagement?...",
"https://evs-02.gravityfield.ai/v2/engagement?..."
]
}
]
}
Контракт ответа
type ShoppingGenerateResponse = {
threadId: string;
resourceId: string;
assistantMessageId: string;
response: {
contents: Array<
| { type: "message"; value: string }
| { type: "products"; value: Product[]; title?: string }
| { type: "button"; value: string; action: string }
>;
ui?: {
suggests: string[];
};
};
events?: EngagementEvent[];
};
type Product = {
sku?: string;
name: string;
price?: number | null;
features?: string;
is_stm?: boolean;
brand?: string;
url?: string;
image_url?: string;
events?: EngagementEvent[];
};
type EngagementEvent = {
type: string;
urls: string[];
};
Поле assistantMessageId содержит ID созданного ответа ассистента. Сохраните его вместе с сообщением в UI: этот ID нужен для отправки оценки через /shopping/feedback.
Кнопка перехода к оператору приходит в массиве response.contents в следующем формате:
{
"type": "button",
"value": "Позвать оператора",
"action": "open_support_chat"
}
Поле value содержит текст кнопки, а action: "open_support_chat" обозначает переход к оператору. Shopping Assistant API только возвращает идентификатор действия. Сам переход выполняется клиентским каналом после нажатия кнопки.
Поле title в блоке products содержит готовый заголовок для этой группы рекомендаций. Если поле пришло, отобразите его над товарными карточками. Если title отсутствует, блок можно отобразить без заголовка.
Для товарных карточек считайте sku основным идентификатором товара. Поля price, brand, url, image_url и другие данные могут использоваться для быстрого отображения карточки, но актуализация цены, изображения, наличия, открытие PDP и добавление в корзину остаются на стороне клиентского канала или клиентского каталога.
PUT /shopping/feedback
Чтобы собрать оценку конкретного ответа Shopping Assistant, вызовите:
PUT https://shopping-assistant-api.gravityfield.ai/shopping/feedback
Content-Type: application/json
Endpoint сохраняет текущую реакцию пользователя на сообщение ассистента. Повторная отправка того же состояния идемпотентна: она не создает дубликат оценки.
Запрос
curl --request PUT \
--url 'https://shopping-assistant-api.gravityfield.ai/shopping/feedback' \
--header 'Content-Type: application/json' \
--data '{
"tenant_id": "670ccaae56afcafaf808c146",
"resourceId": "665f0a000000000000000001",
"threadId": "thread-8f2b1c",
"messageId": "shopping:assistant:8a21...",
"reaction": "like"
}'
API принимает оценку только при точном совпадении tenant_id, resourceId, threadId и messageId. Это не позволяет записать реакцию на сообщение из другого диалога или профиля пользователя.
Ответ
{
"reaction": "like",
"updatedAt": "2026-08-07T10:00:00.000Z"
}
В reaction возвращается сохраненное состояние. Поле updatedAt содержит время его последнего изменения.
Чтобы заменить оценку, отправьте тот же запрос с другим значением reaction. Чтобы пользователь мог снять выбранную оценку, отправьте reaction: null.
Ошибки отправки оценки
POST /shopping/history
Чтобы восстановить сообщения после перезагрузки страницы или повторного открытия чата, вызовите:
POST https://shopping-assistant-api.gravityfield.ai/shopping/history
Content-Type: application/json
Endpoint возвращает только публичные пользовательские сообщения и ответы Shopping Assistant. Внутренние system-сообщения, вызовы инструментов и служебные данные в ответ не попадают.
Запрос истории
curl --request POST \
--url 'https://shopping-assistant-api.gravityfield.ai/shopping/history' \
--header 'Content-Type: application/json' \
--data '{
"tenant_id": "670ccaae56afcafaf808c146",
"resourceId": "665f0a000000000000000001",
"threadId": "thread-8f2b1c",
"page": 1,
"limit": 50
}'
История доступна только при точном совпадении tenant_id, resourceId и threadId. Если диалог не найден или не принадлежит указанной секции или пользователю, API возвращает одинаковый ответ 404.
Ответ
{
"threadId": "thread-8f2b1c",
"resourceId": "665f0a000000000000000001",
"items": [
{
"id": "shopping:user:4f8c...",
"role": "user",
"content": "Помогите выбрать корм для взрослой кошки",
"createdAt": "2026-07-28T10:00:00.000Z"
},
{
"id": "shopping:assistant:8a21...",
"role": "assistant",
"createdAt": "2026-07-28T10:00:01.250Z",
"threadId": "thread-8f2b1c",
"resourceId": "665f0a000000000000000001",
"response": {
"contents": [
{
"type": "message",
"value": "Кошка стерилизована и есть ли особенности по здоровью?"
}
],
"ui": {
"suggests": [
"Стерилизована, без особенностей",
"Не стерилизована",
"Есть особенности по здоровью"
]
}
},
"feedback": {
"reaction": "like"
},
"responseLatencyMs": 1250
}
],
"pagination": {
"page": 1,
"limit": 50,
"hasMore": false
}
}
Элементы в items отсортированы по времени создания от ранних к поздним в пределах страницы:
- для
role: "user"полеcontentимеет тот же формат, что в запросе/shopping/generate: строка для текста или массив блоковtextиimage; - для
role: "assistant"полеresponseимеет тот же контракт, что ответ/shopping/generate;idсообщения используйте какmessageIdдля/shopping/feedback; - поле
feedback.reactionв сообщении ассистента содержит текущую оценку пользователя:like,dislikeилиnull; - дополнительно в сообщении ассистента могут присутствовать
responseLatencyMsиevents; - если
pagination.hasMoreравноtrue, запросите следующую страницу, увеличивpageна1.
Для сообщения с изображением history endpoint возвращает блок image с Base64. Для отображения превью соберите data URL из source.mediaType и source.data, но не передавайте это сообщение повторно в /shopping/generate.
const previewUrl =
`data:${image.source.mediaType};base64,${image.source.data}`;
Ответ содержит заголовок Cache-Control: no-store. Не сохраняйте историю в общем или публичном кеше на клиентской стороне.
Ошибки получения истории
Tracking из ответа Shopping Assistant
В ответе Shopping Assistant tracking-события могут находиться на двух уровнях:
При наступлении события обработайте все элементы events[] с соответствующим type. Для каждого найденного элемента выполните GET-запрос по каждому URL из urls[]. Не ограничивайтесь первым элементом events[] или первым URL: один факт взаимодействия может требовать нескольких tracking-запросов.
async function sendTrackingEvents(events, type) {
const urls = events
.filter((event) => event.type === type)
.flatMap((event) => event.urls);
await Promise.allSettled(
urls.map((url) => fetch(url, { method: "GET", keepalive: true }))
);
}
// Весь рекомендательный блок стал видимым.
sendTrackingEvents(result.events ?? [], "WRIMP");
// Стала видимой конкретная карточка товара.
sendTrackingEvents(product.events ?? [], "visible_impression");
Отправляйте событие видимого показа один раз для одного фактического показа блока или карточки. Ошибка tracking-запроса не должна блокировать отображение рекомендаций или действие пользователя. Общие правила работы с tracking URL описаны в разделе Передача взаимодействий пользователя с кампаниями.
Ошибки /shopping/generate и повтор запроса
Рекомендуем показывать индикатор загрузки до получения ответа, не создавать новый threadId при повторе того же пользовательского сообщения и защищаться от двойной отправки одинакового сообщения при нестабильной сети.