Developers · API Reference
Search Agent API
Embed the conversational Search Agent in your own portal: multi-turn conversations, live progress streaming, and grounded answers with source citations.
POST /api/v1/search-agent/conversations
Base URL and authentication
Send requests to
Overview
The Search Agent answers questions over your knowledge base in a multi-turn conversation. Start a conversation with POST /api/v1/search-agent/conversations/; the response includes a conversation_id. Send follow-up questions to POST /api/v1/search-agent/conversations/{conversation_id}/turns/, and the agent resolves references against the earlier turns ("what about last year?").
Because the agent researches across several rounds of retrieval before it answers, a turn takes longer than a one-shot search. Allow for that in your HTTP client's timeout settings, or use the streaming endpoints, which report progress while the agent works and keep the connection active.
Request
Headers
| Name | Required | Description |
|---|---|---|
| x-api-key | Yes | Your API key for authentication |
Body
The request body should be in JSON format with the following parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | The question for this turn, up to 5,000 characters |
{ "query": "What was EMEA revenue in Q3?" }
Response
Success Response
- Code: 201 Created (new conversation) or 200 OK (follow-up turn)
- Content-Type:
application/json
{ "conversation_id": "7f9d2c48-1a9e-4a41-9c9a-1f2f2b1f6d2e", "turn_id": "a5b0f9f0-6a5f-4f5b-9e0d-8f6a1c2d3e4f", "ordinal": 0, "ai_summary": "EMEA Q3 revenue was €12.4M, up 8% on Q2 [0]...", "documents": [ { "source": { "title": "Q3 Board Report", "path": "https://www.example.com/q3-board-report", "extension": "pdf" }, "date": "2026-07-14", "extract": "EMEA revenue reached €12.4M in the third quarter..." } ] }
| Field | Type | Description |
|---|---|---|
| conversation_id | uuid | Use it to send follow-up turns and read the thread |
| turn_id | uuid | Identifies this turn, e.g. for feedback |
| ordinal | integer | Zero-based position of the turn in the conversation |
| ai_summary | string | The answer, in Markdown, with [n] citations into documents |
| documents | array | The cited source documents, in citation order: title, path, extension, date, and extract |
When the agent needs more information to answer, it asks for it in ai_summary and cites nothing; send the user's reply as the next turn.
Follow-up turns
Send the next question in the same conversation to POST /api/v1/search-agent/conversations/{conversation_id}/turns/ with the same body shape. The response is identical, with ordinal incremented. Retrying a request creates a new turn: requests are not idempotent, so avoid automatic retries of turn POSTs.
Error Responses
| Code | Meaning | Content |
|---|---|---|
| 400 | Missing, empty, or over-length query | {"error": "A non-empty query is required."} |
| 403 | Missing or invalid API key, or the Search Agent is not enabled for your tenant | {"detail": "Invalid API key"} |
| 404 | Unknown conversation for this API key | {"error": "Conversation not found."} |
| 409 | The conversation reached the model's context limit and can't continue; start a new conversation | {"error": "...", "disabled": true} |
| 413 | Request body larger than 64 KB | {"detail": "Request body too large."} |
| 502 | The agent could not complete the turn; the failed turn is recorded on the conversation | {"error": "The assistant could not complete this request."} |
POST /api/v1/search-agent/conversations/stream
Base URL and authentication
Send requests to
Overview
The streaming endpoints run exactly the same turn as the JSON endpoints, but respond with Content-Type: text/event-stream (Server-Sent Events) and emit progress while the agent works. Use POST /api/v1/search-agent/conversations/stream/ to start a conversation, and POST /api/v1/search-agent/conversations/{conversation_id}/turns/stream/ for follow-ups. The request body and headers are identical to the non-streaming endpoints.
Streaming carries progress, not answer tokens: the answer arrives whole in the final result frame, whose payload is exactly the JSON endpoint's response body.
Frame contract
Each frame is a standard SSE event: an event: line naming the frame type and a data: line carrying a JSON payload. Comment frames (lines starting with :) are keep-alives; ignore them.
| Event | Payload | Meaning |
|---|---|---|
| start | {} | The turn has been accepted and is running |
| stage | {"stage": "...", ...} | Progress update; see stage names below |
| result | Turn response body | The finished answer, in the same shape as the JSON endpoint response. Final frame. |
| error | {"error": "...", "disabled"?: true} | The turn failed; disabled marks a conversation that can't continue. Final frame. |
Stage names
| Stage | Extra payload | Meaning |
|---|---|---|
| understanding | — | The agent is reading the question |
| searching | {"round": n} | A retrieval round is running |
| writing | — | The agent is composing the answer |
Stages report progress only. The cited source documents arrive once, with the answer, in the result frame.
event: start data: {} event: stage data: {"stage": "understanding"} event: stage data: {"stage": "searching", "round": 1} event: stage data: {"stage": "writing"} event: result data: {"conversation_id": "7f9d...", "ai_summary": "EMEA Q3 revenue was...", ...}
Note that the browser EventSource API only supports GET; consume these endpoints with fetch and a stream reader, or any SSE client library that supports POST. Bad input and unknown conversations are rejected with a JSON 400/404 before the stream opens; once the stream has opened, failures arrive as an error frame. Disconnecting mid-stream does not cancel the turn: it completes and is stored on the conversation.
GET /api/v1/search-agent/conversations
Base URL and authentication
Send requests to
List conversations
GET /api/v1/search-agent/conversations/ returns the conversations created with your API key, newest first.
{ "conversations": [ { "id": "7f9d2c48-1a9e-4a41-9c9a-1f2f2b1f6d2e", "title": null, "display_title": "What was EMEA revenue in Q3?", "status": "active", "created_at": "2026-08-12T09:00:00Z", "updated_at": "2026-08-12T09:05:00Z", "turn_count": 2 } ] }
Read a conversation thread
GET /api/v1/search-agent/conversations/{conversation_id}/ returns the full thread: every turn with its query, answer, cited documents, and status, in order. The top-level disabled flag is true when the conversation can take no further turns (it reached the model's context limit).
{ "id": "7f9d2c48-1a9e-4a41-9c9a-1f2f2b1f6d2e", "title": null, "status": "active", "created_at": "2026-08-12T09:00:00Z", "updated_at": "2026-08-12T09:05:00Z", "disabled": false, "turns": [ { "id": "a5b0f9f0-6a5f-4f5b-9e0d-8f6a1c2d3e4f", "ordinal": 0, "query": "What was EMEA revenue in Q3?", "status": "completed", "created_at": "2026-08-12T09:00:00Z", "ai_summary": "EMEA Q3 revenue was...", "documents": [ ... ], "feedback_rating": null } ] }
Error Responses
Unknown Conversation
- Code: 404 Not Found
- Content:
{"error": "Conversation not found."}
Invalid API Key
- Code: 403 Forbidden
- Content:
{"detail": "Invalid API key"}
POST /api/v1/search-agent/conversations/{id}/turns/{turn_id}/feedback
Base URL and authentication
Send requests to
Overview
Record a rating on a turn's answer, for example when a user of your portal clicks thumbs up or down. Feedback helps Nous AI monitor and improve answer quality for your tenant.
Request
Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| rating | enum | Yes | positive or negative |
| issue_type | string | No | Issue category for a negative rating, up to 100 characters |
| detail | string | No | Free-text note, up to 2,000 characters |
{ "rating": "negative", "issue_type": "wrong_answer", "detail": "The Q3 figure is from the draft report." }
Response
Success Response
- Code: 200 OK
- Content:
{"status": "ok", "feedback_rating": "negative"}
Error Responses
Invalid Rating
- Code: 400 Bad Request
- Content:
{"error": "A valid rating is required."}
Unknown Turn
- Code: 404 Not Found
- Content:
{"error": "Turn not found."}