Skip to main content

Conversations API

A single thread per contact-channel pair, regardless of which channel the message came in on. Conversations are how the inbox UI groups inbound messages, AI replies, and agent replies — and how you’d integrate the same lifecycle into your own UI. Base path: /api/v1/conversations Authentication: API key (X-API-Key) or session JWT. This page is a curated map of the whole surface, grouped by task. Every endpoint below is live and callable. For per-endpoint request/response schemas and copy-pasteable code samples in six languages, see the full Conversations endpoint reference.

Using the SDKs

Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.

Read & inspect

Lifecycle

Assignment & routing

assign targets an individual agent only — pass agent_id: null to unassign. To route a conversation into a team’s queue instead of to one person, use handoff with a target_queue; omit it to place the conversation in the org-wide queue.

AI handoff & copilot

For AI-led conversations, Orbit provides explicit handoff mechanics so a human agent picks up exactly where the AI left off.

Custom fields, translation & sharing

Followers

Side conversations

Loop an external vendor or subject-matter expert into a child thread tied to the parent conversation, without exposing it to the customer.

Worked request/response examples

Request and response envelopes side by side for the four operations an inbox integration calls first — reply, list the inbox, hand off to a human, and the bulk action. Each one pairs the curl call with the equivalent typed Node SDK call, and the response block shows the exact envelope the API returns, including meta.request_id and meta.pagination where it applies.

Reply to a conversation

A successful reply answers 202 Accepted: the message is queued onto the conversation and delivery continues asynchronously, so watch your message.delivered webhook or poll GET /:id/messages for the terminal state. The reply goes out on the conversation’s inbound channel unless you pass a channel override; a closed, snoozed, or archived conversation rejects the reply until you POST /:id/reopen.

List the inbox

Read the open queue with a channel filter, and page deeper with the cursor the response hands back.
Filters are single-value params (status, channel, sentiment, unread, has_agent, search, …) — combine them in one query string. Page with limit plus the meta.pagination.cursor the previous page returned; when has_more is false the list is exhausted. The SDK’s client.conversations.iter() walks every page for you when you don’t want manual cursor handling.

Hand off to a human

handoff parks the AI and places the thread into a queue for a human operator; the response returns the handoff packet inline so the owner of the queue can greet the customer with context.
While the handoff waits, GET /:id/handoff/queue-position returns the live position, and POST /:id/handoff/resolve marks it done. reason_category accepts cant_understand, policy_block, customer_request, tool_failed, escalation_threshold, or manual.

Bulk action across conversations

Apply one action — close, reopen, assign, add tags, remove tags — across a batch of conversation ids in a single call. The discriminator is action; assign needs agent_id, the tag actions need a tags list. Up to 200 ids per request.
The batch runs inside one transaction, so it is all-or-nothing: any per-id failure rolls the whole batch back and every entry in results reports ok: false with the error next to it, rather than leaving a partial state behind.

More worked samples

Worked request/response envelopes for the next operations an integration hits after the basics below: page a message thread, override the reply channel, tag and prioritize, answer a router offer, drive the conversation surface from webhooks, and loop in side-conversation participants. Each pairs the curl call with the typed Node SDK call, and every response block shows the exact envelope the API returns.

Page a conversation’s messages

The thread is oldest-newest by default; page with the participant_id filter when a side conversation or a monitor session scaffolds extra participants into one thread and you want one side’s view.
Operator actions (assignments, closes, merges) are NOT in this list — read those from GET /:id/activity. Internal notes live under the Inbox API, not the public thread.

Send a reply with a channel override

The reply goes out on the conversation’s inbound channel by default — pass channel to move the thread elsewhere (an email escalation on an SMS thread, a WhatsApp follow-up on a web-chat conversation). A closed conversation still rejects the reply until you POST /:id/reopen.
To move the whole thread onto another channel (so subsequent replies also start there), call POST /:id/resume-channel with { "channel": "email" } instead. To check which sender identity a reply will use before sending, GET /:id/reply-sender returns { channel, from, source }.

Tag and prioritize a conversation

There is no PATCH /api/v1/conversations/{id} on this surface — status moves are explicit lifecycle verbs (close, snooze, reopen, escalate, assignment accept/decline), and the granular writes use their own paths. Replace the tags array and set the manual-triage flag in two calls.
PUT /tags replaces the whole array — send the full set, not a delta. The priority flag (none, low, medium, high, urgent) is a pure sort/filter marker: it never notifies anyone, matches the activity feed, and filters via GET /conversations?priority=urgent.

