#Серверный API диалогов
Статус: Доступно (чтение) / Планируется (запись). Три маршрута чтения —
GET /conversations,GET /conversations/{id}иGET /conversations/{id}/messages— работают. Маршруты записи (POST …) описаны в проекте и ещё не работают: вызовы вернут404. Для записи имена полей тел запросов и ответов, кромеai_replyи конвертаoperation, помечены как предварительные и подлежат сверке с OpenAPI при выпуске.
Назначение: управлять диалогами с вашего сервера: заводить диалог из своего мессенджера, передавать реплики клиента ИИ-продажнику, получать его ответ, отвечать от имени оператора, перехватывать диалог и возвращать его ИИ, закрывать.
#Содержание
- Когда использовать
- Доступ
- Общие правила
- Объекты
- GET /conversations
- GET /conversations/{id}
- GET /conversations/{id}/messages
- POST /conversations
- POST /conversations/{id}/messages
- Причины отсутствия ответа ИИ
- POST /conversations/{id}/operator-messages
- Перехват, возврат ИИ и закрытие
- События
- Примеры на четырёх языках
- Заметки
- Открытые вопросы
#Когда использовать
- Ваши клиенты общаются в вашем мессенджере, приложении или стороннем виджете, а отвечать должен ИИ-продажник Sapport. Пошаговое руководство — ИИ-продажник в своём канале.
- Нужно показывать диалоги Sapport в вашей системе и отвечать оператором оттуда.
- Для чата на сайте без вашего сервера используйте виджет; для своего интерфейса в браузере — клиентский Chat API.
#Доступ
| Базовый адрес | https://<домен ячейки>/api/public/v1 — RU https://formula-cream.pro, EN https://wfacademy.org, ID https://wfacademy.id |
| Аутентификация | Authorization: Bearer <ключ>; ключ действует только в своей ячейке (Аутентификация) |
| Только сервер | CORS отсутствует: вызывайте API с вашего сервера, ключ в браузер и в приложение не помещайте |
| Лицензия | признак api_access; иначе feature_not_in_plan |
| Канал | api. Создаётся в кабинете при создании интеграции: одна интеграция — один канал api; агент выбирается там же. Идентификатор канала возвращает GET /me (channel_id) |
Области ключа:
| Область | Что даёт | Платная | Содержит ПДн |
|---|---|---|---|
conversations:read | чтение диалогов и сообщений | нет | да |
conversations:write | заведение диалога, приём сообщения клиента, сообщение оператора, перехват, возврат ИИ, закрытие | нет | да |
ai_seller:invoke | ответ ИИ-продажника на сообщение клиента | да | да |
Права запроса — пересечение областей ключа, роли интеграции, прав владельца и лицензии (как вычисляются). Для приёма сообщения клиента с ответом ИИ нужны обе области: conversations:write и ai_seller:invoke. Приём без ответа ИИ требует только conversations:write.
В ячейке RU для областей с ПДн (
conversations:*) при подключении интеграции обязательны список разрешённых IP, страна размещения принимающей системы и основание передачи. ПДн передаются только получателям в РФ: если страна иная, областьconversations:*не выдаётся (403 pii_transfer_not_allowed, Ячейки и данные).
| Статус | Код | Когда |
|---|---|---|
401 | invalid_api_key, wrong_cell | ключ неверен или из другой ячейки |
403 | scope_missing, feature_not_in_plan, ip_not_allowed | нет области, тарифа или адрес не разрешён |
413 | payload_too_large | тело запроса больше 256 КБ |
#Общие правила
Подробно — Соглашения.
| Правило | Значение |
|---|---|
| Формат | JSON, UTF-8, тело до 256 КБ |
| Ошибки | application/problem+json, ветвление по полю code; каталог — Соглашения |
| Идентификатор запроса | заголовок X-Request-Id в каждом ответе, поле request_id в ошибке |
| Пагинация | limit от 1 до 100 (по умолчанию 25), cursor (живёт 24 часа); ответ {data, next_cursor}; порядок возрастающий: у диалогов (updated_at, id), у сообщений (created_at, id) (пагинация) |
| Идемпотентность | заголовок Idempotency-Key на POST; для приёма сообщения клиента — обязателен; срок 24 часа (идемпотентность) |
| Асинхронность | ответ ИИ только асинхронно: 202 и конверт operation; ?wait=N (до 20 с) ждёт то же задание; результат — GET /operations/{id} или событие operation.completed (асинхронные операции) |
| Платные операции | ответ ИИ списывается с кошелька арендатора; возможны insufficient_funds (402), spend_limit_reached (402); стоимость — в usage.cost (платные операции) |
| Пределы частоты | чтение 600/мин, запись 120/мин, платные 30/мин; не более 5 одновременных платных операций на арендатора (стартовые значения, действующие — в GET /me) |
| Идентификаторы | непрозрачные строки; в примерах cnv_EXAMPLE01, msg_EXAMPLE01, evt_EXAMPLE01 |
#Объекты
Поля объектов диалога и сообщения — фактические, как их отдаёт чтение (схемы
ConversationиMessageв OpenAPI). Идентификаторы —uuid. Набор значенийstatus,channelиcontent_typeможет расширяться: не считайте списки закрытыми.
#Диалог
| Поле | Тип | Описание |
|---|---|---|
id | строка (uuid) | идентификатор диалога |
channel | строка или null | тип канала: api, telegram, website … |
contact_id | строка или null | идентификатор контакта (клиента) в Sapport |
external_customer_id | строка или null | идентификатор клиента в вашей системе; отдаётся только у канала api, у мессенджеров всегда null (внешний идентификатор мессенджера не раскрывается) |
status | строка | состояние: open, handoff_pending, resolved, closed … |
mode | ai или operator | кто ведёт диалог: ai — отвечает ИИ (режимы auto и supervised), operator — человек (copilot и manual) |
language | строка или null | язык диалога (ISO 639-1) |
created_at | строка или null | начало диалога, ISO 8601 UTC |
updated_at | строка | последнее изменение или сообщение, ISO 8601 UTC; по нему идёт обход. Счётчик непрочитанного его не меняет |
#Сообщение
| Поле | Тип | Описание |
|---|---|---|
id | строка (uuid) | идентификатор сообщения |
conversation_id | строка | диалог |
sender | customer, ai, operator, system | отправитель: клиент, ИИ, оператор, служебное |
content_type | строка | вид содержимого: text, image, file … |
content | строка или null | текст; null — текста нет (только вложение) или сообщение удалено |
attachments | массив | вложения (сейчас не больше одного) |
deleted | логическое | true — сообщение удалено (приходит только при include_deleted=true, без текста и вложений) |
created_at | строка | время сообщения, ISO 8601 UTC |
Сообщения содержат персональные данные клиентов. Не записывайте их в журналы и не передавайте третьим лицам без основания.
Персональные данные и журнал выдач. Каждая выдача записывается в журнал выдач (см. ячейки и данные). На ячейке RU нужны узкий список разрешённых IP-адресов у ключа и страна получателя RU, иначе 403 pii_transfer_not_allowed приходит до чтения. Если строку журнала записать не удалось — 503, данные не отдаются. Диалоги личных ящиков и чаты кабинета ученика не отдаются; чужой, несуществующий или не-uuid идентификатор даёт 404 not_found.
Ответы чтения несут Cache-Control: private, no-store.
#GET /conversations
Статус: Доступно. Область
conversations:read.
Список диалогов с фильтрами и курсором. Порядок возрастающий по (updated_at, id): чтобы синхронизироваться, запоминайте next_cursor и возвращайтесь с ним.
| Параметр | Тип | Обязательный | Ограничения | Пример |
|---|---|---|---|---|
channel | строка | нет | фильтр по каналу | api |
status | строка | нет | фильтр по статусу | open |
mode | строка | нет | ai или operator | operator |
updated_after | строка | нет | диалоги, изменённые строго позже момента; ISO 8601 с поясом | 2026-10-11T00:00:00Z |
include_deleted | логическое | нет | принимается для единообразия, у диалогов ничего не добавляет (мягкого удаления нет) | false |
limit | целое | нет | от 1 до 100, по умолчанию 25 | 50 |
cursor | строка | нет | next_cursor предыдущего ответа | eyJ2Ijox… |
curl -sS "$SAPPORT_BASE_URL/conversations?channel=api&mode=operator&limit=50" \
-H "Authorization: Bearer $SAPPORT_API_KEY"
{
"data": [
{
"id": "7c1f0e52-3a4b-4d6e-9f10-2b8a5c3d9e01",
"channel": "api",
"contact_id": "0d94a7c8-5e2f-4b13-8a6c-91f4e7b02d35",
"external_customer_id": "client-4815",
"status": "open",
"mode": "operator",
"language": "ru",
"created_at": "2026-10-11T08:00:00Z",
"updated_at": "2026-10-11T08:15:30Z"
}
],
"next_cursor": null
}
Меняя фильтры, начинайте обход заново без cursor: курсор привязан к фильтрам. Недействительный, просроченный или чужой курсор — 422 validation_failed.
#GET /conversations/{id}
Статус: Доступно. Область
conversations:read.
Один диалог. Если диалога нет или он принадлежит другому арендатору — 404 not_found.
curl -sS "$SAPPORT_BASE_URL/conversations/7c1f0e52-3a4b-4d6e-9f10-2b8a5c3d9e01" \
-H "Authorization: Bearer $SAPPORT_API_KEY"
#GET /conversations/{id}/messages
Статус: Доступно. Область
conversations:read.
Сообщения диалога постранично: limit (по умолчанию 25, максимум 100), cursor, include_deleted. Порядок возрастающий по (created_at, id) — от старых к новым.
С include_deleted=true удалённое сообщение приходит как «надгробие»: deleted: true, content: null, без вложений.
{
"data": [
{ "id": "1a2b3c4d-0001-4000-8000-000000000001", "conversation_id": "7c1f0e52-3a4b-4d6e-9f10-2b8a5c3d9e01", "sender": "customer", "content_type": "text", "content": "Сколько стоит курс?", "attachments": [], "deleted": false, "created_at": "2026-10-11T08:15:30Z" },
{ "id": "1a2b3c4d-0002-4000-8000-000000000002", "conversation_id": "7c1f0e52-3a4b-4d6e-9f10-2b8a5c3d9e01", "sender": "ai", "content_type": "text", "content": "Курс стоит 9 900 ₽.", "attachments": [], "deleted": false, "created_at": "2026-10-11T08:15:33Z" }
],
"next_cursor": null
}
#POST /conversations
Статус: Планируется. Область
conversations:write.Idempotency-Key— рекомендуется.
Завести диалог из вашего канала (канал api). Один клиент вашей системы — один внешний идентификатор; повторный вызов с тем же внешним идентификатором должен возвращать тот же диалог (платформа ищет или создаёт диалог по внешнему идентификатору).
| Поле | Тип | Обязательный | Ограничения | Пример |
|---|---|---|---|---|
external_customer_id | строка | да | стабильный идентификатор клиента в вашем канале | client-4815 |
name | строка | нет | имя клиента | Анна |
email | строка | нет | [email protected] | |
phone | строка | нет | +79990001122 | |
page | объект | нет | контекст: адрес, заголовок, type, id | {"type":"course","id":"hydrolat-basics"} |
Имена полей предварительные.
curl -sS -X POST "$SAPPORT_BASE_URL/conversations" \
-H "Authorization: Bearer $SAPPORT_API_KEY" \
-H "Idempotency-Key: 6f3c1f3e-5b52-4a52-9e0b-0d8e3a2c9f11" \
-H "Content-Type: application/json" \
-d '{"external_customer_id":"client-4815","name":"Анна"}'
Ответ — объект диалога (201; повтор с тем же ключом — тот же диалог).
#POST /conversations/{id}/messages
Статус: Планируется. Области:
conversations:write(приём) иai_seller:invoke(ответ ИИ).Idempotency-Keyобязателен. Платная операция при вызове ИИ.
Передать сообщение клиента. Если диалог ведёт ИИ, платформа запускает ИИ-продажника.
#Запрос
| Поле | Тип | Обязательный | Ограничения | Пример |
|---|---|---|---|---|
content | строка | да | текст реплики; предел длины уточняется (для виджета — 4000 знаков) | Сколько стоит курс? |
attachments | массив | нет | вложения (форма не зафиксирована) | — |
| Параметр адреса | Тип | Описание |
|---|---|---|
wait | целое от 0 до 20 | секунды ожидания ответа ИИ. Не дождались — вернётся 202 с тем же заданием |
| Заголовок | Обязательный | Описание |
|---|---|---|
Idempotency-Key | да | уникальная строка на каждую новую реплику клиента; повтор с тем же ключом не создаёт дубль и не списывает деньги второй раз |
curl -sS -X POST "$SAPPORT_BASE_URL/conversations/cnv_EXAMPLE01/messages?wait=15" \
-H "Authorization: Bearer $SAPPORT_API_KEY" \
-H "Idempotency-Key: 8d4b2a40-1c63-4d4e-8f0a-6c2d3e1b7a90" \
-H "Content-Type: application/json" \
-d '{"content":"Сколько стоит курс?"}'
#Ответы
Ответ ИИ выполняется только асинхронно: запрос не держит соединение дольше wait. Формы ответа предварительные, кроме ai_reply и operation.
Принято, ответ ИИ готовится (202). ai_reply.status — queued, в operation — конверт асинхронной операции вида conversation.ai_reply:
{
"message": {
"id": "msg_EXAMPLE01",
"conversation_id": "cnv_EXAMPLE01",
"sender": "customer",
"content": "Сколько стоит курс?",
"created_at": "2026-10-11T08:15:30Z"
},
"ai_reply": { "status": "queued" },
"operation": {
"id": "op_EXAMPLE01",
"type": "conversation.ai_reply",
"status": "queued",
"created_at": "2026-10-11T08:15:30Z",
"resource": { "type": "message", "id": "msg_EXAMPLE01" }
}
}
Ответ готов в пределах wait (200). ai_reply.status — replied, операция завершена, в ней стоимость:
{
"message": {
"id": "msg_EXAMPLE01",
"conversation_id": "cnv_EXAMPLE01",
"sender": "customer",
"content": "Сколько стоит курс?",
"created_at": "2026-10-11T08:15:30Z"
},
"ai_reply": { "status": "replied" },
"reply": {
"id": "msg_EXAMPLE02",
"content": "Курс «Основы гидролатов» стоит 9 900 ₽. Хотите, расскажу, что входит?"
},
"operation": {
"id": "op_EXAMPLE01",
"type": "conversation.ai_reply",
"status": "succeeded",
"created_at": "2026-10-11T08:15:30Z",
"completed_at": "2026-10-11T08:15:33Z",
"resource": { "type": "message", "id": "msg_EXAMPLE01" },
"usage": { "cost": { "amount": "0.42", "currency": "RUB" }, "model": "model-EXAMPLE" }
}
}
Не дождались — вернётся 202 с текущим состоянием операции. Дальше выберите способ получения ответа (Асинхронные операции):
- опросите
GET /operations/{id}— поresourceпрочитайте сообщения диалога; - прочитайте
GET /conversations/{id}/messages; - подпишитесь на событие
message.createdиoperation.completed(вебхуки) — предпочтительный способ.
Повтор того же запроса с тем же Idempotency-Key возвращает ту же операцию, новая не запускается.
ИИ не отвечал по причине — это успешный ответ 200, а не ошибка. Операция не создаётся, деньги не списываются:
{
"message": {
"id": "msg_EXAMPLE01",
"conversation_id": "cnv_EXAMPLE01",
"sender": "customer",
"content": "Сколько стоит курс?",
"created_at": "2026-10-11T08:15:30Z"
},
"ai_reply": { "status": "skipped", "reason": "operator_active" }
}
Объект ai_reply приходит и в событии message.created для сообщения клиента. См. следующий раздел.
#Ошибки
| Статус | Код | Когда | Что делать |
|---|---|---|---|
422 | validation_failed | нет текста, недопустимые поля; детали в errors[] | Исправить запрос |
413 | payload_too_large | тело больше 256 КБ | Уменьшить тело |
401 | invalid_api_key, wrong_cell | ключ | Проверить ключ и адрес ячейки |
402 | insufficient_funds | на кошельке не хватает средств | Пополнить кошелёк |
402 | spend_limit_reached | достигнут предел трат | Дождаться окна или поднять предел |
403 | scope_missing | нет conversations:write или ai_seller:invoke | Проверить области |
404 | not_found | диалога нет или он чужой | Проверить идентификатор |
409 | idempotency_conflict | тот же ключ с другим телом | Новый ключ для новой реплики |
409 | operation_in_progress | параллельный повтор | Подождать Retry-After и повторить |
429 | rate_limited | предел частоты | Подождать Retry-After |
503 | upstream_unavailable | недоступна языковая модель | Повторить с тем же ключом |
503 | service_unavailable | недоступно хранилище пределов; есть Retry-After | Повторить с тем же ключом |
Общий разбор классов — Соглашения.
#Причины отсутствия ответа ИИ
Платформа принимает сообщение клиента всегда, когда запрос корректен. ИИ может не ответить — и это не сбой. Состояние ответа ИИ возвращается полем ai_reply в теле успешного ответа на сообщение клиента и в событии message.created.
ai_reply — объект {status, reason?}:
status | Смысл |
|---|---|
replied | ИИ ответил: ответ — отдельное сообщение диалога |
queued | ответ готовится; результат — по operation или событием message.created |
skipped | ИИ не ответит; причина в reason |
reason (при skipped) | Смысл | Что делать вашему серверу |
|---|---|---|
operator_active | диалог ведёт оператор; ИИ не отвечает | Ничего: оператор ответит. Ответ придёт событием message.created |
spam | сообщение признано спамом | Не пересылать клиенту автоответ; при необходимости — журнал |
flow_reply | ответил сценарий (поток), а не ИИ | Взять ответ сценария из диалога |
ai_unavailable_handoff | ИИ недоступен; диалог передан оператору | Сообщить клиенту, что ответит сотрудник |
no_agent | для канала не назначен агент | Назначить агента в кабинете |
daily_limit | исчерпан суточный предел ответов ИИ в канале | Сообщение сохранено, диалог получил состояние handoff_pending, ответ ИИ не запрашивался и не оплачивался. Сообщить клиенту, что ответит сотрудник |
Для канала api суточный предел работает так же, как у виджета: значение берётся из настройки канала ai_daily_limit (по умолчанию 200 в сутки, максимум 10 000); в событии conversation.handoff_requested диалог попадает к оператору (События).
#POST /conversations/{id}/operator-messages
Статус: Планируется. Область
conversations:write.Idempotency-Key— рекомендуется.
Ответ оператора, введённый в вашей системе. Сообщение пишется в диалог от имени оператора и уходит клиенту событием message.created через ваш вебхук.
| Поле | Тип | Обязательный | Ограничения | Пример |
|---|---|---|---|---|
content | строка | да | Здравствуйте! Меня зовут Анна. | |
operator_id | строка | нет | идентификатор сотрудника (предварительно) | usr_EXAMPLE01 |
curl -sS -X POST "$SAPPORT_BASE_URL/conversations/cnv_EXAMPLE01/operator-messages" \
-H "Authorization: Bearer $SAPPORT_API_KEY" \
-H "Idempotency-Key: 0a1f6f7b-8e0d-47c5-9f53-5a1b27c9d3e4" \
-H "Content-Type: application/json" \
-d '{"content":"Здравствуйте! Меня зовут Анна."}'
#Перехват, возврат ИИ и закрытие
Статус: Планируется. Область
conversations:write.
| Маршрут | Действие | После вызова |
|---|---|---|
POST /conversations/{id}/takeover | Оператор берёт диалог | mode=operator; ИИ перестаёт отвечать |
POST /conversations/{id}/release | Оператор возвращает диалог ИИ | mode=ai; следующие реплики клиента получают ответ ИИ |
POST /conversations/{id}/close | Закрыть диалог | диалог закрыт |
Тела запросов в проекте не описаны. Повтор вызова должен быть безопасным; Idempotency-Key передавайте в любом случае. Действие, недопустимое в текущем состоянии диалога (например, takeover закрытого диалога), возвращает 409 invalid_state.
curl -sS -X POST "$SAPPORT_BASE_URL/conversations/cnv_EXAMPLE01/takeover" \
-H "Authorization: Bearer $SAPPORT_API_KEY" \
-H "Idempotency-Key: c2d6f3a9-4b0e-4a8b-8b7f-2d9a6e1f0c55"
#События
Получайте результат вебхуками вместо опроса (Вебхуки). Конверт события — {id, type, api_version, created_at, livemode, data}.
| Событие | Когда | В data |
|---|---|---|
conversation.created | создан диалог | conversation_id |
message.created | в диалоге появилось сообщение: клиента, ИИ или оператора | conversation_id, message_id, sender; для сообщения клиента — ai_reply: {status, reason?} |
conversation.handoff_requested | запрошена передача оператору (в том числе по daily_limit и ai_unavailable_handoff) | conversation_id, lead_id (если у диалога есть лид) |
conversation.summary_ready | готова сводка диалога | conversation_id |
operation.completed | завершился ответ ИИ (succeeded, failed, canceled) | конверт operation |
Событие передачи оператору одно — conversation.handoff_requested; имя handoff.requested относится к вебхукам версии 1 и в v2 не используется.
Событие message.created «тонкое» всегда: текст сообщения в него не входит, даже при include_pii: true. Содержимое получайте запросом GET /conversations/{id}/messages. Флаг include_pii подписки добавляет ПДн в conversation.created, conversation.handoff_requested и conversation.summary_ready; для него у интеграции нужны conversations:read и leads:read (Вебхуки).
Событие message.created приходит и на сообщения оператора, и на ответы ИИ. Чтобы не отправить клиенту собственное сообщение дважды, сравнивайте message_id с тем, что вы уже обработали.
#Примеры на четырёх языках
Во всех примерах ключ и адрес берутся из переменных окружения; идентификатор диалога — условный.
#curl
curl -sS -X POST "$SAPPORT_BASE_URL/conversations/cnv_EXAMPLE01/messages?wait=15" \
-H "Authorization: Bearer $SAPPORT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"content":"Сколько стоит курс?"}'
#JavaScript (Node.js 18+)
import { randomUUID } from 'node:crypto';
const BASE = process.env.SAPPORT_BASE_URL; // https://formula-cream.pro/api/public/v1
const KEY = process.env.SAPPORT_API_KEY;
export async function sendCustomerMessage(conversationId, text, idempotencyKey = randomUUID()) {
const res = await fetch(`${BASE}/conversations/${conversationId}/messages?wait=15`, {
method: 'POST',
headers: {
Authorization: `Bearer ${KEY}`,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ content: text })
});
const body = await res.json();
if (!res.ok) {
// application/problem+json: ветвимся по code
const err = new Error(`${body.code}: ${body.detail}`);
err.code = body.code;
err.requestId = body.request_id;
throw err;
}
// 202 — ответ ещё готовится (body.operation): ждите вебхук message.created
// или опрашивайте GET /operations/{id}
return { status: res.status, body, idempotencyKey };
}
#Python (httpx)
import os
import uuid
import httpx
BASE = os.environ["SAPPORT_BASE_URL"] # https://formula-cream.pro/api/public/v1
KEY = os.environ["SAPPORT_API_KEY"]
def send_customer_message(conversation_id: str, text: str, idempotency_key: str | None = None):
key = idempotency_key or str(uuid.uuid4())
res = httpx.post(
f"{BASE}/conversations/{conversation_id}/messages",
params={"wait": 15},
headers={"Authorization": f"Bearer {KEY}", "Idempotency-Key": key},
json={"content": text},
timeout=30,
)
body = res.json()
if res.status_code >= 400:
# application/problem+json
raise RuntimeError(f'{body.get("code")}: {body.get("detail")} (request {body.get("request_id")})')
return res.status_code, body, key
#PHP
<?php
function sapport_send_customer_message(string $conversationId, string $text, ?string $idempotencyKey = null): array
{
$base = getenv('SAPPORT_BASE_URL'); // https://formula-cream.pro/api/public/v1
$key = getenv('SAPPORT_API_KEY');
$idempotencyKey ??= bin2hex(random_bytes(16));
$ch = curl_init("$base/conversations/$conversationId/messages?wait=15");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $key",
"Idempotency-Key: $idempotencyKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['content' => $text], JSON_UNESCAPED_UNICODE),
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$body = json_decode($raw, true);
if ($status >= 400) {
throw new RuntimeException(($body['code'] ?? 'error') . ': ' . ($body['detail'] ?? ''));
}
return [$status, $body, $idempotencyKey];
}
#Заметки
- Не повторяйте с новым ключом. После сетевого сбоя повторяйте запрос с тем же
Idempotency-Key, иначе клиент получит два ответа, а кошелёк спишет два раза. - Один ключ — одна реплика. Не используйте один
Idempotency-Keyдля разных реплик. - Клиентские сообщения из вашего канала не должны быть длиннее предела. Предел для серверного API проектом не определён; ориентируйтесь на 4000 знаков виджета.
- Канал
apiизолирован. Для него никогда не используется общий промпт площадки; ИИ отвечает по настройкам вашего агента и базе знаний арендатора. - ПДн. Содержимое диалогов — персональные данные. В ячейке RU подключение интеграции с областями
conversations:*требует списка IP, страны размещения принимающей системы (только РФ; иначеpii_transfer_not_allowed) и основания передачи; передача выдач фиксируется в журнале выдач, который арендатор видит в кабинете и поGET /disclosures(Ячейки и данные). - Журнал изменений: 90-changelog.
#Открытые вопросы
- Полные схемы тел запросов и ответов (имена полей, перечень значений
status,mode,sender) — только в OpenAPI при выпуске; здесь поля предварительные. Техническое. - Предельная длина реплики клиента и формат вложений в серверном API. Техническое.
- Правила слияния диалогов при повторном
POST /conversationsс тем же внешним идентификатором клиента: что возвращается, если диалог закрыт. Техническое. - Тела и коды
409 invalid_stateдляtakeover,release,closeв закрытом диалоге. Техническое.