Диалоги
Диалог (conversation) — карточка общения с одним контактом, объединяющая один или несколько тредов (переписок в конкретных каналах: WhatsApp, Telegram, Instagram и т.д.). Через эти эндпоинты вы ведёте жизненный цикл диалога — открываете и закрываете его, назначаете участников (операторов) через participants, добавляете внутренние заметки и метки, читаете единую хронологию событий (events) и тред-структуру. Большинство операций мутируют лог событий диалога, доступный через GET /conversations/{id}/events.
Создание диалога
Создать диалог можно двумя путями: POST /conversations для уже известного контакта либо POST /dialogs — интеграторский вход, который по идентификатору account (телефон или username) за один вызов создаёт или переиспользует связку contact → contact_account → conversation → thread; для телефонных каналов (sms, whatsapp) account нормализуется как телефон. GET /dialogs/lookup ищет существующий активный диалог с учётом родственных типов каналов (whatsapp/whatsapp_business/sms, telegram/telegram_bot, instagram/instagram_business). Оба маршрута — POST /dialogs и GET /dialogs/lookup — доступны только business-компаниям: для семейной (kids) компании оба отвечают 403 {"message": "...", "error": "company_type_forbidden"} — эти интеграторские точки входа могли бы создать или найти диалог целиком на канале, закрытом для семьи гейтом «18+», в обход проверок доступности диалога выше.
Канал с can_start_conversation: false (сейчас это только Telegram Business, см. группу «Каналы») не открывает переписку с контактом, с которым у этого канала ещё нет треда. POST /dialogs через такой канал отвечает 422 {"data": {"message": "…", "error_code": "channel_cannot_start_conversation"}}, контакт и аккаунт, созданные под этот вызов, не сохраняются. Тот же ответ, тоже в обёртке data, получает POST /conversations/{conversation}/threads, если новый тред создаётся на таком канале. Если у канала уже есть тред с этим аккаунтом контакта в любом диалоге компании, вызов проходит как обычно.
Список и фильтры
Список GET /conversations использует постраничную пагинацию (page/perPage, ответ содержит блок pagination {page, perPage, total, lastPage, has_more}) и поддерживает фильтры thread_filter (all|awaiting|unread|assigned|unassigned|my_team|archived|groups), channel_id, funnel_id, funnel_stage_ids (id стадий воронки через запятую), contact_id, lifecycle_status (active|closed), is_archived, unread_only, search, сортировку sortBy (id|last_message_at|created_at|updated_at) / sortDir (asc|desc) и include_counters=1 (добавить в ответ блок counters).
Семантика search (обновлено 28.09.2026 — поиск диалогов и тредов больше не читает contacts/contact_accounts полным сканом): одно и то же правило действует у search в GET /conversations и GET /threads; с 29.09.2026 отдельным изменением тем же правилом стал руководствоваться и GET /conversations/counters (ниже) — до этой даты параметр search там не был описан в правилах запроса и потому тихо отбрасывался, не сужая счётчики. Термин ищется по контакту диалога (display_name, first_name, last_name, company) ИЛИ по контакт-аккаунту ЛЮБОГО треда этого диалога (profile_display_name, address_book_name, external_id, username) — для GET /threads те же поля читаются от диалога-владельца треда и от собственного контакт-аккаунта треда; совпадения по любому полю любой из групп достаточно, поиск всегда ограничен текущей компанией. Спецсимволы ILIKE-маски %, _ и экранирующий их \ теперь ищутся БУКВАЛЬНО, а не как SQL-маска: раньше search=% фактически означал «без фильтра» (совпадал с чем угодно) — это исправлено.
Термин обрезается (trim) до сравнения: например, search=%20 (один пробел) равносилен отсутствию фильтра — вернутся все записи, как если бы search не передавали вовсе. Это отдельно от случая, когда сам параметр передан буквально пустым (search=, ноль символов) — такой запрос отвечает 422, как и для любого другого строкового query-параметра без значения; обрезка тут ни при чём, это происходит раньше, на этапе валидации запроса. Форма ответа GET /conversations/counters не меняется — search лишь сужает выборку, по которой считаются те же ключи счётчиков.
with_total (необязательный boolean, по умолчанию true, с 2026-09-06) переключает пагинацию списка диалогов на облегчённый режим без точного подсчёта. По умолчанию (или при with_total=1) поведение не меняется: total и lastPage — точные значения из COUNT(*). При with_total=0 этот COUNT(*) — доминирующая часть запроса, дававшая p95 ~2 с и пики до 15 с на списке диалогов — не выполняется вовсе, а total/lastPage в ответе равны null. has_more (boolean) присутствует внутри pagination всегда, в обоих режимах, — это единственный надёжный признак «есть ли следующая страница» при with_total=0; не полагайтесь на эвристику «страница пришла полной» — она ложно срабатывает, когда количество диалогов кратно perPage. Если интерфейсу не нужна постраничная нумерация (только бесконечная подгрузка) — передавайте with_total=0 и листайте по has_more.
С 2026-09-08 диалоги контактов, скрытых из рабочего inbox (POST /v1/contacts/{contact}/hide-from-inbox, см. группу «Контакты»), не попадают ни в GET /conversations, ни в GET /threads, ни в один счётчик GET /conversations/counters. Параметра «показать скрытые» здесь нет — скрытые контакты видны только через GET /contacts?hidden=1. При этом сам диалог остаётся читаемым по id (GET /conversations/{id}, GET /messages), а исходящие вебхуки по нему продолжают уходить.
Ключ счётчика `aisar_notifications`
Блок counters (в списке диалогов при include_counters=1 и в GET /conversations/counters) несёт ключ aisar_notifications — присутствует всегда, включая пустой ответ для компании без активной подписки. Смена семантики (2026-08-24, без нового префикса версии): до 24.08 ключ считал служебные диалоги AISAR внутри самих диалогов (см. историю ниже); теперь платформенные уведомления вынесены в отдельный ресурс GET /platform-notifications, и counters.aisar_notifications — это число НЕПРОЧИТАННЫХ записей platform_notifications компании: записей за последние 90 дней с created_at позже курсора ui_preferences.aisar_notifications.seen_until текущего пользователя (курсор null — считаются все записи в окне). Формат значения (целое число) не изменился, изменился только источник данных и то, что он теперь считается per-user, а не per-company.
Ожидание ответа (`awaiting_since`) и «Не требует ответа»
ConversationResource (и в списке GET /conversations, и в GET /conversations/{id}) содержит вычисляемое поле awaiting_since (ISO-8601 строка либо null) — момент первого неотвеченного входящего сообщения; null, если диалог сейчас не ожидает ответа. Семантика совпадает с thread_filter=awaiting. Значение считается обратным сканированием истории диалога (не более 100 последних сообщений); для диалога с исключительно длинным неотвеченным хвостом это приближение — момент самого старого входящего сообщения в пределах этого окна, а не гарантированно первого за всю историю.
POST /conversations/{conversation}/dismiss-awaiting (2026-08-24, право conversation.update) — оператор помечает диалог как «не требует ответа»: сервер выставляет служебное поле awaiting_dismissed_at и с этого момента awaiting_since в ответе диалога держится null, пока не придёт следующее входящее сообщение (оно снова взводит awaiting_since как обычно). Ответ — { "data": { "id": <conversation_id>, "awaiting_since": null } }.
Треды как самостоятельная сущность
Тред можно листать и как самостоятельную сущность: GET /threads возвращает кросс-диалоговый список тредов (режим отображения «по каналам») с теми же фильтрами и пагинацией, а POST /threads/{id}/close|open|archive|unarchive меняет состояние конкретного треда независимо от диалога (переоткрытие вернёт 422, если у контакта уже есть другой активный диалог). Блок counters в ответе GET /threads содержит ровно восемь ключей — all, awaiting, unread, assigned, unassigned, my_team, archived, groups (все целые числа, считаются по тому же набору тредов, что и список, но без применённого thread_filter); ключа aisar_notifications здесь нет — он есть только у счётчиков диалогов (GET /conversations/counters).
Закрепление диалогов личное: пин видит только тот, кто его поставил. PUT /conversations/{conversation}/pin закрепляет диалог, PUT /threads/{thread}/pin (режим «по каналам») закрепляет тред; DELETE по тем же путям снимает собственный пин. Ответ 200: {"data": {"conversation_id", "thread_id" | null, "pinned_at" | null}}. Лимит: 5 закреплённых диалогов и отдельно 5 закреплённых тредов на пользователя; сверх лимита PUT отвечает 422 {"data": {"message": "…", "code": "pin_limit_reached"}}, архивный диалог или тред закрепить нельзя (422), а архивация снимает пин. DELETE без собственного пина отвечает 404. Лимит запросов: 60 в минуту. Эндпоинты /pin доступны и embed-токенам. В элементах GET /conversations, GET /threads и в ответах GET /conversations/{id} и GET /conversations/{id}/threads/{id} есть поле pinned_at (ISO 8601 или null, для текущего пользователя). Закреплённые идут первыми в списках GET /conversations и GET /threads (кроме запросов с search, вкладки thread_filter=archived и сортировки не по last_message_at); фильтры при этом действуют как обычно, поэтому первая страница может содержать больше perPage элементов на число показанных пинов, а total учитывает их.
С 28.09.2026 доступ к треду ПО ID — GET/PATCH /conversations/{conversation}/threads/{thread}, GET/POST .../threads/{thread}/events, POST /threads/{id}/close|open|archive|unarchive — дополнительно проверяет, виден ли пользователю именно этот тред: та же логика доступа к каналам (channel_access), что и у списков, при скрытом канале отвечает 404 «Thread not found.» (администратор компании видит любой тред своей компании). Отдельно от этого, если канал треда закрыт гейтом «18+» для переписки Kids, ответ — 403 adult_child_correspondence, и это правило не знает исключений по роли — блокирует чтение и изменение даже администратору компании (в отличие от проверки channel_access чуть выше, где админ видит всё). POST /conversations/{conversation}/threads (создание нового треда) при скрытом диалоге отвечает 404 «Conversation not found.», а при недоступном пользователю, архивном или заблокированном 18+-гейтом канале — 422 «Invalid channel for current company.». Список тредов диалога — GET /conversations/{conversation}/threads — с 29.09.2026 при скрытом диалоге тоже отвечает 404 «Conversation not found.» (раньше отвечал 200 с пустым списком, что отличало скрытый диалог от несуществующего — разный ответ был оракулом на существование чужого диалога).
PATCH .../threads/{thread} больше не позволяет сменить channel_id или contact_account_id у существующего треда: значение, отличное от текущего, отвечает 422 («Cannot change thread channel.» либо «Cannot change thread contact account.» соответственно), а то же самое значение, отправленное обратно (например при PATCH всего объекта целиком), — не ошибка, а no-op. То же самое верно для is_group и external_thread_id: другое значение отвечает 422 («Cannot change thread is_group.» / «Cannot change thread external_thread_id.»), а то же самое значение (включая null = null) — no-op; отдельное исключение — тред канала-виджета (live-chat), где external_thread_id хранит токен посетителя чата: попытка изменить его там отвечает 422 «Cannot change live chat thread external id.», по тому же принципу «то же значение — no-op».
Статусы прочтения
Read-статусы отдаёт GET /conversations/{id}/reads — кто из операторов и до какого сообщения прочитал диалог (per-user last_read_message_id/last_read_at), а POST /conversations/{id}/reads выставляет позицию прочтения текущего пользователя. У POST /conversations/mark-read поле conversation_ids необязательно (без него помечаются все диалоги компании), а read-статус диалога обновляется и автоматически при исходящем сообщении оператора. Поскольку для крупной компании массовая отметка может занять десятки секунд, POST /conversations/mark-read ставит upsert и рассылку CRM-бейджей в очередь и отвечает 202 Accepted без поля updated_count в теле — реальное число обновлённых диалогов приходит только асинхронно, через WebSocket-событие message.read с bulk: true и updated_count. Вызов с пустым массивом conversation_ids: [] в очередь не ставится и отвечает обычным 200.
POST /conversations/{conversation}/mark-unread — обратная операция: отматывает курсор прочтения на секунду раньше последнего входящего сообщения диалога (или его треда — необязательный thread_id), так что это сообщение снова становится непрочитанным. Область действия зависит от настройки компании shared_read_receipts: при общем счётчике отматывается курсор КАЖДОГО оператора, у кого он был позже новой отметки; при индивидуальном — только курсор текущего пользователя. Если во всём диалоге (или треде) вообще нет входящих сообщений — 422 { "data": { "message": "Conversation has no inbound messages to mark as unread." } }. Успешный ответ: { "data": { message, item, unread_count, shared_read_receipts } } — item в той же форме, что и у reads (может быть null), unread_count — сколько входящих сообщений теперь непрочитано у текущего пользователя, shared_read_receipts эхом отдаёт настройку компании.
ИИ-копилот и действия ИИ-агента
ИИ-копилот: POST /conversations/{id}/ai-suggestions/generate генерирует черновик ответа для диалога, а .../ai-suggestions/{id}/accept (с опциональной правкой final_text) и .../reject фиксируют решение оператора по подсказке.
.../ai-suggestions/generate не требует фичи ai_agents на уровне маршрута — она проверяется внутри самой генерации, вместе с тарифным лимитом ИИ-ответов, и различает несколько отказов: агенту не назначен диалог — 404 (без тела с кодом); агент неактивен — 422 { "code": "agent_inactive" }; исчерпан собственный $-бюджет агента (настраивается компанией) — 422 { "code": "budget_exceeded" }; исчерпан тарифный лимит ИИ-ответов за расчётный период (ai_replies_included) — в том числе если тариф компании вовсе не несёт ai_agents (например, после даунгрейда) — 422 { "code": "plan_limit_exceeded" }; использование достигло технического порога защиты от абьюза (кратно тарифному лимиту) — 422 { "code": "usage_abuse_stopped" }; слишком частые вызовы — 429 (без тела с кодом).
ИИ-агент может предлагать действия, требующие подтверждения оператора (например закрытие диалога, блокировка контакта, удаление сообщений) — очередь таких предложений по конкретному диалогу отдаёт GET /conversations/{id}/ai-pending-actions: каждый элемент несёт tool_key, человекочитаемый comment, произвольный payload и approval_status (pending|approved|rejected); само решение фиксируется company-scoped маршрутами approve/reject.
POST /conversations/{id}/ai-tools/summary возвращает краткое ИИ-резюме диалога для передачи смены (summary_text, message_count, tokens_used); требует фичи ai_agents на уровне маршрута (без неё — 403 feature_not_available), принимает необязательный locale (иначе берётся язык приложения), действует лимит 60 запросов ИИ-ассистента в час на компанию (429 ai_assist_rate_limited), пустой диалог — 422 empty_conversation, ошибка провайдера — 502 ai_provider_error.
GET /conversations/{id}/ai-tools/summary отдаёт СОХРАНЁННОЕ резюме диалога без нового вызова ИИ: { "data": { "summary": null | { "summary_text", "message_count", "locale", "generated_at", "generated_by": { "id", "name" } | null } } }. summary: null — резюме ещё не создавалось, либо диалог смешанный и пользователю видна только часть его каналов, либо контакт скрыт из inbox. Диалог другой компании или недоступный пользователю — 404. POST того же пути теперь дополнительно возвращает generated_at (ISO-8601) и saved (boolean): true, если свежее резюме записано как сохранённое (перезаписывает прежнее), false — если по тем же причинам сохранение пропущено.
GET /conversations/{id}/notes принимает необязательный order (asc по умолчанию, desc) — порядок по времени создания заметки; любое другое значение — 422. Остальные фильтры (thread_id, is_private) без изменений.
С 2026-09-29 ИИ-агент автоответа и черновик копилота (.../ai-suggestions/generate) получают в контекст только переписку с клиентом — входящие и исходящие сообщения. Внутренние заметки (в том числе ИИ-сводка звонка) и системные карточки ленты в контекст модели не попадают, поэтому агент не может пересказать клиенту то, что предназначалось только команде. ИИ-резюме для передачи смены (POST /conversations/{id}/ai-tools/summary) заметки по-прежнему учитывает, но как внутренние записи, которые клиент не видел.
Доступ к каналам и проверки принадлежности (с 2026-09-26)
Правила доступа к каналам (channel_access команд) теперь действуют не только на список, но и на все пути диалога по id: GET/PATCH/DELETE /conversations/{id}, закрытие/открытие, архив, «не требует ответа», события, заметки, теги, прочтения, участники, треды и ИИ-инструменты. Диалог, у которого есть треды, но ни один не лежит в доступном пользователю канале, отвечает 404 — так же, как диалог другой компании (диалог без тредов остаётся доступным). Администратор компании видит всё. Если последнее сообщение диалога пришло в недоступный пользователю канал, last_message/last_message_id берутся из доступного треда, а если такого нет — равны null. Фильтр ?unread_only=1 списка GET /conversations тоже учитывает только сообщения доступных пользователю каналов — раньше он читал входящие сообщения ЛЮБОГО канала, и диалог, видимый только через один тред, мог попасть в unread-выборку по непрочитанному на другом, скрытом от пользователя канале, хотя unread_count_for_current_user у него был 0; для администратора/сотрудника платформы (без ограничений channel_access) поведение не изменилось.
ИИ-инструменты диалога: POST /conversations/{id}/ai-tools/summary требует права conversation.view, а генерация, принятие и отклонение подсказки копилота (.../ai-suggestions/generate|{id}/accept|{id}/reject) — conversation.update (без права — 403, закрытый каналом диалог — 404). Списки действий ИИ-агента (GET /ai-pending-actions, GET /conversations/{id}/ai-pending-actions, GET /ai-agents/instances/{id}/pending-actions и .../stats) требуют conversation.view и показывают только действия по доступным пользователю диалогам (по скрытому диалогу — пустой список); POST /ai-pending-actions/{event}/approve|reject требует conversation.update, по скрытому диалогу — 404.
С 28.09.2026 «смешанный» диалог (треды на нескольких каналах) при чтении СКВОЗНОГО содержимого сужается до тредов/каналов, видимых пользователю, — вместо 404 на весь диалог: GET /conversations/{id}/events, список заметок (GET .../notes), POST /conversations/{id}/ai-tools/summary и генерация подсказки копилота (POST .../ai-suggestions/generate) отдают только то, что относится к доступным тредам/каналам (для ИИ-резюме и копилота это означает, что в контекст модели попадают только сообщения из доступных тредов); запись на скрытом треде просто отсутствует в выдаче, а не превращает весь ответ в 404. На этом уровне сотрудник платформы и администратор компании видят всё без сужения (то же исключение, что и у карточки диалога) — это отличает данную группу эндпоинтов от прямых by-id путей треда выше, где 18+-гейт не знает исключений по роли. Для заметок (ConversationNoteController) это означает: существующая заметка на скрытом треде, запрошенная по её id, отвечает 404 «Conversation note not found.»; создание заметки со скрытым thread_id — 422 «Invalid thread for current conversation.»; а GET .../notes?thread_id=<скрытый> — не ошибка, а просто пустая страница (тред существует и принадлежит диалогу, но не виден пользователю; несуществующий thread_id по-прежнему отвечает 422 через exists:threads,id).
С 28.09.2026 POST /ai-pending-actions/{event}/approve и .../reject дополнительно проверяют, что {event} — настоящее, созданное платформой предложение ИИ, а не подделка: событие с type, отличным от ai_action_pending_approval, либо с непустым author_user_id (человеческое событие не может быть системным предложением) отвечает 404 { "data": { "message": "Not found." } } — тем же кодом и телом, что и событие другой компании или скрытого диалога, без отдельного признака «это подделка». Уже решённое предложение (approval_status — approved либо rejected) отвечает 409 { "data": { "code": "already_resolved" } } на повторный вызов. Для предложения с payload.tool_key: "block_contact" действие approve дополнительно требует право contact.update (как и настоящий POST /contacts/{contact}/block, который оно исполняет) и всегда отказывает embed-токену (403); reject того же предложения — то есть просто отклонить намерение ИИ, не выполняя его, — требует лишь conversation.update, но embed-токену тоже отвечает 403. Любое другое tool_key (например close_conversation) не требует прав сверх conversation.update, которым уже авторизован сам маршрут, для обоих решений.
С 30.09.2026 для семейных (kids) компаний диалог, ВСЕ треды которого лежат на каналах, закрытых для семьи гейтом «18+» (переписка совершеннолетнего ребёнка — тот же гейт, что и у прямых by-id путей треда выше), полностью исключён с этого уровня: его нет ни в GET /conversations, ни в его total, ни в GET /conversations/counters, ни в блоке counters при include_counters=1, ни в realtime-счётчиках; по id — все пути (карточка, изменение, закрытие/открытие, архив, заметки, теги, события, участники, прочтения, ИИ-инструменты, список тредов) отвечают 404 «Conversation not found.», а не 403 — диалог ведёт себя как несуществующий, а не как запрещённый. Realtime-канал такого диалога (conversation.{id}) тоже не авторизуется — попытка подписаться отклоняется наравне с чужим диалогом. «Смешанный» диалог (есть и разрешённый, и закрытый 18+-гейтом тред) остаётся видимым БЕЗ закрытого треда — так же, как при обычном channel_access в разделе выше; при этом непрочитанные, awaiting_since и порядок списка (sortBy=last_message_at) для такого диалога считаются только по разрешённым тредам, закрытый тред в эти цифры не входит. Сотрудники платформы и администраторы компании видят всё без сужения, как и в остальных проверках доступа к каналам этого раздела. Тред этого же диалога, запрошенный ПО СВОЕМУ id (GET/PATCH /conversations/{conversation}/threads/{thread} и т.д.) — по-прежнему 403 adult_child_correspondence без исключения по роли (см. «Треды как самостоятельная сущность» выше), а не 404: 404 здесь — только для диалога целиком.
Межтенантные проверки: contact_id при создании диалога и deal_id/team_id при создании и изменении — только записи своей компании (не удалённые), иначе 422. В PATCH треда last_message_id может указывать только на сообщение этого же треда своей компании (иначе 422), а при создании треда поле больше не принимается — у нового треда оно всегда null. То же верно для last_message_at: при создании треда поле не принимается (у нового треда всегда null), а в PATCH допускается только момент не позже текущего (с небольшим допуском на рассинхронизацию часов клиента) — более позднее значение отвечает 422.
Групповые чаты
Групповые чаты (WhatsApp-группы и т.п.) обслуживаются отдельными маршрутами: GET /group-chats/{id}/participants возвращает карточку группы и постраничный (page/per_page, по умолчанию 50) список участников с ролями (superadmin|admin|member, отсортированы по старшинству), а POST /group-chats/{id}/refresh принудительно перезапрашивает метаданные группы из мессенджер-рантайма (503, если у канала нет рантайм-инстанса или рантайм не принял запрос) и отдаёт обновлённый состав.
Значения перечислений
Значения перечислений в телах запросов:
- Участники добавляются с
member_type(user|team|bot). - События несут
type(created|comment|status_changed|archived|unarchived|assigned|unassigned и другие внутренние значения). С 28.09.2026 из телаPOST .../events(диалога или треда) принимается толькоtype: "comment"— любое другое значение отвечает422; остальные значения появляются в хронологии, только когда их создаёт сама платформа (смена статуса, архивация, назначение и т.п.), через эти маршруты клиент их создать не может. - Метки — с
source(manual|automation|integration).
В POST /conversations/{conversation}/events необязательное поле thread_id привязывает комментарий к конкретному треду диалога, а не ко всему диалогу. С 28.09.2026 этот тред обязан не только принадлежать данному диалогу, но и быть видимым пользователю (правила доступа к каналам, 18+): тред другого диалога, несуществующий тред и скрытый от пользователя тред этого же диалога отвечают одинаково — 422 «Invalid thread for current conversation.», без отдельного кода для каждой причины.
Файлы и медиа
GET /conversations/{conversation}/attachments отдаёт постраничную галерею вложений диалога для контекст-панели (все треды диалога, с учётом доступных пользователю каналов); необязательный kind фильтрует по image|video|document|audio. С 30.09.2026 для недоступного диалога (скрытого правилами доступа к каналам или целиком лежащего на канале, закрытом для семьи 18+) маршрут отвечает 404 «Conversation not found.» — тем же кодом, что и остальные by-id пути диалога, а не 200 с пустой галереей, как раньше (пустой ответ маскировал недоступный диалог под диалог без вложений — разный ответ был оракулом на существование чужого диалога). Каждая строка — {attachment_id, message_id, kind, mime_type, size, name, thumb_url, created_at}; thumb_url минтится только для image/video (для остальных kind — null) как короткоживущая (20 минут) подписанная ссылка на тот же роут .../media с w=640 (webp-превью) — в отличие от постоянной подписи MessageAttachmentResource.url, срок жизни ограничен намеренно, чтобы страница галереи не была способом массово выгрузить бессрочные ссылки на медиа.
С 2026-09-08 у эндпоинта появился второй, необязательный фильтр — family (media | document). Это та же классификация, что и kind в галерее контакта (GET /contacts/{contact}/attachments): media — image, video, ig_story, ig_reel, ig_post, story_mention; document — document, file, contact, audio; всё остальное (voice, sticker, template, ephemeral, unsupported_type, share, ig_story_reply, location) отбрасывается. Смысл kind при этом не изменился — это по-прежнему СЫРОЙ message_attachments.type (image|video|document|audio), поэтому kind=document возвращает ровно type = "document", как и раньше. Параметры складываются: family=document&kind=audio — только аудиофайлы. Если не передан ни один из них, поведение прежнее: отдаётся всё, включая голосовые и стикеры.
Диалог в instagram (личный аккаунт)
Для каналов типа instagram (личный аккаунт), если этот тип доступен вашей компании, POST /dialogs принимает в account @username, ссылку на профиль (например https://instagram.com/username/) или числовой id аккаунта; повторные вызовы с username, ссылкой или id одного аккаунта возвращают тот же диалог. Некорректный username или ссылка не на профиль — 422. Ошибки возвращаются в data и содержат message, error_code (и retry_after для 429): 403 instagram_not_available — тип недоступен вашей компании; 429 resolve_rate_limited с data.retry_after (секунды) и заголовком Retry-After — слишком много проверок аккаунтов; 422 instagram_account_not_found — такого аккаунта в Instagram нет; 422 instagram_resolve_unavailable — аккаунт не удалось проверить сейчас, повторите позже. Числовой id и уже известный аккаунт не тратят лимит проверок.
Окно ответа Instagram и Messenger: meta_window_state
GET /conversations/{id} возвращает поле meta_window_state, только если в диалоге есть личный (не групповой) тред Instagram (Meta API) или Messenger. Объект: { "is_open": true, "expires_at": 1790000000, "human_agent_open": true, "human_agent_expires_at": 1790400000 }. is_open и expires_at (unix-время или null) — стандартное 24-часовое окно, общее для всех отправителей, включая ИИ и автоматизации. human_agent_open и human_agent_expires_at (unix-время; null, пока функция выключена) — окно ручного ответа оператора до 7 дней (см. поле human_agent в группе «Сообщения»). Описание по аналогии с wa_window_state: это подсказка клиенту, можно ли сейчас отправить сообщение; окончательное решение принимает сервер при отправке. Окно у каждого аккаунта своё: если клиент пишет в два Instagram-аккаунта компании, окна у них разные. Поэтому ответ также содержит массив meta_window_states, по записи на каждый такой тред, с теми же полями плюс channel_id и thread_id; пустой массив означает, что Meta-тредов в диалоге нет. meta_window_state равен первой записи массива и оставлен для совместимости. Нет записи для канала или expires_at равен null — клиент в этот аккаунт ещё не писал, и ответить из него нельзя.
Первое письмо контакту: contact_email_id
В POST /conversations/{conversation}/threads вместо contact_account_id можно передать contact_email_id: id адреса из emailes контакта этого диалога (поле id есть в emailes ответа контакта). Ровно одно из двух полей обязательно, вместе они недопустимы (422). Сервер сам создаёт (или находит) для этого адреса контакт-аккаунт типа email и открывает тред на указанном канале. Условия: channel_id это канал типа email (иначе 422), адрес принадлежит контакту именно этого диалога и вашей компании (чужой или несуществующий id даёт тот же 422), адрес корректен, и он ещё не принадлежит другому контакту. Если тред с этим адресом на этом канале уже есть, ответ содержит его с message: "Thread already exists.". Первое письмо в созданный тред отправляется через POST /messages/send с обязательным metadata.email.subject (см. группу «Сообщения»).
Эндпоинты
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/conversations/countersСчётчики диалогов по статусам (all/awaiting/unread/assigned/unassigned/my_team/archived/groups/aisar_notifications); aisar_notifications = число непрочитанных platform_notifications компании. Фильтры: search (с 2026-09-29 учитывается, как в списке), channel_id, funnel_id, funnel_stage_ids, team_id, unread_only. | Счётчики диалогов по статусам (all/awaiting/unread/assigned/unassigned/my_team/archived/groups/aisar_notifications); aisar_notifications = число непрочитанных platform_notifications компании. Фильтры: search (с 2026-09-29 учитывается, как в списке), channel_id, funnel_id, funnel_stage_ids, team_id, unread_only. |
| GET | /v1/conversationsСписок диалогов компании с пагинацией и фильтрами. | Список диалогов компании с пагинацией и фильтрами. |
| POST | /v1/conversationsСоздать новый диалог для контакта. | Создать новый диалог для контакта. |
| GET | /v1/conversations/{conversation}Получить диалог по id (с контактом, тредами, участниками). | Получить диалог по id (с контактом, тредами, участниками). |
| PATCH | /v1/conversations/{conversation}Обновить поля диалога (тема, статус жизненного цикла, архивация). | Обновить поля диалога (тема, статус жизненного цикла, архивация). |
| DELETE | /v1/conversations/{conversation}Удалить диалог (мягкое удаление). | Удалить диалог (мягкое удаление). |
| POST | /v1/conversations/{conversation}/closeЗакрыть диалог с обязательным комментарием. | Закрыть диалог с обязательным комментарием. |
| POST | /v1/conversations/{conversation}/openОткрыть (переоткрыть) закрытый диалог. | Открыть (переоткрыть) закрытый диалог. |
| POST | /v1/conversations/{conversation}/archiveАрхивировать диалог. | Архивировать диалог. |
| POST | /v1/conversations/{conversation}/unarchiveРазархивировать диалог. | Разархивировать диалог. |
| PUT | /v1/conversations/{conversation}/pinЗакрепить диалог для текущего пользователя (личный пин, не больше 5 диалогов). 422 `code: pin_limit_reached` при превышении лимита и 422 для архивного диалога. | Закрепить диалог для текущего пользователя (личный пин, не больше 5 диалогов). 422 `code: pin_limit_reached` при превышении лимита и 422 для архивного диалога. |
| DELETE | /v1/conversations/{conversation}/pinСнять собственный пин с диалога; 404, если пина у текущего пользователя нет. | Снять собственный пин с диалога; 404, если пина у текущего пользователя нет. |
| POST | /v1/conversations/{conversation}/dismiss-awaitingПометить диалог как «не требует ответа» — обнуляет awaiting_since до следующего входящего сообщения. | Пометить диалог как «не требует ответа» — обнуляет awaiting_since до следующего входящего сообщения. |
| GET | /v1/conversations/{conversation}/participantsСписок участников диалога (операторы, команды, боты). | Список участников диалога (операторы, команды, боты). |
| POST | /v1/conversations/{conversation}/participantsДобавить участника — например назначить оператора ответственным (is_primary). | Добавить участника — например назначить оператора ответственным (is_primary). |
| DELETE | /v1/conversations/{conversation}/participants/{participant}Удалить участника из диалога (снять назначение). | Удалить участника из диалога (снять назначение). |
| GET | /v1/conversations/{conversation}/tagsСписок меток диалога. | Список меток диалога. |
| POST | /v1/conversations/{conversation}/tagsДобавить метку к диалогу. | Добавить метку к диалогу. |
| DELETE | /v1/conversations/{conversation}/tags/{tag}Снять метку с диалога. | Снять метку с диалога. |
| GET | /v1/conversations/{conversation}/notesСписок внутренних заметок по диалогу. | Список внутренних заметок по диалогу. |
| POST | /v1/conversations/{conversation}/notesДобавить внутреннюю заметку (не видна контакту). | Добавить внутреннюю заметку (не видна контакту). |
| PATCH | /v1/conversations/{conversation}/notes/{note}Изменить текст заметки. | Изменить текст заметки. |
| DELETE | /v1/conversations/{conversation}/notes/{note}Удалить заметку. | Удалить заметку. |
| GET | /v1/conversations/{conversation}/eventsХронология событий диалога (создание, комментарии, смена статуса, назначения). | Хронология событий диалога (создание, комментарии, смена статуса, назначения). |
| POST | /v1/conversations/{conversation}/eventsЗаписать событие в хронологию диалога (например комментарий оператора). | Записать событие в хронологию диалога (например комментарий оператора). |
| GET | /v1/conversations/{conversation}/attachmentsПостраничная галерея вложений диалога (все треды; фильтры kind=image|video|document|audio по сырому типу и family=media|document по классификации, складываются) для контекст-панели «Файлы и медиа». | Постраничная галерея вложений диалога (все треды; фильтры kind=image|video|document|audio по сырому типу и family=media|document по классификации, складываются) для контекст-панели «Файлы и медиа». |
| GET | /v1/conversations/{conversation}/threadsСписок тредов (переписок по каналам) внутри диалога. | Список тредов (переписок по каналам) внутри диалога. |
| POST | /v1/conversations/{conversation}/threadsСоздать новый тред в существующем диалоге для дополнительного канала. | Создать новый тред в существующем диалоге для дополнительного канала. |
| GET | /v1/conversations/{conversation}/threads/{thread}Получить тред по id. | Получить тред по id. |
| PATCH | /v1/conversations/{conversation}/threads/{thread}Обновить тред (last_message_at, last_message_id, комментарий события); channel_id, contact_account_id, is_group и external_thread_id неизменяемы. | Обновить тред (last_message_at, last_message_id, комментарий события); channel_id, contact_account_id, is_group и external_thread_id неизменяемы. |
| GET | /v1/conversations/{conversation}/threads/{thread}/eventsХронология событий конкретного треда. | Хронология событий конкретного треда. |
| POST | /v1/conversations/{conversation}/threads/{thread}/eventsЗаписать событие в хронологию треда. | Записать событие в хронологию треда. |
| GET | /v1/threadsКросс-диалоговый список тредов (режим отображения «по каналам») с фильтрами, пагинацией и счётчиками. | Кросс-диалоговый список тредов (режим отображения «по каналам») с фильтрами, пагинацией и счётчиками. |
| POST | /v1/threads/{thread}/closeЗакрыть отдельный тред (независимо от диалога). | Закрыть отдельный тред (независимо от диалога). |
| POST | /v1/threads/{thread}/openОткрыть (переоткрыть) отдельный тред; 422, если у контакта уже есть активный диалог. | Открыть (переоткрыть) отдельный тред; 422, если у контакта уже есть активный диалог. |
| POST | /v1/threads/{thread}/archiveАрхивировать отдельный тред. | Архивировать отдельный тред. |
| POST | /v1/threads/{thread}/unarchiveРазархивировать отдельный тред. | Разархивировать отдельный тред. |
| PUT | /v1/threads/{thread}/pinЗакрепить тред для текущего пользователя (личный пин, не больше 5 тредов). 422 `code: pin_limit_reached` при превышении лимита и 422 для архивного треда. | Закрепить тред для текущего пользователя (личный пин, не больше 5 тредов). 422 `code: pin_limit_reached` при превышении лимита и 422 для архивного треда. |
| DELETE | /v1/threads/{thread}/pinСнять собственный пин с треда; 404, если пина у текущего пользователя нет. | Снять собственный пин с треда; 404, если пина у текущего пользователя нет. |
| GET | /v1/conversations/{conversation}/readsRead-статусы диалога (кто и до какого момента прочитал); фильтры thread_id/user_id. | Read-статусы диалога (кто и до какого момента прочитал); фильтры thread_id/user_id. |
| POST | /v1/conversations/{conversation}/readsВыставить позицию прочтения текущего пользователя (last_read_message_id / last_read_at). | Выставить позицию прочтения текущего пользователя (last_read_message_id / last_read_at). |
| POST | /v1/conversations/mark-readПометить все (или указанные) диалоги компании прочитанными. | Пометить все (или указанные) диалоги компании прочитанными. |
| POST | /v1/conversations/{conversation}/mark-readПометить конкретный диалог (или тред в нём) прочитанным. | Пометить конкретный диалог (или тред в нём) прочитанным. |
| POST | /v1/conversations/{conversation}/mark-unreadОтметить диалог (или его тред) непрочитанным — отматывает курсор к последнему входящему сообщению. | Отметить диалог (или его тред) непрочитанным — отматывает курсор к последнему входящему сообщению. |
| POST | /v1/conversations/{conversation}/ai-suggestions/generateСгенерировать черновик ответа ИИ-копилота для диалога. | Сгенерировать черновик ответа ИИ-копилота для диалога. |
| POST | /v1/conversations/{conversation}/ai-suggestions/{suggestion}/acceptПринять подсказку (опционально с отредактированным final_text). | Принять подсказку (опционально с отредактированным final_text). |
| POST | /v1/conversations/{conversation}/ai-suggestions/{suggestion}/rejectОтклонить подсказку копилота. | Отклонить подсказку копилота. |
| GET | /v1/conversations/{conversation}/ai-pending-actionsОчередь действий ИИ-агента по диалогу, ожидающих подтверждения оператора. | Очередь действий ИИ-агента по диалогу, ожидающих подтверждения оператора. |
| POST | /v1/conversations/{conversation}/ai-tools/summaryСгенерировать краткое ИИ-резюме диалога (для передачи смены). | Сгенерировать краткое ИИ-резюме диалога (для передачи смены). |
| GET | /v1/conversations/{conversation}/ai-tools/summaryСохранённое ИИ-резюме диалога (`summary: null`, если его нет). | Сохранённое ИИ-резюме диалога (`summary: null`, если его нет). |
| GET | /v1/group-chats/{groupChat}/participantsКарточка группового чата и постраничный список участников с ролями. | Карточка группового чата и постраничный список участников с ролями. |
| POST | /v1/group-chats/{groupChat}/refreshПерезапросить метаданные и состав группового чата из мессенджер-рантайма. | Перезапросить метаданные и состав группового чата из мессенджер-рантайма. |
| POST | /v1/dialogsСоздать диалог по идентификатору аккаунта: contact → conversation → thread за один вызов. | Создать диалог по идентификатору аккаунта: contact → conversation → thread за один вызов. |
| GET | /v1/dialogs/lookupНайти существующий активный диалог по каналу и аккаунту (channel_id, account). | Найти существующий активный диалог по каналу и аккаунту (channel_id, account). |
Примеры
Список неназначенных диалогов
Запрос
curl -X GET "https://api.aisar.app/v1/conversations?thread_filter=unassigned&perPage=20" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 15,
"company_id": 1,
"contact_id": 45,
"subject": null,
"lifecycle_status": "active",
"is_closed": false,
"is_archived": false,
"last_message_id": 1001,
"last_message_at": "2026-02-09T18:00:00Z",
"last_message": { "id": 1001, "message_content": { "text": "Здравствуйте, подскажите по заказу" }, "direction": "inbound" },
"unread_count_for_current_user": 1,
"awaiting_since": "2026-02-09T18:00:00Z",
"closed_at": null,
"archived_at": null,
"created_at": "2026-02-09T17:00:00Z",
"updated_at": "2026-02-09T18:00:00Z",
"contact": { "id": 45, "displayName": "John Doe", "phones": [{ "phone": "+12025550187", "type": "mobile" }] },
"threads": [{ "id": 33, "channel_id": 3, "is_group": false }]
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1, "has_more": false },
"counters": { "all": 120, "awaiting": 21, "unread": 14, "assigned": 47, "unassigned": 73, "my_team": 32, "archived": 9, "groups": 6, "aisar_notifications": 3 }
}
}Фильтрация и сортировка списка
Запрос
curl -X GET "https://api.aisar.app/v1/conversations?funnel_id=1&funnel_stage_ids=3,4&unread_only=1&sortBy=last_message_at&sortDir=desc&perPage=20&include_counters=1" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 22,
"company_id": 1,
"contact_id": 51,
"lifecycle_status": "active",
"is_archived": false,
"last_message_id": 1104,
"last_message_at": "2026-02-09T19:40:00Z",
"unread_count_for_current_user": 2,
"awaiting_since": null,
"contact": { "id": 51, "displayName": "Jane Roe", "phones": [{ "phone": "+12025550168", "type": "mobile" }] },
"threads": [{ "id": 40, "channel_id": 3, "is_group": false }]
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1, "has_more": false },
"counters": { "all": 120, "awaiting": 21, "unread": 14, "assigned": 47, "unassigned": 73, "my_team": 32, "archived": 9, "groups": 6, "aisar_notifications": 3 }
}
}Поиск диалогов без точного total (`with_total=0`)
Запрос
curl -X GET "https://api.aisar.app/v1/conversations?perPage=20&with_total=0" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 22,
"company_id": 1,
"contact_id": 51,
"lifecycle_status": "active",
"is_archived": false,
"last_message_id": 1104,
"last_message_at": "2026-02-09T19:40:00Z",
"unread_count_for_current_user": 2,
"awaiting_since": null,
"contact": { "id": 51, "displayName": "Jane Roe", "phones": [{ "phone": "+12025550168", "type": "mobile" }] },
"threads": [{ "id": 40, "channel_id": 3, "is_group": false }]
}
],
"pagination": { "page": 1, "perPage": 20, "total": null, "lastPage": null, "has_more": true }
}
}Кросс-диалоговый список тредов
Запрос
curl -X GET "https://api.aisar.app/v1/threads?thread_filter=unread&sortBy=last_message_at&sortDir=desc&perPage=20" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 33,
"conversation_id": 15,
"company_id": 1,
"channel_id": 3,
"contact_account_id": 78,
"is_group": false,
"external_thread_id": null,
"lifecycle_status": "active",
"is_closed": false,
"is_archived": false,
"closed_at": null,
"archived_at": null,
"last_message_id": 1001,
"last_message_at": "2026-02-09T18:00:00Z",
"unread_count_for_current_user": 1,
"contact_id": 45,
"created_at": "2026-02-09T17:00:00Z",
"updated_at": "2026-02-09T18:00:00Z"
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 },
"counters": { "all": 84, "awaiting": 12, "unread": 6, "assigned": 30, "unassigned": 54, "my_team": 18, "archived": 4, "groups": 5 }
}
}Закрытие отдельного треда
Запрос
curl -X POST "https://api.aisar.app/v1/threads/33/close" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"comment": "Вопрос по этому каналу закрыт."
}'Ответ
{
"data": {
"message": "Thread closed.",
"thread": {
"id": 33,
"conversation_id": 15,
"channel_id": 3,
"lifecycle_status": "closed",
"is_closed": true,
"is_archived": false,
"closed_at": "2026-02-09T19:00:00Z"
}
}
}Read-статусы диалога
Запрос
curl -X GET "https://api.aisar.app/v1/conversations/15/reads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 8,
"company_id": 1,
"conversation_id": 15,
"thread_id": 33,
"user_id": 5,
"last_read_message_id": 1001,
"first_read_at": "2026-02-09T18:02:00Z",
"last_read_at": "2026-02-09T18:20:00Z",
"created_at": "2026-02-09T18:02:00Z",
"updated_at": "2026-02-09T18:20:00Z",
"user": { "id": 5, "name": "Alex", "lastname": "Ivanov", "email": "alex@example.com" }
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 }
}
}Выставить позицию прочтения
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/reads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"thread_id": 33,
"last_read_message_id": 1001
}'Ответ
{
"data": {
"message": "Conversation read state updated.",
"item": {
"id": 8,
"company_id": 1,
"conversation_id": 15,
"thread_id": 33,
"user_id": 5,
"last_read_message_id": 1001,
"first_read_at": "2026-02-09T18:02:00Z",
"last_read_at": "2026-02-09T18:00:00Z",
"created_at": "2026-02-09T18:02:00Z",
"updated_at": "2026-02-09T18:25:00Z"
}
}
}Отметить диалог непрочитанным
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/mark-unread" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"thread_id": 33
}'Ответ
{
"data": {
"message": "Conversation marked as unread.",
"item": {
"id": 8,
"company_id": 1,
"conversation_id": 15,
"thread_id": 33,
"user_id": 5,
"last_read_message_id": 999,
"first_read_at": "2026-02-09T18:02:00Z",
"last_read_at": "2026-02-09T17:59:59Z",
"created_at": "2026-02-09T18:02:00Z",
"updated_at": "2026-02-09T18:35:00Z"
},
"unread_count": 1,
"shared_read_receipts": false
}
}Черновик ответа ИИ-копилота
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/ai-suggestions/generate" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"id": 5821,
"instance_id": 12,
"conversation_id": 15,
"message_id": null,
"suggested_text": "Здравствуйте! Доставка по городу занимает 1-2 дня.",
"status": "pending",
"accepted_by": null,
"final_text": null,
"tokens_used": 184,
"created_at": "2026-02-09T18:30:00Z",
"updated_at": "2026-02-09T18:30:00Z"
}
}Принять подсказку копилота
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/ai-suggestions/5821/accept" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"final_text": "Здравствуйте! Доставка по городу занимает 1-2 дня, курьер согласует время."
}'Ответ
{
"data": {
"id": 5821,
"instance_id": 12,
"conversation_id": 15,
"message_id": null,
"suggested_text": "Здравствуйте! Доставка по городу занимает 1-2 дня.",
"status": "accepted",
"accepted_by": 5,
"final_text": "Здравствуйте! Доставка по городу занимает 1-2 дня, курьер согласует время.",
"tokens_used": 184,
"created_at": "2026-02-09T18:30:00Z",
"updated_at": "2026-02-09T18:31:00Z"
}
}Закрытие диалога
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/close" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"comment": "Вопрос клиента решён, заказ подтверждён."
}'Ответ
{
"data": {
"message": "Conversation closed.",
"conversation": {
"id": 15,
"lifecycle_status": "closed",
"is_closed": true,
"closed_at": "2026-02-09T18:20:00Z"
}
}
}Создание диалога по номеру телефона
Запрос
curl -X POST "https://api.aisar.app/v1/dialogs" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 3,
"account": "+12025550166",
"is_group": false,
"comment": "Первое касание по заявке с сайта"
}'Ответ
{
"data": {
"message": "Dialog created.",
"thread": {
"id": 33,
"conversation_id": 15,
"channel_id": 3,
"contact_account_id": 78,
"is_group": false,
"created_at": "2026-02-09T17:00:00Z"
}
}
}Поиск существующего диалога
Запрос
curl -X GET "https://api.aisar.app/v1/dialogs/lookup?channel_id=3&account=%2B12025550166" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"thread_id": 42,
"conversation_id": 15,
"contact_account_id": 78
}
}Добавление треда в диалог (второй канал)
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/threads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 7,
"contact_account_id": 78,
"funnel_id": 1,
"funnel_stage_id": 3,
"is_group": false,
"external_thread_id": null
}'Ответ
{
"data": {
"id": 34,
"conversation_id": 15,
"channel_id": 7,
"contact_account_id": 78,
"funnel_id": 1,
"funnel_stage_id": 3,
"is_group": false,
"external_thread_id": null,
"created_at": "2026-02-09T18:30:00Z"
}
}Назначение оператора участником
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/participants" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"member_type": "user",
"member_id": 5,
"role": "assignee",
"is_primary": true
}'Ответ
{
"data": {
"id": 61,
"conversation_id": 15,
"member_type": "user",
"member_id": 5,
"role": "assignee",
"is_primary": true,
"created_at": "2026-02-09T18:05:00Z"
}
}Запись события в хронологию диалога
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/events" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "comment",
"comment": "Клиент просит перезвонить после 18:00",
"thread_id": 33
}'Ответ
{
"data": {
"id": 900,
"conversation_id": 15,
"thread_id": 33,
"type": "comment",
"comment": "Клиент просит перезвонить после 18:00",
"created_at": "2026-02-09T18:10:00Z"
}
}Добавление внутренней заметки
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/notes" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body": "Постоянный клиент, дать скидку 10%",
"is_private": true,
"thread_id": null
}'Ответ
{
"data": {
"id": 77,
"conversation_id": 15,
"thread_id": null,
"body": "Постоянный клиент, дать скидку 10%",
"is_private": true,
"pinned_at": null,
"created_at": "2026-02-09T18:12:00Z"
}
}Добавление метки к диалогу
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/tags" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "vip",
"color": "#e11d48",
"source": "manual"
}'Ответ
{
"data": {
"id": 12,
"conversation_id": 15,
"name": "vip",
"color": "#e11d48",
"source": "manual",
"created_at": "2026-02-09T18:15:00Z"
}
}Массовая отметка диалогов прочитанными (202 — ставится в очередь)
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/mark-read" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"conversation_ids": [15, 16, 22]
}'Ответ
{
"data": {
"message": "Conversations marked as read."
}
}Действия ИИ-агента, ожидающие подтверждения
Запрос
curl -X GET "https://api.aisar.app/v1/conversations/15/ai-pending-actions" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 4120,
"conversation_id": 15,
"tool_key": "close_conversation",
"comment": "AI agent proposed to close the conversation (reason: customer confirmed the order)",
"payload": {
"tool_key": "close_conversation",
"ai_agent_instance_id": 12,
"reason": "customer confirmed the order"
},
"approval_status": "pending",
"resolved_at": null,
"approved_by_user_id": null,
"rejected_by_user_id": null,
"rejected_reason": null,
"execution_result": null,
"created_at": "2026-02-09T18:40:00.000000Z",
"conversation": null
}
]
}
}ИИ-резюме диалога для передачи смены
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/ai-tools/summary" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"locale": "ru"
}'Ответ
{
"data": {
"summary_text": "- Клиент спрашивал про сроки доставки по городу.\n- Оператор ответил, что доставка занимает 1–2 дня, курьер согласует время.\n- Статус: заказ подтверждён, клиент ожидает звонка курьера.",
"message_count": 14,
"tokens_used": 512,
"generated_at": "2026-10-07T09:30:00+00:00",
"saved": true
}
}Сохранённое резюме диалога
Запрос
curl -X GET "https://api.aisar.app/v1/conversations/15/ai-tools/summary" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"summary": {
"summary_text": "- Клиент спрашивал про сроки доставки по городу.\n- Статус: заказ подтверждён.",
"message_count": 14,
"locale": "ru",
"generated_at": "2026-10-07T09:30:00+00:00",
"generated_by": { "id": 7, "name": "Айгерим К." }
}
}
}Диалог пуст — резюме невозможно (422)
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/ai-tools/summary" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Ответ
{
"data": {
"message": "Conversation has no messages to summarise",
"code": "empty_conversation"
}
}Участники группового чата
Запрос
curl -X GET "https://api.aisar.app/v1/group-chats/8/participants?page=1&per_page=50" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"group_chat": {
"id": 8,
"external_group_id": "120363000000000001@g.us",
"subject": "Отдел продаж — клиенты",
"description": "Рабочая группа по заявкам",
"photo": null,
"announce": false,
"restrict": false,
"size": 3,
"updated_at": "2026-02-09T18:00:00.000000Z"
},
"participants": [
{
"id": 51,
"role": "superadmin",
"display_name": "Alex Ivanov",
"photo": null,
"phone": "12025550166",
"contact_account_id": 78,
"external_id": "12025550166"
},
{
"id": 52,
"role": "member",
"display_name": "Jane Roe",
"photo": null,
"phone": "12025550168",
"contact_account_id": 79,
"external_id": "12025550168"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 3, "last_page": 1 }
}
}Обновление состава группового чата из рантайма
Запрос
curl -X POST "https://api.aisar.app/v1/group-chats/8/refresh" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"group_chat": {
"id": 8,
"external_group_id": "120363000000000001@g.us",
"subject": "Отдел продаж — клиенты",
"description": "Рабочая группа по заявкам",
"photo": null,
"announce": false,
"restrict": false,
"size": 4,
"updated_at": "2026-02-09T18:45:00.000000Z"
},
"participants": [],
"pagination": { "page": 1, "per_page": 50, "total": 4, "last_page": 1 }
}
}Обновление невозможно — у канала нет рантайм-инстанса (503)
Запрос
curl -X POST "https://api.aisar.app/v1/group-chats/8/refresh" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"message": "Channel has no runtime instance"
}Пометить диалог «не требует ответа»
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/dismiss-awaiting" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"id": 15,
"awaiting_since": null
}
}/v1/dialogs недоступен семейной компании (403)
Запрос
curl -X POST "https://api.aisar.app/v1/dialogs" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "channel_id": 3, "account": "+12025550187" }'Ответ
{
"data": {
"message": "This area is not available for your account type.",
"error": "company_type_forbidden"
}
}Email: тред для первого письма (contact_email_id)
Запрос
curl -X POST "https://api.aisar.app/v1/conversations/15/threads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "channel_id": 41, "contact_email_id": 12 }'Ответ
{
"data": {
"id": 77,
"conversation_id": 15,
"channel_id": 41,
"contact_account_id": 95,
"is_group": false
}
}Закрепить диалог
Запрос
curl -X PUT "https://api.aisar.app/v1/conversations/15/pin" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"conversation_id": 15,
"thread_id": null,
"pinned_at": "2026-10-24T09:30:00.000000Z"
}
}Закрепить тред, лимит и снятие пина
Запрос
# В режиме «по каналам» закрепляется тред: PUT /v1/threads/{thread}/pin
# Шестой пин подряд (лимит 5 на пользователя и вид списка):
curl -X PUT "https://api.aisar.app/v1/threads/77/pin" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
// 422: лимит исчерпан
{
"data": {
"message": "Pin limit reached: at most 5 pinned threads.",
"code": "pin_limit_reached"
}
}
// DELETE /v1/threads/77/pin — 200, пина больше нет
{
"data": {
"conversation_id": 15,
"thread_id": 77,
"pinned_at": null
}
}