Skip to content
AISARAISAR
REST API

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

MethodPath
GET/v1/conversations/counters

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/conversations

List the company's conversations with pagination and filters.

POST/v1/conversations

Create a new conversation for a contact.

GET/v1/conversations/{conversation}

Get a conversation by id (with contact, threads, participants).

PATCH/v1/conversations/{conversation}

Update conversation fields (subject, lifecycle status, archival).

DELETE/v1/conversations/{conversation}

Delete a conversation (soft delete).

POST/v1/conversations/{conversation}/close

Close a conversation with a required comment.

POST/v1/conversations/{conversation}/open

Open (reopen) a closed conversation.

POST/v1/conversations/{conversation}/archive

Archive a conversation.

POST/v1/conversations/{conversation}/unarchive

Unarchive a conversation.

POST/v1/conversations/{conversation}/dismiss-awaiting

Mark a conversation as "no reply needed" — clears awaiting_since until the next inbound message.

GET/v1/conversations/{conversation}/participants

List conversation participants (operators, teams, bots).

POST/v1/conversations/{conversation}/participants

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).

GET/v1/conversations/{conversation}/tags

List conversation tags.

POST/v1/conversations/{conversation}/tags

Add a tag to the conversation.

DELETE/v1/conversations/{conversation}/tags/{tag}

Remove a tag from the conversation.

GET/v1/conversations/{conversation}/notes

List internal notes on the conversation.

POST/v1/conversations/{conversation}/notes

Add an internal note (not visible to the contact).

PATCH/v1/conversations/{conversation}/notes/{note}

Edit a note's body.

DELETE/v1/conversations/{conversation}/notes/{note}

Delete a note.

GET/v1/conversations/{conversation}/events

Conversation event timeline (created, comments, status changes, assignments).

POST/v1/conversations/{conversation}/events

Log an event on the conversation timeline (e.g. an operator comment).

GET/v1/conversations/{conversation}/attachments

Paginated attachment gallery for the conversation (all threads; kind=image|video|document|audio filter) for the "Files & media" context panel.

GET/v1/conversations/{conversation}/threads

List threads (per-channel conversations) inside the conversation.

POST/v1/conversations/{conversation}/threads

Create a new thread inside an existing conversation for an additional channel.

GET/v1/conversations/{conversation}/threads/{thread}

Get a thread by id.

PATCH/v1/conversations/{conversation}/threads/{thread}

Update a thread (funnel/stage, external_thread_id, etc).

GET/v1/conversations/{conversation}/threads/{thread}/events

Event timeline of a specific thread.

POST/v1/conversations/{conversation}/threads/{thread}/events

Log an event on a specific thread timeline.

GET/v1/threads

Cross-conversation thread list (the "by channel" display mode) with filters, pagination and counters.

POST/v1/threads/{thread}/close

Close a single thread (independently of its conversation).

POST/v1/threads/{thread}/open

Open (reopen) a single thread; 422 if the contact already has an active conversation.

POST/v1/threads/{thread}/archive

Archive a single thread.

POST/v1/threads/{thread}/unarchive

Unarchive a single thread.

GET/v1/conversations/{conversation}/reads

Conversation read statuses (who has read up to which point); thread_id/user_id filters.

POST/v1/conversations/{conversation}/reads

Set the current user's read position (last_read_message_id / last_read_at).

POST/v1/conversations/mark-read

Mark all (or the given) company conversations as read.

POST/v1/conversations/{conversation}/mark-read

Mark a specific conversation (or one of its threads) as read.

POST/v1/conversations/{conversation}/mark-unread

Mark a conversation (or one of its threads) as unread — rewinds the cursor to the latest inbound message.

POST/v1/conversations/{conversation}/ai-suggestions/generate

Generate an AI copilot draft reply for the conversation.

POST/v1/conversations/{conversation}/ai-suggestions/{suggestion}/accept

Accept a suggestion (optionally with an edited final_text).

POST/v1/conversations/{conversation}/ai-suggestions/{suggestion}/reject

Reject a copilot suggestion.

GET/v1/conversations/{conversation}/ai-pending-actions

Queue of AI-agent actions on the conversation awaiting operator approval.

POST/v1/conversations/{conversation}/ai-tools/summary

Generate a short AI summary of the conversation (for a shift handoff).

GET/v1/group-chats/{groupChat}/participants

Group-chat card and a paginated list of participants with roles.

POST/v1/group-chats/{groupChat}/refresh

Re-fetch group-chat metadata and roster from the messaging runtime.

POST/v1/dialogs

Create a dialog from an account identifier: contact → conversation → thread in one call.

GET/v1/dialogs/lookup

Look up an existing active dialog by channel and account (channel_id, account).

Examples

List unassigned conversations

Request

bash
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

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": "+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

bash
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

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": "+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

bash
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

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 }
  }
}

Close a single thread

Request

bash
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

json
{
  "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

bash
curl -X GET "https://api.aisar.app/v1/conversations/15/reads" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

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 }
  }
}

Set the read position

Request

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
curl -X POST "https://api.aisar.app/v1/conversations/15/ai-suggestions/generate" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

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"
  }
}

Accept a copilot suggestion

Request

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "data": {
    "thread_id": 42,
    "conversation_id": 15,
    "contact_account_id": 78
  }
}

Add a thread to a conversation (second channel)

Request

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "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

bash
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

json
{
  "data": {
    "message": "Conversations marked as read."
  }
}

AI-agent actions awaiting approval

Request

bash
curl -X GET "https://api.aisar.app/v1/conversations/15/ai-pending-actions" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

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
      }
    ]
  }
}

AI conversation summary for a handoff

Request

bash
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

json
{
  "data": {
    "summary_text": "- Клиент спрашивал про сроки доставки по городу.\n- Оператор ответил, что доставка занимает 1–2 дня, курьер согласует время.\n- Статус: заказ подтверждён, клиент ожидает звонка курьера.",
    "message_count": 14,
    "tokens_used": 512
  }
}

Empty conversation — summary not possible (422)

Request

bash
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

json
{
  "data": {
    "message": "Conversation has no messages to summarise",
    "code": "empty_conversation"
  }
}

Group-chat participants

Request

bash
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

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": "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

bash
curl -X POST "https://api.aisar.app/v1/group-chats/8/refresh" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

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 }
  }
}

Refresh not possible — channel has no runtime instance (503)

Request

bash
curl -X POST "https://api.aisar.app/v1/group-chats/8/refresh" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "message": "Channel has no runtime instance"
}

Mark a conversation as "no reply needed"

Request

bash
curl -X POST "https://api.aisar.app/v1/conversations/15/dismiss-awaiting" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "data": {
    "id": 15,
    "awaiting_since": null
  }
}