Conversations
A conversation is a thread between one visitor and one chatbot. Every surface writes to the same place — the API, the website widget, the hosted agent page and every messaging channel — so this is where you go to see what your bots are actually being asked.
Listing conversations
GET/v1/conversations
With no parameters you get the whole account, newest first. That is usually what an analytics job or a support inbox wants.
curl "https://api.agentency.com/v1/conversations?limit=20" \
-H "Authorization: Bearer <YOUR_KEY>"Narrow it with chatbot_id when you only care about one bot:
curl "https://api.agentency.com/v1/conversations?chatbot_id=1&limit=20" \
-H "Authorization: Bearer <YOUR_KEY>"{
"object": "list",
"data": [
{
"object": "conversation",
"id": 7,
"session_id": "widget-3f19…",
"chatbot_id": 1,
"status": "active",
"channel": "widget",
"is_reviewed": false,
"handoff_requested": true,
"handoff_reason": "pricing_negotiation",
"started_at": "2026-01-01T00:00:00+00:00"
}
],
"has_more": true,
"next_cursor": "eyJpZCI6NywiX3B…"
}Results are cursor-paginated. Pass next_cursor as starting_after to walk
forward — see Pagination.
Reading one thread
GET/v1/conversations/{id}/messages
curl "https://api.agentency.com/v1/conversations/7/messages" \
-H "Authorization: Bearer <YOUR_KEY>"Each message carries the question, the answer, the confidence, the token cost, and the sources the answer was grounded in:
{
"object": "message",
"id": 42,
"conversation_id": 7,
"query": "How long do refunds take?",
"response": "Refunds are issued to the original payment method within 5 business days.",
"status": "answered",
"confidence_score": 92.4,
"sources": [{ "title": "Refund policy", "dataset_id": 3 }],
"input_tokens": 33,
"output_tokens": 157,
"cost_usd": "0.000412"
}`sources` on a stored message is the same citation list the live chat response returned. You can render "where did this come from" long after the turn, from the message id alone.
Finding a conversation by session id
If you have the session id — from a chat response, a webhook, or your own records — you can look the conversation up directly:
GET/v1/conversations/by_session/{sessionId}
This accepts sessions from any surface. api_… from this API, widget-… from
the website widget, and the ids channels mint are all valid, as long as the
conversation belongs to your account.
Handoff: the one to watch
handoff_requested means the visitor asked for a human, or the bot decided it
could not help. It is the most time-sensitive thing in this data, and polling
for it is the wrong tool.
`conversation.handoff.requested` fires once per conversation, on the edge from false to true. Route it to whatever your team actually watches.
handoff_reason carries the why, when one was given.
Review workflow
Conversations can be marked reviewed, which is how a QA process keeps track of what a human has already read.
POST/v1/conversations/{id}/review
Needs conversations:write. is_reviewed appears on every conversation
object, so you can list what is still outstanding.
Feedback on an answer
If your interface offers thumbs up/down, record it. Feedback is stored against the message and shows up in analytics.
POST/v1/messages/{id}/feedback
curl -X POST https://api.agentency.com/v1/messages/42/feedback \
-H "Authorization: Bearer <YOUR_KEY>" \
-H "Content-Type: application/json" \
-d '{"feedback": "positive"}'Deleting a conversation
DELETE/v1/conversations/{id}
Removes the thread and its messages. Use it for data-subject requests, or to
clear test traffic before going live. It emits conversation.ended.
There is no undelete. If you need the transcript for compliance, export it first.
Exporting
There is no bulk conversation export endpoint. Page through
GET /v1/conversations with created[gte] and created[lte], then fetch
messages per conversation. Cursor pagination is stable under concurrent
writes, so a long export will not skip or repeat rows.
curl "https://api.agentency.com/v1/conversations?created[gte]=2026-01-01&created[lte]=2026-01-31&limit=100" \
-H "Authorization: Bearer <YOUR_KEY>"Common mistakes
- Assuming one channel. A bot on the widget and on WhatsApp writes both
into the same list. Filter on
channelif you only want one. - Trying to append to a widget conversation. Reading is cross-channel;
writing a new turn is not.
POST /v1/chatcontinues API sessions only. - Polling for handoffs. By the time a poll notices, the visitor has left.
- Paging with offsets. This API is cursor-based; there is no
pageparameter.
What's next
- Chat — where these messages come from.
- Events — conversation events you can subscribe to.
- Pagination — cursors, filters and limits.