Chat API
The chat endpoints are the most useful surface for automation: send a question, and the answer comes back grounded in whatever your pipelines have indexed. Retrieval happens automatically on every message, scoped to the collections the authenticated user can access (or a narrower set you choose per chat).
Start a conversation
Section titled “Start a conversation”curl -b cookies.txt -X POST $BASE/chats \ -H 'Content-Type: application/json' \ -d '{"model_name": "llama3.2", "message": "Summarize our vacation policy."}'The response is the full conversation (ChatDetail):
{ "id": "…chat uuid…", "title": "Vacation policy summary", "collection_ids": [], "messages": [ { "id": "…", "role": "user", "content": "Summarize our vacation policy.", "model_name": "llama3.2", "tokens_used": 12, "sibling_index": 0, "sibling_count": 1, "interrupted": false, "sources": [], "created_at": "…" }, { "id": "…", "role": "assistant", "content": "Employees accrue 1.5 days…", "model_name": "llama3.2", "tokens_used": 148, "sibling_index": 0, "sibling_count": 1, "interrupted": false, "sources": [ { "source": "/Policies/vacation_policy.md", "chunk_index": 0, "similarity_score": 0.83, "collection_name": "company_docs", "content": "Employees accrue 1.5 days of paid leave per month…" } ], "created_at": "…" } ], "created_at": "…", "updated_at": "…"}Things to know:
- This form blocks until the answer is fully generated. Set your HTTP client’s timeout generously, or use the streaming endpoints below.
model_namemust be a model that exists in the organization, or you get404 {"detail": "Model '…' not found"}.- Optional
collection_idsrestricts retrieval for this chat to those collections; every id must be a collection you can access (404otherwise). Omitted or empty means “everything I can access”. - A short title is generated automatically from your first message.
- Retrieval is best-effort: if nothing relevant is indexed, the model
answers from general knowledge — the shape of the response is identical
(with an empty
sourceslist).
Sources are ephemeral
Section titled “Sources are ephemeral”Each freshly generated assistant message carries a sources array — the
retrieved chunks that actually made it into the model’s context, with the
file path, chunk index, similarity score, collection name, and the chunk
text itself. This is what powers the citation underlines in the chat UI.
Retrieval results are not persisted. Sources appear only on the reply
you just generated; any later read of the same conversation (GET /chats/{id}/branch, switch_branch) returns those messages with sources
empty. If your integration needs the citations, capture them from the
generating response.
Streaming
Section titled “Streaming”Chat can stream the reply token by token instead of blocking. The streaming
endpoints return NDJSON (application/x-ndjson): one JSON object per
line, not Server-Sent Events.
curl -N -b cookies.txt -X POST $BASE/chats/<chat_id>/followup/stream \ -H 'Content-Type: application/json' \ -d '{"model_name": "llama3.2", "message": "And how many days roll over?"}'{"type": "user_message", "message": { …your persisted message… }}{"type": "token", "content": "Employees"}{"type": "token", "content": " may"}…{"type": "done", "message": { …full assistant message, with sources… }, "stats": {"eval_count": 148, "elapsed": 4.2}}Line types:
| Line | Meaning |
|---|---|
user_message |
Your message as persisted (first line, followup/stream only) |
token |
The next piece of the reply — append content as it arrives |
done |
The completed assistant message (including sources) plus generation stats |
error |
Generation or persistence failed mid-stream — detail explains, persisted says whether a partial reply was saved |
Because the HTTP status is already 200 when generation starts, mid-stream
failures arrive as an error line rather than an error status — handle it.
If your client disconnects mid-stream, the partial reply is saved with
interrupted: true.
Starting a new chat with streaming is a two-step dance: create the chat and user message first, then stream the reply onto it:
# 1. Create the chat; the returned branch ends on your user messagecurl -b cookies.txt -X POST $BASE/chats/init \ -H 'Content-Type: application/json' \ -d '{"model_name": "llama3.2", "message": "Summarize our vacation policy."}'
# 2. Stream the reply to that messagecurl -N -b cookies.txt -X POST \ $BASE/chats/<chat_id>/messages/<user_message_id>/respond/stream \ -H 'Content-Type: application/json' \ -d '{"model_name": "llama3.2"}'Editing works the same way: POST /chats/{chat_id}/edit_message/init with
{"user_message_id": …, "new_content": …} creates the new branch, then
respond/stream on the returned leaf generates the answer.
Continue it (blocking)
Section titled “Continue it (blocking)”curl -b cookies.txt -X POST $BASE/chats/<chat_id>/followup \ -H 'Content-Type: application/json' \ -d '{"model_name": "llama3.2", "message": "And how many days roll over?"}'Returns just the new [user, assistant] message pair. You can switch
model_name on any message.
Scope a chat’s retrieval
Section titled “Scope a chat’s retrieval”curl -b cookies.txt -X PUT $BASE/chats/<chat_id>/collections \ -H 'Content-Type: application/json' \ -d '{"collection_ids": ["<collection_id>", …]}'Restricts which collections this chat searches from now on; an empty list
restores “all collections I can access”. Ids you can’t access come back as a
404 naming them. The chat’s current scope is the collection_ids field on
ChatDetail. If your access to a scoped collection is later revoked, it is
silently dropped from retrieval rather than erroring.
List, rename, delete
Section titled “List, rename, delete”curl -b cookies.txt $BASE/chats # summaries: id, title, timestampscurl -b cookies.txt -X POST $BASE/chats/<chat_id> \ -H 'Content-Type: application/json' -d '{"new_title": "Policy Q&A"}'curl -b cookies.txt -X DELETE $BASE/chats/<chat_id> # permanentChats are private to the authenticated user.
Versions & branching
Section titled “Versions & branching”Conversations are trees: editing a user message or regenerating an assistant
reply creates a sibling version rather than overwriting. Each message
carries sibling_index / sibling_count so you can tell where alternatives
exist.
# Re-answer an assistant message (adds a sibling)curl -b cookies.txt -X POST $BASE/chats/<chat_id>/regenerate_response \ -H 'Content-Type: application/json' \ -d '{"assistant_message_id": "<message_id>", "model_name": "llama3.2"}'
# Edit a user message (new branch + fresh answer)curl -b cookies.txt -X POST $BASE/chats/<chat_id>/edit_message \ -H 'Content-Type: application/json' \ -d '{"user_message_id": "<message_id>", "new_content": "…", "model_name": "llama3.2"}'
# Choose which sibling is activecurl -b cookies.txt -X POST $BASE/chats/<chat_id>/switch_branch \ -H 'Content-Type: application/json' \ -d '{"fk_message_id": "<parent_message_id>", "sibling_index": 1}'
# Read the currently active branchcurl -b cookies.txt $BASE/chats/<chat_id>/branchAll four return the full active-branch ChatDetail, so your client always
has a consistent view after any mutation. Remember that branch and
switch_branch are reads as far as retrieval is concerned — messages come
back with sources empty.