Answer a router offer

When the omnichannel router offers you a conversation, accept it to stamp the acceptance, or decline it so the router re-offers to the next best-fit agent (excluding everyone who already declined). The acting user must be the current assignee; a stale offer comes back as 422 ASSIGNMENT_OFFER_STALE.
POST /:id/assignment/decline takes an empty body and returns the same shape with a next_offer summary when a replacement agent was found.

Drive the conversation surface from webhooks

The conversations surface emits message.received, message.delivered, conversation.created, conversation.updated, and the status lifecycle events (see the webhook events reference). Subscribe a tenant-owned endpoint, then watch the delivery stream. Events filter by exact type repeat param or a comma-separated list.
Delete with DELETE /api/v1/webhooks/{id} when the receiver rotates. A delivery that keeps failing lands in the DLQ — replay one with POST /api/v1/webhooks/dlq/{delivery_id}/replay once the receiver is healthy. Verify signatures per the webhook consumer guide.

Loop in side-conversation participants

Side conversations carry an external_participants array — each entry is a { label, email?, phone?, role? } object, the E.164 phone optional when email is the channel. Create or list the child threads without exposing them to the customer.
Reply in the side thread with POST /:id/side-conversations/{sideId}/reply; inbound replies from the external party carry their label as the author. Resolve with .../{sideId}/resolve and reopen within the window with .../{sideId}/reopen.

Work through it end-to-end

Four worked flows a contact-center integration hits almost immediately: reply, handle the WhatsApp window error, call the same flow from the Node SDK, and kick off an AI → human handoff.

1. Reply on the conversation’s channel

The reply goes out on the same channel the conversation is on (SMS, WhatsApp, email, …) unless you pass a channel override.

2. Handle the WhatsApp 24-hour window error

WhatsApp only accepts free-form messages within 24 hours of the contact’s last inbound message. Outside that window the reply returns 422 with a typed error envelope:
The fix is to re-engage with an approved template through the Messaging API. Once the contact replies, the window is open again and free-form replies succeed.

3. Same flow from the SDKs

conversations is a covered resource in the Node SDK, so you don’t need raw fetch for the flow above:
The SDK also exposes assign, handoff, resolveHandoff, getHandoffQueuePosition, copilotSuggest, updateTags, close, snooze, reopen, and merge with full typings.

4. Kick off an AI → human handoff

Call handoff when an AI-led conversation needs a human. The response returns the handoff packet inline — the clearance status, the auto-generated brief, the matched operators, and the customer’s live queue position — so the human picks up with full context.
While the handoff waits, GET /handoff/queue-position returns the live position, GET /handoff/packet returns the full packet again, and POST /handoff/resolve marks it done and closes the AI pause. To let the AI take the thread back, call POST /agent/resume.

Common operations

Four paired bash + Python snippets that cover the highest-traffic operator flows: filter the open inbox, reply and assign in one pass, escalate into a handoff, and read the sentiment timeline with the outcome prediction. All shapes here match the endpoint reference.

1. List the open inbox filtered by channel + sentiment

Filter with status=open, channel=whatsapp, and sentiment=negative — single-value params, no CSV needed.

2. Reply, then assign the conversation

Post the reply, then write the queue-tag. The two calls are independent — a failed reply should not block assignment.
PUT /tags replaces the whole array, so send the full set. To assign to an agent instead of a tag, post assign with { "agent_id": "usr_2ab" } (or null to unassign).

3. Escalate, then hand off — read the packet before resuming

Escalation notifies your configured recipients; a handoff parks the AI and queues the thread for a human. Read GET /handoff/packet before posting /agent/resume so you hand the thread back with the resolution captured.
These packet fields are what a consumer reads before resuming: needs_human gates the resume call, handoff_brief is the human’s working context, and matched_operators / queue_position tell you who can take it and how long the customer waits.

4. Read the sentiment timeline and outcome prediction

Two reads, one conversation id in the path. The timeline returns the per-message trajectory oldest → newest; the prediction scores the live thread — resolution likelihood, escalation risk, and a predicted CSAT before any survey comes back.
The prediction short-caches for 90s and busts on each new message; ?refresh=1 forces a recompute. recommended_action is one of monitor, coach_agent, proactive_escalate, reassign, or offer_resolution — read it before deciding whether this thread needs a handoff at all.

See also