Conversations
These endpoints let your software chat with an agent, and read or delete the chat history of an agent.
For example, your help desk tool can open a conversation when a ticket comes in, send the customer's question, and show the agent's suggested answer to your support person. Or a nightly job can copy yesterday's conversations into your data warehouse.
A chat over the API works like a chat in the Hosted Chat. The agent uses the same instructions, sources and tools, and every reply counts toward your message limit.
Read the overview first for the base URL, keys, errors and paging.
How a chat works
- Open a conversation. You get its
id. - Send a message to it. The answer holds the agent's reply.
- Send more messages to the same conversation. The agent remembers what was said before.
The conversation object
{
"id": "0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"title": "Return a damaged item",
"sentiment": "neutral",
"tone": "confused",
"handoff_status": "ai",
"last_activity_at": "2026-10-25T10:31:09+00:00",
"created_at": "2026-10-25T10:28:40+00:00"
}| Field | Type | Meaning |
|---|---|---|
id |
string | The conversation's id. |
title |
string or null | The title. Made from the chat after the first reply, unless you set one. |
sentiment |
string or null | The visitor's mood: positive, neutral or negative. null until the conversation is analyzed. |
tone |
string or null | neutral, frustrated, urgent, confused or satisfied. null until analyzed. |
handoff_status |
string or null | Who answers: ai, pending (a person was asked for), human (a teammate took over) or returned (handed back to the AI). See Human handoff. |
last_activity_at |
string or null | When something last happened in it. |
created_at |
string | When it was opened. |
List conversations
Returns the agent's conversations from every surface, the most recently active first.
GET /api/v1/agents/{agent}/conversationsWho can call it: the owner, or a teammate with View conversations.
curl https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations \
-H "Authorization: Bearer $CHATVIA_API_KEY"The answer is { "data": [conversation, …], "next_cursor": … }. See Pagination.
A conversation opened through the API shows up here only after the agent's first reply.
Open a conversation
Opens a new conversation with the agent, ready for messages.
POST /api/v1/agents/{agent}/conversationsWho can call it: anyone with access to the agent. No extra permission is needed, as in the dashboard's Playground.
| Body field | Type | Required | Default | Notes |
|---|---|---|---|---|
title |
string | no | New Chat |
Up to 255 characters. |
chatter_id |
string | no | none | The id of one of your chatters. The conversation is then counted as theirs: their monthly message limit applies, and your functions get their id in X-Chatvia-Chatter-Id. The chatter must have access to the agent. If not, you get a 422 error on chatter_id. |
curl https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations \
-H "Authorization: Bearer $CHATVIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Ticket 8841" }'{
"data": {
"id": "0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"title": "Ticket 8841",
"sentiment": null,
"tone": null,
"handoff_status": "ai",
"last_activity_at": "2026-10-25T10:28:40+00:00",
"created_at": "2026-10-25T10:28:40+00:00"
}
}The answer is 201 Created. A conversation that never gets a reply expires after a day.
Send a message
Sends a message to the agent and returns its reply.
POST /api/v1/agents/{agent}/conversations/{conversation}/messagesWho can call it: anyone with access to the agent. This endpoint has its own rate limit of 30 messages a minute.
You can only send messages to a conversation you opened through the API. A conversation from the hosted chat, the chat widget or a channel answers 404.
| Body field | Type | Required | Default | Notes |
|---|---|---|---|---|
message |
string | yes | Up to 32,000 characters. | |
stream |
boolean | no | false |
true sends the reply piece by piece as it is written. See Streaming the reply. An Accept: text/event-stream header does the same. |
request_id |
string | no | none | Your own id for this message, up to 64 characters. See Retrying safely. |
The reply as one answer
By default you get the reply once it is finished:
curl https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations/0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages \
-H "Authorization: Bearer $CHATVIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "Can I return a damaged item?", "request_id": "ticket-8841-1" }'{
"data": {
"conversation_id": "0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"message": {
"id": "0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b",
"role": "assistant",
"content": "Yes. You can return a damaged item within 30 days for a full refund [1]."
},
"sources": [
{
"id": "0199f3c1-2b3a-7d4e-9f50-6a7b8c9d0e1f",
"label": "returns-policy.pdf",
"kind": "file",
"url": null,
"page": 2,
"passages": 1,
"blocks": [1],
"excerpt": "Damaged items can be returned within 30 days of delivery..."
}
],
"done_reason": "complete",
"response_time_ms": 2140,
"error": null
}
}| Field | Meaning |
|---|---|
conversation_id |
The conversation. |
message.id, message.role, message.content |
The reply. role is always assistant. |
sources |
The sources the reply used, best first. Empty when the agent doesn't show its sources. Each has a label, a kind (file, media, website, text or video), a url or page when there is one, how many passages were used, the blocks that [1] style markers in the reply point to, and an excerpt. |
done_reason |
complete, or error when the reply was cut short. |
response_time_ms |
How long the reply took. |
error |
null, or why the reply was cut short: code, message and retryable. |
When a message fails
When the reply fails before any text was written, you get an error status:
{
"message": "This agent has reached its monthly message limit. Please try again next month.",
"code": "message_limit_reached",
"retryable": false
}code |
Status | Meaning |
|---|---|---|
message_limit_reached |
429 |
The workspace used up its monthly messages. |
chatter_message_limit_reached |
429 |
The chatter of this conversation used up their monthly messages. |
rate_limited |
429 |
Too many messages right now. Try again in a minute. |
access_blocked |
403 |
The agent's security settings blocked this message. |
upstream_unavailable |
502 |
The AI provider didn't answer. Try again. |
internal |
500 |
Something else went wrong. |
retryable is true when sending the same message again can work.
When the reply fails after some text was written, the answer is a 200 with done_reason: "error", the text so far in message.content, and the reason in error. That text is already saved in the conversation and counted. To try again, send a new message, not the same one.
Streaming the reply
Add "stream": true to get the reply as server-sent events: a stream of small messages over one open connection. Your user sees the reply appear while the agent writes it.
curl -N https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations/0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages \
-H "Authorization: Bearer $CHATVIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "Can I return a damaged item?", "stream": true }'The answer has the type text/event-stream. Each event is one data: line of JSON followed by an empty line:
data: {"v":1,"seq":0,"id":"0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b","type":"start","data":{"assistant_message_id":"0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b","user_message_id":"0199f3f1-4b2a-7c1d-9e8f-0a1b2c3d4e5f","request_id":null,"language":"en","replayed":false}}
data: {"v":1,"seq":1,"id":"0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b","type":"delta","data":{"text":"Yes. You can"}}
data: {"v":1,"seq":2,"id":"0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b","type":"delta","data":{"text":" return a damaged item"}}
data: {"v":1,"seq":3,"id":"0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b","type":"done","data":{"reason":"complete","response_time_ms":2140,"finished_at":"2026-10-25T10:31:09+00:00"}}Every event has the same outer fields:
| Field | Meaning |
|---|---|
v |
The format version, always 1. |
seq |
Counts up from 0. |
id |
The id of the reply message. |
type |
What kind of event it is. See below. |
data |
The event's content. |
type |
data |
What to do |
|---|---|---|
start |
assistant_message_id, user_message_id, request_id, language, replayed |
The reply began. |
delta |
text |
Add text to the end of what you have. |
replace |
text |
A safety check rewrote the reply. Throw away what you have and show text. |
reasoning |
text |
The model's thinking, when the model shares it. It is not part of the reply. |
sources |
sources |
The sources the reply used, in the same shape as above. |
tool |
phase, tool_call_id, name and more |
The agent is using a tool. phase is start or end. |
meta |
key, value |
Extra signals, such as handoff when a person takes over. |
error |
code, message, retryable |
The reply failed. The codes are the same as above. |
done |
reason, response_time_ms, finished_at |
The last event. reason is complete or error. |
Skip event types you don't know. New ones may be added.
Retrying safely
Networks fail. If your request times out, you can't tell whether the agent replied. To make a retry safe, send a request_id of your own with each message. When you send the same request_id to the same conversation again within 10 minutes, Chatvia sends back the finished reply again. It doesn't ask the AI model again and doesn't count another message. In a stream, the start event then has "replayed": true.
Use a new request_id for every new message.
Good to know
- Tools that need a person's approval are not offered to the agent here. The agent says it can't do that.
- When a teammate has taken over the conversation in the dashboard, the agent doesn't answer. Your message goes to the teammate, the reply is empty, and a stream carries a
metaevent with the keyhandoff.
Get a conversation
Returns the conversation with its first 500 messages, oldest first.
GET /api/v1/agents/{agent}/conversations/{conversation}Who can call it: the owner, or a teammate with View conversations.
curl https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations/0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f \
-H "Authorization: Bearer $CHATVIA_API_KEY"{
"data": {
"id": "0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"title": "Ticket 8841",
"sentiment": "neutral",
"tone": "confused",
"handoff_status": "ai",
"created_at": "2026-10-25T10:28:40+00:00",
"messages": [
{
"id": "0199f3f1-4b2a-7c1d-9e8f-0a1b2c3d4e5f",
"role": "user",
"content": "Can I return a damaged item?",
"created_at": "2026-10-25T10:31:07+00:00"
},
{
"id": "0199f3f1-4c5d-7e6f-8a7b-9c0d1e2f3a4b",
"role": "assistant",
"content": "Yes. You can return a damaged item within 30 days for a full refund [1].",
"created_at": "2026-10-25T10:31:09+00:00"
}
]
}
}A message's role is user (the visitor), assistant (the AI agent) or agent (a teammate who took over the chat). For longer conversations, use List messages.
List messages
Returns the conversation's messages, newest first. Use it to page through long conversations.
GET /api/v1/agents/{agent}/conversations/{conversation}/messagesWho can call it: the owner, or a teammate with View conversations.
curl "https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations/0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages?per_page=100" \
-H "Authorization: Bearer $CHATVIA_API_KEY"The answer is { "data": [message, …], "next_cursor": … }, with messages in the shape shown above.
Delete a conversation
Erases the conversation and its messages.
DELETE /api/v1/agents/{agent}/conversations/{conversation}Who can call it: the owner, or a teammate with Delete conversations.
curl -X DELETE https://chat.example.com/api/v1/agents/a3f9c2e17b4d08/conversations/0199f3f1-0a1b-7c2d-8e3f-4a5b6c7d8e9f \
-H "Authorization: Bearer $CHATVIA_API_KEY"The answer is 204 No Content. The message text is wiped and the conversation disappears from your chat log and from the visitor's chat. This can't be undone.