Chat
Conversations are trees of messages, not flat threads: every user message
can carry a parent_message_id, which is what makes edit-and-resend
branches possible. Sending a message doesn't return JSON — it opens a
Server-Sent Events stream that carries retrieval, agent, and token frames as
the answer is generated.
All routes below require a session JWT (Authorization: Bearer $TOKEN),
gated per-route by the granular permission shown next to each endpoint.
Create Chat
Create a new chat in a workspace.
POST /api/v1/chats · Auth: JWT, chat.generate
Sample request
curl -sX POST https://ragz.example.com/api/v1/chats \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"workspace_id": "a1b2...-uuid", "title": "Q3 policy questions"}'Sample response
{ "id": "c3d4...-uuid", "workspace_id": "...", "title": "...", ... }List Chats
List your chats, optionally scoped to one workspace.
GET /api/v1/chats · Auth: JWT, chat.read
Notes
- Accepts an optional
?workspace_id=query parameter to filter to a single workspace.
Get Chat Tree
Return the full message tree for a chat.
GET /api/v1/chats/{chat_id} · Auth: JWT, chat.read
Rename Chat
Rename a chat.
PATCH /api/v1/chats/{chat_id} · Auth: JWT, chat.update
Delete Chat
Delete a chat.
DELETE /api/v1/chats/{chat_id} · Auth: JWT, chat.delete
Send Message
Send a message on a chat. This does not return JSON — the response is a
text/event-stream that carries retrieval, agent, and token frames as the
answer is generated.
POST /api/v1/chats/{chat_id}/messages · Auth: JWT, chat.generate
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The message text. 1–32000 chars. |
parent_message_id | string (uuid) | null | No | Omit entirely to append to the active branch; pass null to start a new root sibling (editing a root message); pass a UUID to branch from a specific message (edit-and-resend). |
model_id | string (uuid) | null | No | Optional per-message model override; omitted/null uses the workspace's default model. |
reasoning_effort | "off" | "low" | "medium" | "high" | No | Only valid on models that support it — a mismatch is rejected before any stream bytes are sent. |
attachment_ids | array | No | Up to 50 ids from prior POST .../attachments calls to include with this turn. |
web_search_consented | boolean | No | Must be explicitly true for the agent to run an external web search on this turn; fails closed even when the workspace has web search enabled. |
Sample request
curl -N -sX POST https://ragz.example.com/api/v1/chats/$CHAT_ID/messages \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"content": "What is our refund policy?", "reasoning_effort": "off"}'Sample response
The response is text/event-stream. Each frame is event: <name> +
data: <json>. Frames observed over the course of an answer, roughly in
order:
event: retrieval_started
data: {}
event: sources
data: {"sources":[{"marker":1,"document_id":"...","filename":"policy.pdf","page":4,...}]}
event: token
data: {"delta":"Enterprise"}
event: token
data: {"delta":" customers"}
event: citations
data: {"citations":[{"marker":1,"document_id":"...","chunk_ref":"...","page":4,"section":"Refunds","version":2,"url":null}]}
event: done
data: {"message_id":"...","prompt_tokens":812,"completion_tokens":96,"no_answer":false,"grounding":"documents","validation_failed":false}Event frames
| Event | Carries |
|---|---|
retrieval_started | Signals retrieval has begun (empty payload) |
agent_step | {n, tool, query} — one agent tool invocation (Phase 3 agent loop) |
tool_result | {n, tool, results} — display-only results for a tool step (e.g. web search hits) |
sources | {sources: [...]} — retrieved chunks backing the answer, pre-citation |
token | {delta} — one incremental chunk of the assistant's text |
citations | {citations: [...]} — the final citation list (document, chunk, page, section, version, score, url) |
blocks | {blocks: [...]} — generative-UI blocks (charts/tables), only when the workspace has that feature enabled |
done | {message_id, prompt_tokens, completion_tokens, no_answer, grounding, validation_failed} |
error | {detail} — a typed error mid-stream |
Notes
done.no_answer: truemeans Ragz declined to answer because retrieval didn't produce sufficient grounding — this is deliberate no-answer mode, not a failure.done.groundingis"documents"or"web"depending on what backed the answer.
Rate limited
Message sends are capped per user (not just per IP) — expect a 429 if
you script rapid-fire sends. Retry with backoff.
Regenerate Message
Regenerate an assistant reply — streams the identical frame set (see
Send Message) for a fresh answer to an existing assistant
message, optionally with a different model_id / reasoning_effort.
POST /api/v1/messages/{message_id}/regenerate · Auth: JWT, chat.generate
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
model_id | string (uuid) | null | No | Optional per-message model override; omitted/null uses the workspace's default model. |
reasoning_effort | "off" | "low" | "medium" | "high" | No | Only valid on models that support it. |
Event frames
Same event set as Send Message.
Set Message Feedback
Thumbs up/down + optional comment on an assistant message.
PUT /api/v1/messages/{message_id}/feedback · Auth: JWT, chat.feedback
Clear Message Feedback
Clear feedback on an assistant message.
DELETE /api/v1/messages/{message_id}/feedback · Auth: JWT, chat.feedback
Upload Attachment
Upload a file first (multipart/form-data, capped by the interactive
upload limit), then reference its id in attachment_ids on the next
Send Message call.
POST /api/v1/chats/{chat_id}/attachments · Auth: JWT, chat.attachments.create
Sample request
curl -sX POST https://ragz.example.com/api/v1/chats/$CHAT_ID/attachments \
-H "Authorization: Bearer $TOKEN" \
-F "file=@quarterly-notes.png"Sample response
{ "id": "e5f6...-uuid", "kind": "image", "filename": "...", "mime": "image/png", "status": "processing" }Notes
- Image attachments on a vision-capable model are sent to the model directly; everything else (documents, or images on a non-vision model) is routed through OCR/extraction and retrieval instead.
Get Attachment Content
Stream an attachment's bytes inline (previews/thumbnails).
GET /api/v1/chats/{chat_id}/attachments/{attachment_id}/content · Auth: JWT, chat.read