Conversations
A conversation is the umbrella record for all communication with a single contact, grouping one or more channel-specific threads (WhatsApp, Telegram, Instagram, etc). These endpoints manage a conversation's lifecycle — opening and closing it, assigning participants (operators) via the participants sub-resource, keeping internal notes and tags, and reading its unified event timeline and thread structure. Most mutating actions append to the conversation's event log, readable via GET /conversations/{id}/events.
Creating a dialog
A dialog can be created two ways: POST /conversations for an already-known contact, or POST /dialogs — the integrator entry point that, given an account identifier (a phone or username), creates or reuses the whole contact → contact_account → conversation → thread chain in a single call; for phone-based channels (sms, whatsapp) the account is normalized as a phone number. GET /dialogs/lookup finds an existing active dialog, taking related channel types into account (whatsapp/whatsapp_business/sms, telegram/telegram_bot, instagram/instagram_business).
Listing and filters
The GET /conversations list uses page-based pagination (page/perPage; the response carries a pagination block {page, perPage, total, lastPage}) and supports the thread_filter (all|awaiting|unread|assigned|unassigned|my_team|archived|groups), channel_id, funnel_id, funnel_stage_ids (comma-separated funnel-stage ids), contact_id, lifecycle_status (active|closed), is_archived, unread_only and search filters, sortBy (id|last_message_at|created_at|updated_at) / sortDir (asc|desc) sorting, and include_counters=1 (adds a counters block to the response).
The `aisar_notifications` counter key
The counters block (in the conversation list with include_counters=1, and in GET /conversations/counters) carries an aisar_notifications key — always present, including the empty-response case for a company with no active subscription. Semantics change (2026-08-24, shipped without a new version prefix): before 08-24 this key counted AISAR notification conversations among the conversations themselves (see the history below); platform notifications are now a separate resource, GET /platform-notifications, and counters.aisar_notifications is the count of UNSEEN platform_notifications rows for the company: rows from the last 90 days with created_at later than the current user's ui_preferences.aisar_notifications.seen_until cursor (a null cursor counts every row in the window). The value's shape (a plain integer) is unchanged — only the data source changed, and it is now computed per-user rather than per-company.
Awaiting a reply (`awaiting_since`) and "dismiss awaiting"
ConversationResource (in both the GET /conversations list and GET /conversations/{id}) carries a computed awaiting_since field (ISO-8601 string or null) — the moment of the first unanswered inbound message; null if the conversation is not currently awaiting a reply. The semantics match thread_filter=awaiting. The value is computed by scanning the conversation's history backward (at most the last 100 messages); for a conversation with an exceptionally long unanswered tail this is an approximation — the oldest inbound message found within that window, not necessarily the first ever.
POST /conversations/{conversation}/dismiss-awaiting (2026-08-24, requires conversation.update) lets an operator mark a conversation as "no reply needed": the server sets the internal awaiting_dismissed_at field, and from that point awaiting_since on the conversation stays null until the next inbound message arrives (which re-arms awaiting_since as usual). Response: { "data": { "id": <conversation_id>, "awaiting_since": null } }.
Threads as a standalone entity
A thread can also be listed as a standalone entity: GET /threads returns a cross-conversation thread list (the "by channel" display mode) with the same filters, pagination and counters, and POST /threads/{id}/close|open|archive|unarchive changes a single thread's state independently of its conversation (reopening returns 422 if another active conversation already exists for the contact).
Read state
Read state is exposed by GET /conversations/{id}/reads — which operator has read the conversation up to which message (per-user last_read_message_id/last_read_at) — while POST /conversations/{id}/reads sets the current user's read position. On POST /conversations/mark-read the conversation_ids field is optional (omit it to mark every company conversation), and a conversation's read state is also updated automatically when an operator sends an outbound message. Because a bulk mark-read can take tens of seconds for a large company, POST /conversations/mark-read queues the upsert and the CRM badge fan-out and responds 202 Accepted with no updated_count in the body — the real count of updated conversations only arrives asynchronously, via the message.read WebSocket event with bulk: true and updated_count. A call with an empty conversation_ids: [] array is not queued and still returns a plain 200.
POST /conversations/{conversation}/mark-unread is the reverse operation: it rewinds the read cursor to one second before the conversation's (or its thread's, with the optional thread_id) latest inbound message, so that message becomes unread again. Scope follows the company's shared_read_receipts setting: when receipts are shared, EVERY operator's cursor that was past the new mark gets rewound; when private, only the current user's cursor does. If the conversation (or thread) has no inbound messages at all — 422 { "data": { "message": "Conversation has no inbound messages to mark as unread." } }. A successful response is { "data": { message, item, unread_count, shared_read_receipts } } — item has the same shape as in reads (can be null), unread_count is how many inbound messages are now unread for the current user, and shared_read_receipts echoes the company setting.
AI copilot and AI-agent actions
AI copilot: POST /conversations/{id}/ai-suggestions/generate produces a draft reply for the conversation, and .../ai-suggestions/{id}/accept (with an optional edited final_text) and .../reject record the operator's decision on a suggestion.
.../ai-suggestions/generate is not gated by the ai_agents feature at the route level — that check happens inside generation itself, together with the plan's AI-reply allowance, and distinguishes several failure modes: no agent assigned to the conversation — 404 (no code in the body); the agent is inactive — 422 { "code": "agent_inactive" }; the agent's own configurable $ budget is exhausted — 422 { "code": "budget_exceeded" }; the company plan's AI-reply allowance for the billing period is used up (ai_replies_included) — including when the company's current plan no longer carries ai_agents at all (e.g. after a downgrade) — 422 { "code": "plan_limit_exceeded" }; usage has reached the technical anti-abuse threshold (a multiple of the plan allowance) — 422 { "code": "usage_abuse_stopped" }; too many calls in a short window — 429 (no code in the body).
An AI agent can also propose actions that require operator approval (for example closing a conversation, blocking a contact, or deleting messages) — the queue of such proposals for a specific conversation is returned by GET /conversations/{id}/ai-pending-actions: each item carries a tool_key, a human-readable comment, a free-form payload, and an approval_status (pending|approved|rejected); the decision itself is recorded via the company-scoped approve/reject routes.
POST /conversations/{id}/ai-tools/summary returns a short AI summary of the conversation for a shift handoff (summary_text, message_count, tokens_used); it requires the ai_agents feature at the route level (without it — 403 feature_not_available), accepts an optional locale (otherwise the app locale is used), is subject to a 60-requests-per-hour AI-assist limit per company (429 ai_assist_rate_limited), returns 422 empty_conversation for a conversation with no messages, and 502 ai_provider_error on a provider failure.
Group chats
Group chats (WhatsApp groups, etc.) are served by their own routes: GET /group-chats/{id}/participants returns the group card and a paginated (page/per_page, default 50) list of participants with roles (superadmin|admin|member, sorted by seniority), while POST /group-chats/{id}/refresh force-refetches the group metadata from the messaging runtime (503 if the channel has no runtime instance or the runtime rejected the request) and returns the refreshed roster.
Enum values
Enum values used in request bodies:
- Participants are added with a
member_type(user|team|bot). - Events with a
type(created|comment|status_changed|archived|unarchived|assigned|unassigned). - Tags with a
source(manual|automation|integration).
Files and media
GET /conversations/{conversation}/attachments returns a paginated attachment gallery for the context panel (across all of the conversation's threads, scoped to the channels the user can access); the optional kind filters by image|video|document|audio. Each row is {attachment_id, message_id, kind, mime_type, size, name, thumb_url, created_at}; thumb_url is only minted for image/video (null for other kind values) as a short-lived (20-minute) signed link to the same .../media route with w=640 (a webp preview) — unlike MessageAttachmentResource.url's permanent signature, the lifetime is deliberately short so the gallery page can't be used to bulk-export forever-valid media links.
Endpoints
| Method | Path | Summary |
|---|---|---|
| GET | /v1/conversations/countersConversation counters by status (all/awaiting/unread/assigned/unassigned/my_team/archived/groups/aisar_notifications); aisar_notifications = count of the company's unseen platform_notifications rows. | Conversation counters by status (all/awaiting/unread/assigned/unassigned/my_team/archived/groups/aisar_notifications); aisar_notifications = count of the company's unseen platform_notifications rows. |
| GET | /v1/conversationsList the company's conversations with pagination and filters. | List the company's conversations with pagination and filters. |
| POST | /v1/conversationsCreate a new conversation for a contact. | Create a new conversation for a contact. |
| GET | /v1/conversations/{conversation}Get a conversation by id (with contact, threads, participants). | Get a conversation by id (with contact, threads, participants). |
| PATCH | /v1/conversations/{conversation}Update conversation fields (subject, lifecycle status, archival). | Update conversation fields (subject, lifecycle status, archival). |
| DELETE | /v1/conversations/{conversation}Delete a conversation (soft delete). | Delete a conversation (soft delete). |
| POST | /v1/conversations/{conversation}/closeClose a conversation with a required comment. | Close a conversation with a required comment. |
| POST | /v1/conversations/{conversation}/openOpen (reopen) a closed conversation. | Open (reopen) a closed conversation. |
| POST | /v1/conversations/{conversation}/archiveArchive a conversation. | Archive a conversation. |
| POST | /v1/conversations/{conversation}/unarchiveUnarchive a conversation. | Unarchive a conversation. |
| POST | /v1/conversations/{conversation}/dismiss-awaitingMark a conversation as "no reply needed" — clears awaiting_since until the next inbound message. | Mark a conversation as "no reply needed" — clears awaiting_since until the next inbound message. |
| GET | /v1/conversations/{conversation}/participantsList conversation participants (operators, teams, bots). | List conversation participants (operators, teams, bots). |
| POST | /v1/conversations/{conversation}/participantsAdd a participant — e.g. assign an operator as the primary owner (is_primary). | Add a participant — e.g. assign an operator as the primary owner (is_primary). |
| DELETE | /v1/conversations/{conversation}/participants/{participant}Remove a participant from the conversation (unassign). | Remove a participant from the conversation (unassign). |
| GET | /v1/conversations/{conversation}/tagsList conversation tags. | List conversation tags. |
| POST | /v1/conversations/{conversation}/tagsAdd a tag to the conversation. | Add a tag to the conversation. |
| DELETE | /v1/conversations/{conversation}/tags/{tag}Remove a tag from the conversation. | Remove a tag from the conversation. |
| GET | /v1/conversations/{conversation}/notesList internal notes on the conversation. | List internal notes on the conversation. |
| POST | /v1/conversations/{conversation}/notesAdd an internal note (not visible to the contact). | Add an internal note (not visible to the contact). |
| PATCH | /v1/conversations/{conversation}/notes/{note}Edit a note's body. | Edit a note's body. |
| DELETE | /v1/conversations/{conversation}/notes/{note}Delete a note. | Delete a note. |
| GET | /v1/conversations/{conversation}/eventsConversation event timeline (created, comments, status changes, assignments). | Conversation event timeline (created, comments, status changes, assignments). |
| POST | /v1/conversations/{conversation}/eventsLog an event on the conversation timeline (e.g. an operator comment). | Log an event on the conversation timeline (e.g. an operator comment). |
| GET | /v1/conversations/{conversation}/attachmentsPaginated attachment gallery for the conversation (all threads; kind=image|video|document|audio filter) for the "Files & media" context panel. | Paginated attachment gallery for the conversation (all threads; kind=image|video|document|audio filter) for the "Files & media" context panel. |
| GET | /v1/conversations/{conversation}/threadsList threads (per-channel conversations) inside the conversation. | List threads (per-channel conversations) inside the conversation. |
| POST | /v1/conversations/{conversation}/threadsCreate a new thread inside an existing conversation for an additional channel. | Create a new thread inside an existing conversation for an additional channel. |
| GET | /v1/conversations/{conversation}/threads/{thread}Get a thread by id. | Get a thread by id. |
| PATCH | /v1/conversations/{conversation}/threads/{thread}Update a thread (funnel/stage, external_thread_id, etc). | Update a thread (funnel/stage, external_thread_id, etc). |
| GET | /v1/conversations/{conversation}/threads/{thread}/eventsEvent timeline of a specific thread. | Event timeline of a specific thread. |
| POST | /v1/conversations/{conversation}/threads/{thread}/eventsLog an event on a specific thread timeline. | Log an event on a specific thread timeline. |
| GET | /v1/threadsCross-conversation thread list (the "by channel" display mode) with filters, pagination and counters. | Cross-conversation thread list (the "by channel" display mode) with filters, pagination and counters. |
| POST | /v1/threads/{thread}/closeClose a single thread (independently of its conversation). | Close a single thread (independently of its conversation). |
| POST | /v1/threads/{thread}/openOpen (reopen) a single thread; 422 if the contact already has an active conversation. | Open (reopen) a single thread; 422 if the contact already has an active conversation. |
| POST | /v1/threads/{thread}/archiveArchive a single thread. | Archive a single thread. |
| POST | /v1/threads/{thread}/unarchiveUnarchive a single thread. | Unarchive a single thread. |
| GET | /v1/conversations/{conversation}/readsConversation read statuses (who has read up to which point); thread_id/user_id filters. | Conversation read statuses (who has read up to which point); thread_id/user_id filters. |
| POST | /v1/conversations/{conversation}/readsSet the current user's read position (last_read_message_id / last_read_at). | Set the current user's read position (last_read_message_id / last_read_at). |
| POST | /v1/conversations/mark-readMark all (or the given) company conversations as read. | Mark all (or the given) company conversations as read. |
| POST | /v1/conversations/{conversation}/mark-readMark a specific conversation (or one of its threads) as read. | Mark a specific conversation (or one of its threads) as read. |
| POST | /v1/conversations/{conversation}/mark-unreadMark a conversation (or one of its threads) as unread — rewinds the cursor to the latest inbound message. | Mark a conversation (or one of its threads) as unread — rewinds the cursor to the latest inbound message. |
| POST | /v1/conversations/{conversation}/ai-suggestions/generateGenerate an AI copilot draft reply for the conversation. | Generate an AI copilot draft reply for the conversation. |
| POST | /v1/conversations/{conversation}/ai-suggestions/{suggestion}/acceptAccept a suggestion (optionally with an edited final_text). | Accept a suggestion (optionally with an edited final_text). |
| POST | /v1/conversations/{conversation}/ai-suggestions/{suggestion}/rejectReject a copilot suggestion. | Reject a copilot suggestion. |
| GET | /v1/conversations/{conversation}/ai-pending-actionsQueue of AI-agent actions on the conversation awaiting operator approval. | Queue of AI-agent actions on the conversation awaiting operator approval. |
| POST | /v1/conversations/{conversation}/ai-tools/summaryGenerate a short AI summary of the conversation (for a shift handoff). | Generate a short AI summary of the conversation (for a shift handoff). |
| GET | /v1/group-chats/{groupChat}/participantsGroup-chat card and a paginated list of participants with roles. | Group-chat card and a paginated list of participants with roles. |
| POST | /v1/group-chats/{groupChat}/refreshRe-fetch group-chat metadata and roster from the messaging runtime. | Re-fetch group-chat metadata and roster from the messaging runtime. |
| POST | /v1/dialogsCreate a dialog from an account identifier: contact → conversation → thread in one call. | Create a dialog from an account identifier: contact → conversation → thread in one call. |
| GET | /v1/dialogs/lookupLook up an existing active dialog by channel and account (channel_id, account). | Look up an existing active dialog by channel and account (channel_id, account). |
Examples
List unassigned conversations
Request
curl -X GET "https://api.aisar.app/v1/conversations?thread_filter=unassigned&perPage=20" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"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": "+77001234567", "type": "mobile" }] },
"threads": [{ "id": 33, "channel_id": 3, "is_group": false }]
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 },
"counters": { "all": 120, "awaiting": 21, "unread": 14, "assigned": 47, "unassigned": 73, "my_team": 32, "archived": 9, "groups": 6, "aisar_notifications": 3 }
}
}Filter and sort the list
Request
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"Response
{
"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": "+77000000456", "type": "mobile" }] },
"threads": [{ "id": 40, "channel_id": 3, "is_group": false }]
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 },
"counters": { "all": 120, "awaiting": 21, "unread": 14, "assigned": 47, "unassigned": 73, "my_team": 32, "archived": 9, "groups": 6, "aisar_notifications": 3 }
}
}Cross-conversation thread list
Request
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"Response
{
"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 }
}
}Close a single thread
Request
curl -X POST "https://api.aisar.app/v1/threads/33/close" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"comment": "Вопрос по этому каналу закрыт."
}'Response
{
"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"
}
}
}Conversation read statuses
Request
curl -X GET "https://api.aisar.app/v1/conversations/15/reads" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"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 }
}
}Set the read position
Request
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
}'Response
{
"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"
}
}
}Mark a conversation as unread
Request
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
}'Response
{
"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
}
}AI copilot draft reply
Request
curl -X POST "https://api.aisar.app/v1/conversations/15/ai-suggestions/generate" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"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"
}
}Accept a copilot suggestion
Request
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 дня, курьер согласует время."
}'Response
{
"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"
}
}Close a conversation
Request
curl -X POST "https://api.aisar.app/v1/conversations/15/close" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"comment": "Вопрос клиента решён, заказ подтверждён."
}'Response
{
"data": {
"message": "Conversation closed.",
"conversation": {
"id": 15,
"lifecycle_status": "closed",
"is_closed": true,
"closed_at": "2026-02-09T18:20:00Z"
}
}
}Create a dialog from a phone number
Request
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": "+77000000123",
"is_group": false,
"comment": "Первое касание по заявке с сайта"
}'Response
{
"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"
}
}
}Look up an existing dialog
Request
curl -X GET "https://api.aisar.app/v1/dialogs/lookup?channel_id=3&account=%2B77000000123" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"data": {
"thread_id": 42,
"conversation_id": 15,
"contact_account_id": 78
}
}Add a thread to a conversation (second channel)
Request
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
}'Response
{
"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"
}
}Assign an operator as a participant
Request
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
}'Response
{
"data": {
"id": 61,
"conversation_id": 15,
"member_type": "user",
"member_id": 5,
"role": "assignee",
"is_primary": true,
"created_at": "2026-02-09T18:05:00Z"
}
}Log an event on the conversation timeline
Request
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
}'Response
{
"data": {
"id": 900,
"conversation_id": 15,
"thread_id": 33,
"type": "comment",
"comment": "Клиент просит перезвонить после 18:00",
"created_at": "2026-02-09T18:10:00Z"
}
}Add an internal note
Request
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
}'Response
{
"data": {
"id": 77,
"conversation_id": 15,
"thread_id": null,
"body": "Постоянный клиент, дать скидку 10%",
"is_private": true,
"pinned_at": null,
"created_at": "2026-02-09T18:12:00Z"
}
}Add a tag to a conversation
Request
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"
}'Response
{
"data": {
"id": 12,
"conversation_id": 15,
"name": "vip",
"color": "#e11d48",
"source": "manual",
"created_at": "2026-02-09T18:15:00Z"
}
}Bulk-mark conversations as read (202 — queued)
Request
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]
}'Response
{
"data": {
"message": "Conversations marked as read."
}
}AI-agent actions awaiting approval
Request
curl -X GET "https://api.aisar.app/v1/conversations/15/ai-pending-actions" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"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
}
]
}
}AI conversation summary for a handoff
Request
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"
}'Response
{
"data": {
"summary_text": "- Клиент спрашивал про сроки доставки по городу.\n- Оператор ответил, что доставка занимает 1–2 дня, курьер согласует время.\n- Статус: заказ подтверждён, клиент ожидает звонка курьера.",
"message_count": 14,
"tokens_used": 512
}
}Empty conversation — summary not possible (422)
Request
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 '{}'Response
{
"data": {
"message": "Conversation has no messages to summarise",
"code": "empty_conversation"
}
}Group-chat participants
Request
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"Response
{
"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": "77000000123",
"contact_account_id": 78,
"external_id": "77000000123"
},
{
"id": 52,
"role": "member",
"display_name": "Jane Roe",
"photo": null,
"phone": "77000000456",
"contact_account_id": 79,
"external_id": "77000000456"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 3, "last_page": 1 }
}
}Refresh group-chat roster from the runtime
Request
curl -X POST "https://api.aisar.app/v1/group-chats/8/refresh" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"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 }
}
}Refresh not possible — channel has no runtime instance (503)
Request
curl -X POST "https://api.aisar.app/v1/group-chats/8/refresh" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"message": "Channel has no runtime instance"
}Mark a conversation as "no reply needed"
Request
curl -X POST "https://api.aisar.app/v1/conversations/15/dismiss-awaiting" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Response
{
"data": {
"id": 15,
"awaiting_since": null
}
}