Three words, kept straight
A person can have many conversations. What ties them together is
contact_key — your own stable id for that person — not conversation_id,
which is new on every call for an agent that starts clean.
So: to read one run, open the call. To read a person’s history, list
conversations by contact_key. To find out which agent version handled a
given turn, list the conversation’s sessions.
Listing conversations
GET /v1/conversations returns conversations across every channel, most
recently active first.
Only customer threads are listed: web, phone (
sip) and messaging
integrations. CoPilot threads are not conversations.
One conversation
GET /v1/conversations/{conversation_id} returns the header: agent_id and
agent_name for the entry agent, surface, last_message_text,
last_message_at, summary, metadata, vars and userdata.
userdata here is the contact’s bag, not the thread’s. It is what your
agents’ tools have published about this person across every conversation with
them, so every thread with the same contact_key shows the same value. For
what one call ended up holding, read session.userdata on that call —
calls. The two are different questions and the API
keeps them apart.
vars is this thread’s {{vars.*}} values, fixed at creation and never merged
into the contact’s bag.
The transcript
GET /v1/conversations/{conversation_id}/items is what was said.
Each item carries
direction (inbound or outbound), type, role, text,
attachments, metrics for the turn, and — on an outbound one —
delivery_status and delivery_error, which say whether it actually landed.
It also stamps its own agent_id and agent_version, so a thread that handed
off reads correctly line by line.
Which version answered
GET /v1/conversations/{conversation_id}/sessions lists the agent sessions
behind a thread, newest first. This is how you read a multi-agent call: one
row per execution, each naming the agent and the version that ran it.
Did this thread go well?
GET /v1/conversations/{conversation_id}/snapshot returns one verdict over the
whole thread, in the same shape a call’s snapshot uses. issues names every
problem found across every session — errors, failed tools, slow responses, a
bad connection — and ok is true when there were none. Cost and duration sum
across sessions; latency and tooling come from the merged timeline.
Cost sums only the priced sessions and stays null when none are priced, so a
partly-priced thread never reports a total that silently omits the rest.
Correcting what an agent knows
PATCH /v1/conversations/{conversation_id} edits summary, userdata or
metadata.
Writing userdata changes what the agent sees as {{userdata.field}} on the
next turn — the way to correct or preload what it knows from outside a call.
It belongs to the contact, so it reaches every conversation with that
person, not just this one. Each field you send replaces that whole object;
omitted fields are left alone.
Watching one happen
GET /v1/conversations/{conversation_id}/events is a Server-Sent Events stream
that opens with a conversation.snapshot frame — the conversation and its
items — and then pushes item.created, item.delivery_updated, turn and
turn.failed, and assistant.started / assistant.delta /
assistant.completed as a reply is generated. Nothing on the stream is
canonical: a dropped frame is recovered by reconnecting, which re-sends the
snapshot, or by reading the items back. The full frame reference is in
text conversations.
In the dashboard
Conversations is the inbox: one row per thread with its surface, its agent, the last message and when it arrived, filterable by agent. Opening one shows the transcript with the health verdict at the top, the sessions behind it, and per-turn latency for each assistant reply. It follows a live thread over the event stream, so a reply appears as it is generated rather than on a refresh.Calls
One voice or video session at full resolution.
Conversation memory
What a new call knows about earlier ones with the same person.