Shopping Assistant API
На этой странице описаны контракты Shopping Assistant API: отправка сообщений, обработка структурированного ответа, сбор обратной связи и восстановление истории. Публичные ссылки описаны в разделе Шаринг диалогов, а события и tracking URL — в разделе Трекинг событий.
Перед работой с 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" | "debug";
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"]
}
}
}
Режим отладки
Используйте trafficType: "debug", чтобы проверить вызов endpoint-а и отображение ответа в клиентском интерфейсе без обращения к LLM и расхода токенов. В этом режиме Shopping Assistant возвращает одну и ту же заглушку независимо от текста сообщения и настроек секции.
Запрос по-прежнему должен соответствовать контракту: передайте непустые threadId, resourceId, runtimeContext.tenant_id и хотя бы одно сообщение пользователя.
curl --request POST \
--url 'https://shopping-assistant-api.gravityfield.ai/shopping/generate' \
--header 'Content-Type: application/json' \
--data '{
"threadId": "debug-thread-1",
"resourceId": "debug-user-1",
"trafficType": "debug",
"messages": [
{
"role": "user",
"content": "Проверка интеграции"
}
],
"runtimeContext": {
"tenant_id": "670ccaae56afcafaf808c146"
}
}'
Ответ содержит текст, два тестовых товара, три быстрых ответа и тестовое tracking-событие:
{
"threadId": "debug-thread-1",
"resourceId": "debug-user-1",
"assistantMessageId": "shopping:assistant:8a21...",
"response": {
"contents": [
{
"type": "message",
"value": "Debug response"
},
{
"type": "products",
"title": "Debug products",
"shelf_id": "debug-products",
"source_query": "debug",
"value": [
{
"sku": "debug-product-1",
"name": "Debug product 1",
"brand": "Debug brand",
"price": 100
},
{
"sku": "debug-product-2",
"name": "Debug product 2",
"brand": "Debug brand",
"price": 200
}
]
}
],
"ui": {
"suggests": [
"Debug suggestion 1",
"Debug suggestion 2",
"Debug suggestion 3"
]
}
},
"responseLatencyMs": 100,
"events": [
{
"type": "visible_impression",
"url": "https://runtime.test/widget"
}
]
}
Значения responseLatencyMs и events в этом ответе являются частью заглушки: они не отражают фактическую задержку и не фиксируют аналитику. Debug-запрос не запускает генерацию и обработку изображений, не сохраняет сообщения и не появляется в истории. Не отправляйте оценку такого ответа через /shopping/feedback.
После проверки интеграции замените debug на test или real.
Минимальный пример
Запрос
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": "Подходящие корма для взрослых кошек",
"categories": [
"Зоотовары",
"Корма для кошек"
],
"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; categories?: string[] }
| { type: "button"; value: string; action: string }
>;
ui?: {
suggests: string[];
};
};
responseLatencyMs?: number;
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[] }
| { type: string; url: 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 отсутствует, блок можно отобразить без заголовка.
Поле categories в блоке products содержит путь категории товарного блока: от общей категории к более конкретной. Например, ["Диваны и кресла", "Диваны"]. Поле опционально и появляется в ответе после согласования с командой сопровождения Gravity Field.
Для товарных карточек считайте sku основным идентификатором товара. Поля price, brand, url, image_url и другие данные могут использоваться для быстрого отображения карточки, но актуализация цены, изображения, наличия, открытие PDP и добавление в корзину остаются на стороне клиентского канала или клиентского каталога.
Ошибки и повтор запроса
Показывайте индикатор загрузки до получения ответа, не создавайте новый threadId при повторе того же пользовательского сообщения и защищайтесь от двойной отправки одинакового сообщения при нестабильной сети.
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. Не сохраняйте историю в общем или публичном кеше на клиентской стороне.