Conversations API
Multi-channel conversation threads and threading state Base path:/api/v1/conversations
Endpoint count: 38
List all conversations
GET /api/v1/conversations/string
Pagination cursor
integer
—
string
Sort field (prefix with - for DESC)
string (enum: sms|whatsapp|email|rcs|viber|instagram|…)
—
string (enum: open|closed|pending|snoozed|active|archived|…)
—
string
Filter by assigned agent ID (legacy)
string
Search by contact name, phone, or email
string
Filter by tag (legacy, single)
string
Comma-separated list of channels
string
Comma-separated list of statuses (open,closed,pending,snoozed,…)
string
Comma-separated tag list (ANY match)
string
“me”, “unassigned”, or a user id
string
—
string
—
string
—
string (enum: true|false)
—
string (enum: true|false)
—
string (enum: true|false)
—
string (enum: positive|neutral|negative)
—
string (enum: true|false)
—
string
JSON-encoded full filter AST (takes precedence)
Get a conversation
GET /api/v1/conversations/{id}string
required
—
Conversation activity feed
GET /api/v1/conversations/{id}/activitystring
required
—
string
—
integer
—
Read agent_active state for a conversation
GET /api/v1/conversations/{id}/agent/activestring
required
—
Get live cost breakdown for one conversation
GET /api/v1/conversations/{id}/costpublic.llm_cost_events (tenant-scoped). Includes per-model and per-agent breakdown plus a soft-budget alert evaluation against organizations.settings.agent_defaults.soft_cost_per_conversation_usd. Distinct from the hard per-conversation cost cap in apps/agent-runtime’s Redis meter — this is the READ side for the inbox detail panel and never blocks anything. Returns zero totals (never errors) on missing ledger rows, missing soft cap, or DB blip so the FE chip degrades to a hyphen rather than 5xx-ing the panel.
string
required
—
List conversation followers
GET /api/v1/conversations/{id}/followersstring
required
—
Get the full handoff packet for an in-flight AI conversation
GET /api/v1/conversations/{id}/handoff/packetstring
required
—
Get current queue position for an in-flight handoff
GET /api/v1/conversations/{id}/handoff/queue-positionstring
required
—
List linked conversations
GET /api/v1/conversations/{id}/linksparent_of B, then GET /A/links returns parent_of and GET /B/links returns child_of for the same row.
string
required
—
Get conversation messages
GET /api/v1/conversations/{id}/messagesstring
required
—
string
—
integer
—
Per-message sentiment timeline
GET /api/v1/conversations/{id}/sentiment-timeline[{message_id, ts, sentiment_score, sentiment_label}] for every analyzed message (sentiment_score IS NOT NULL). The FE renders this as a sparkline strip at the top of the conversation detail with per-message hover. Unanalyzed messages are skipped; scheduler-sentiment-backfill (5-min cadence in webhook-worker) populates the columns for messages with NULL sentiment_score. Empty array when no messages have been analyzed yet — FE renders an Analyzing… empty state.
string
required
—
List side conversations for a parent
GET /api/v1/conversations/{id}/side-conversationsstring
required
—
Get side-conversation message thread
GET /api/v1/conversations/{id}/side-conversations/{sideId}/messagesstring
required
—
string
required
—
List distinct conversation tags for the tenant
GET /api/v1/conversations/tags/distinctResume AI agent after human-in-the-loop handoff
POST /api/v1/conversations/{id}/agent/resumestring
required
—
Continue a conversation on a different channel
POST /api/v1/conversations/{id}/resume-channeltarget_channel so a customer can continue the SAME thread after switching channel (SMS ↔ email ↔ web-chat ↔ voice ↔ …) without repeating themselves. Repoints the conversation’s primary channel (the default for the next operator or AI-agent reply) and unions the new channel into channels[] so the full cross-channel history keeps loading. Idempotent — already on the target returns 200 with already_on_channel=true. Rejects terminal threads (422, reopen first) and channels the contact has no identity for (400). Sends no outbound message — the next reply still routes via the standard reply path.
string
required
Conversation id.
string (enum: sms|whatsapp|email|rcs|viber|instagram|messenger|line|telegram|voice|apple_messages|web_chat)
required
The channel to continue the conversation on.
string
Optional free-text note (≤280 chars) stamped into the activity trail (e.g. “customer asked to switch to email”).
Assign conversation
POST /api/v1/conversations/{id}/assignstring
required
—
Close conversation
POST /api/v1/conversations/{id}/closestring
required
—
Agent Copilot — suggest replies + next-best-actions
POST /api/v1/conversations/{id}/copilot/suggestlookback (default 25, max 50) controls how many recent messages are fed to the model.
string
required
—
integer
How many recent messages to feed the LLM. Defaults to 25.
Escalate conversation
POST /api/v1/conversations/{id}/escalatestring
required
—
string
Free-text reason — surfaced in Slack + email body and stored on metadata.escalation.reason.
string
Optional structured escalation reason code (lowercase snake_case) drawn from your organization’s configured escalation reason codes. Validated against your tenant’s active codes before it’s stored; an unknown or archived code silently falls back to the free-text reason only.
string (enum: normal|high|urgent)
Escalation priority. Default ‘urgent’. Drives Slack button styling (danger vs primary) and email subject prefix.
string[]
Optional caller override of recipients. Each id is validated against the caller’s organizationId so a malicious or buggy caller cannot leak the notification to another tenant’s users. When omitted, falls back to organizations.settings.escalation.user_ids.
string (enum: slack|email)[]
Opt-out lever for individual notification channels. Default [‘slack’,‘email’] (both). Useful for tests / programmatic callers that only want one fan-out path.
Follow a conversation
POST /api/v1/conversations/{id}/followersstring
required
—
string
Optional explicit follower user_id. Defaults to the caller’s user_id when omitted (the dominant ‘Follow this thread’ button case).
Handoff conversation from AI to a human
POST /api/v1/conversations/{id}/handoffstring
required
—
string
Short reason — surfaced to the receiving human (e.g. ‘asked for a manager’).
string
Optional queue to route to. Defaults to the tenant’s default queue.
string (enum: cant_understand|policy_block|customer_request|tool_failed|escalation_threshold|manual)
Machine-readable handoff category for analytics. When omitted, inferred from the free-text reason.
string[]
Caller override for the skill set used to match operators. When omitted, derived from the conversation’s most recent AI agent’s skill_tags.
string
Caller-supplied 2-3 sentence summary. When omitted, the service generates one via the LLM gateway.
string
Routes the handoff to a specific team’s human queue inside the Inbox module (and pauses the AI agent) instead of the org-wide queue. When omitted, the conversation enters the org-wide queue.
string
Id of the AI agent handing the conversation off. Stored on the conversation row so the Resume AI action knows which agent to re-activate.
Resolve AI handoff
POST /api/v1/conversations/{id}/handoff/resolvestring
required
—
Link a related conversation
POST /api/v1/conversations/{id}/linksstring
required
—
string
required
—
string (enum: relates_to|duplicate_of|blocks|blocked_by|parent_of|child_of)
—
string
—
Merge conversations
POST /api/v1/conversations/{id}/mergestring
required
—
string[]
required
—
boolean
Acknowledge and override the weak-identity safety check. When a source row shares only a phone number or email with the primary but the two carry a conflicting contact_id (different ids, or one side has a contact record and the other doesn’t), the merge is refused with a
422 WEAK_IDENTITY_MATCH_REQUIRES_CONFIRMATION. Re-submit with this set to true to confirm the rows are the same person and force the merge. Defaults to false.Translate one message body to a target language
POST /api/v1/conversations/{id}/messages/{mid}/translatetarget is a BCP-47-ish short tag (en, es, pt-br). Tenant-scoped via the auth ctx schema and double-guarded by a conversation_id predicate on the message read.
string
required
Conversation id
string
required
Message id
string
required
BCP-47-ish target language tag (e.g. en, es, fr, pt-br).
Reopen conversation
POST /api/v1/conversations/{id}/reopenstring
required
—
Reply in a conversation
POST /api/v1/conversations/{id}/replystring
required
—
Create a side conversation
POST /api/v1/conversations/{id}/side-conversations{label, email?, phone?, role?}. Optional initial_message inserts the opening outbound turn in the same call.
string
required
—
string
required
—
object[]
required
—
string
—
Reopen a resolved side conversation
POST /api/v1/conversations/{id}/side-conversations/{sideId}/reopenstring
required
—
string
required
—
Reply in a side conversation
POST /api/v1/conversations/{id}/side-conversations/{sideId}/replystring
required
—
string
required
—
string
required
—
string (enum: outbound|inbound)
—
string
—
Resolve a side conversation
POST /api/v1/conversations/{id}/side-conversations/{sideId}/resolvestring
required
—
string
required
—
Snooze conversation
POST /api/v1/conversations/{id}/snoozestring
required
—
string
required
Absolute ISO 8601 timestamp (must be in the future)
string
Optional human-readable label e.g. ‘wait for invoice approval’
Undo a recent conversation merge
POST /api/v1/conversations/{id}/unmergeexpired counts sources outside the 30-min window so the UI can show ‘contact support for a manual revert’ when the window has fully expired.
string
required
—
Bulk action
POST /api/v1/conversations/bulkstring (enum: close|reopen|assign|add_tags|remove_tags)
required
—
string[]
required
—
string | null
Required for action=assign. Null to unassign.
string[]
Required for action=add_tags / remove_tags.
Update conversation tags
PUT /api/v1/conversations/{id}/tagsstring
required
—
Set conversation priority
PATCH /api/v1/conversations/{id}/prioritystring
required
—
string | null (enum: low|normal|high|urgent|null)
required
One of low | normal | high | urgent, or null to clear.
Unfollow a conversation
DELETE /api/v1/conversations/{id}/followers/{userId}string
required
—
string
required
—
Unlink a related conversation
DELETE /api/v1/conversations/{id}/links/{linkId}string
required
—
string
required
—