Chat API
Endpoints for conversational RAG with SSE streaming, conversation management, and message feedback.
The chat API provides a conversational RAG interface. Users ask questions and receive grounded, citation-backed answers streamed in real time via Server-Sent Events (SSE). Conversations are persisted in MongoDB for multi-turn continuity.
Create Conversation
POST /chat
Content-Type: application/json{
"title": "NadD inhibitor research"
}Returns 201 Created with the conversation metadata:
{
"conversation_id": "uuid",
"workspace_id": "uuid",
"owner_id": "uuid",
"title": "NadD inhibitor research",
"created_at": "2026-03-29T12:00:00Z",
"is_archived": false
}List Conversations
GET /chat?skip=0&limit=20&is_archived=falseReturns conversations for the current user, sorted by most recent activity.
| Parameter | Type | Default | Description |
|---|---|---|---|
skip | integer | 0 | Pagination offset |
limit | integer | 20 | Max results |
is_archived | boolean | false | Include archived conversations |
Get Conversation
GET /chat/{conversation_id}?skip=0&limit=100Returns the conversation with its message history. Messages are paginated.
Send Message (SSE Stream)
POST /chat/{conversation_id}/messages
Content-Type: application/json{
"message": "What are the most potent NadD inhibitors in our collection?",
"mode": "thinking"
}| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | User message (1-10,000 characters) |
mode | string | No | quick, thinking, deep_thinking, or literature (published papers instead of this corpus; only when LITERATURE_ENABLED=true, otherwise 404). Null uses server default. |
reasoning | object | No | Per-lane reasoning override, e.g. {"synthesis": "high"}. Lanes: synthesis, retrieval, base; levels: off, low, medium, high. Absent lanes use the server default. |
stats | boolean | No | Compute chart panels for this answer (default false) |
Returns a text/event-stream response. Because this is a POST, read the stream with fetch() (or an SSE library that supports POST), not the browser EventSource. Each frame carries an id: sequence number.
Generation is decoupled from the connection: if the client disconnects, the run keeps going and its answer is still saved. Only one run per conversation at a time; a second send returns 409.
Reattach and Stop
GET /chat/{conversation_id}/messages/stream?after=-1Reattaches to an in-flight (or just-finished) run: replays frames with an id greater than after, then tails live until done. Returns 404 if there is no active run.
DELETE /chat/{conversation_id}/runStops an in-flight run. Returns 204 No Content; the partial answer is discarded.
SSE Event Types
Events are delivered as named SSE events with JSON data payloads:
agent_step
Emitted when a pipeline stage starts or completes (status: started, completed, or failed). Completed steps may carry an output summary, and thinking_content with a thinking_label for stages that produce reasoning traces.
event: agent_step
data: {"type": "step_completed", "step": "planning", "status": "completed", "output": "Type: factual. ...", "thinking_content": "...", "thinking_label": "Query Analysis"}
event: agent_step
data: {"type": "step_started", "step": "retrieval", "status": "started", "description": "Searching documents (agentic mode)..."}
event: agent_step
data: {"type": "step_completed", "step": "retrieval", "status": "completed", "output": "Found 12 relevant sources (840ms)"}query_context
Emitted after query planning with extracted entities and classification.
event: query_context
data: {"type": "query_context", "query_context_entities": [{"entity_text": "NadD", "entity_type": "target"}], "query_context_authors": [], "query_context_type": "factual", "query_context_reformulated": "most potent NadD inhibitors"}retrieval_results
Emitted after context assembly with the citation sources used for the answer.
event: retrieval_results
data: {"type": "retrieval_results", "sources": [{"citation_index": 1, "artifact_id": "...", "page_id": "...", "page_index": 7, "artifact_title": "...", "text_excerpt": "...", "similarity_score": 0.92, "source_type": "document"}]}literature_results
Emitted in literature mode with the papers found by the literature search tool (Europe PMC), including each paper's licence and whether it can be ingested.
event: literature_results
data: {"type": "literature_results", "literature_results": [{"external_id": "...", "title": "...", "doi": "...", "year": 2024, "licence": "cc by", "is_open_access": true, "is_ingestable": true, "url": "...", "...": "..."}]}token
Streaming answer tokens. Emitted only during the answer formatting stage.
event: token
data: {"type": "token", "delta": "The most potent"}
event: token
data: {"type": "token", "delta": " NadD inhibitor in your collection is"}reasoning_token
Streaming reasoning tokens, emitted alongside the answer stream when the model exposes its reasoning.
event: reasoning_token
data: {"type": "reasoning_token", "delta": "The user is asking about"}grounding_result
Emitted after inline verification.
event: grounding_result
data: {"type": "grounding_result", "grounding_is_grounded": true, "grounding_confidence": 0.92, "grounding_llm_verified": false}structured_block
Rich content blocks such as tables, molecule renderings, or charts. The block.type is one of text, table, molecule, citation_list, source_card, or chart.
event: structured_block
data: {"type": "structured_block", "block": {"type": "molecule", "smiles": "CC(=O)Oc1ccccc1C(=O)O", "label": "SACC-111", "page_id": "..."}}done
Final event with metadata about the completed response.
event: done
data: {"type": "done", "message_id": "uuid", "total_tokens": 2340, "prompt_tokens": 1800, "completion_tokens": 540, "model": "...", "duration_ms": 8500, "sources": [...]}error
Emitted if the pipeline encounters an error.
event: error
data: {"type": "error", "error_message": "Failed to connect to LLM provider"}Client Example
const res = await fetch(`${API_URL}/chat/${conversationId}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${idpToken}`,
"X-Authz-Token": authzToken,
},
body: JSON.stringify({ message, mode: "thinking" }),
});
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
const frames = buffer.split("\n\n");
buffer = frames.pop()!; // keep the incomplete frame
for (const frame of frames) {
const line = frame.split("\n").find((l) => l.startsWith("data: "));
if (!line) continue;
const data = JSON.parse(line.slice(6));
if (data.type === "token") appendToAnswer(data.delta);
if (data.type === "done") setCitations(data.sources);
}
}To resume after a dropped connection, read GET /chat/{conversation_id}/messages/stream?after=<last id> the same way.
Record Feedback
POST /chat/{conversation_id}/messages/{message_id}/feedback
Content-Type: application/json{
"feedback": "positive"
}Returns 204 No Content. Records thumbs-up or thumbs-down feedback on a specific assistant message. Accepts "positive" or "negative".
Delete Conversation
DELETE /chat/{conversation_id}Returns 204 No Content. Deletes the conversation and all its messages.