Query endpoints
Two endpoints, one request shape. Both need a scoped token — see Issue a scoped token — and both are stateless: each call is answered on its own, with no memory of the last one.
Ask a question
curl -X POST "$BASE_URL/kb-api/api/v1/kb/$KB_ID/widget/query" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"question": "What is the notice period for a fixed-term contract?"}'
Request
| Field | Type | Notes |
|---|---|---|
question | string | Required. The question, in plain language. Note the name — it is not query. |
top_k | integer | How many passages to retrieve before answering. Optional; the service picks a sensible number. |
options | object | Reserved for per-call tuning. Optional. |
conversation_id | string | Accepted and ignored. These endpoints hold no conversation. |
Response
{
"answer": "A fixed-term contract requires 30 days' written notice…",
"citations": [
{ "index": 1, "doc_id": "doc_01J…", "kb_id": "kb_01J…", "source_span": { } }
],
"provenance": "generated",
"abstained": false
}
| Field | Type | Notes |
|---|---|---|
answer | string | The grounded answer. When abstained is true, this is the refusal message. |
citations | array | What the answer was drawn from. Empty when nothing was cited. |
citations[].index | integer | The marker used in answer. |
citations[].doc_id | string | The document the passage came from. |
citations[].kb_id | string | The knowledge base it belongs to. |
citations[].source_span | object | Opaque locator for the passage. Pass it back to us when reporting a problem; do not parse it. |
provenance | string | generated, abstained or qa_shortcut. |
abstained | boolean | True when Elie declined to answer. |
Fields with no value are omitted rather than sent as null.
An abstention is a success, not an error. It arrives as 200 with abstained: true, and it
means the knowledge base held no evidence good enough to answer from. Treat it as an answer your
interface should show, not as a failure to retry.
Stream the answer
curl -N -X POST "$BASE_URL/kb-api/api/v1/kb/$KB_ID/widget/query/stream" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"question": "What is the notice period?"}'
Same request body. The response is text/event-stream, and every frame carries a JSON object
whose type repeats the event name.
| Event | Payload | What to do with it |
|---|---|---|
token | { "type": "token", "text": "…" } | Append text to what you are showing. |
citations | { "type": "citations", "citations": [ … ] } | Render the sources. Same fields as above, plus text when the passage is quotable. May arrive before the answer finishes. |
abstain | { "type": "abstain", "answer": "…" } | Replace everything shown so far with answer. |
reset | { "type": "reset" } | Discard everything shown so far and keep listening. |
suggestions | { "type": "suggestions", "questions": [ … ] } | Optional follow-up questions to offer. |
done | { "type": "done", "abstained": false } | The answer is complete. Close the stream. |
error | { "type": "error", "code": "…", "message": "…" } | Generation failed part-way. Show what you have, plus a failure notice. |
Handle reset and abstain or you will show text that Elie has withdrawn. Both exist precisely
because the service may retract a partial answer it no longer stands behind.
A refused request never becomes a stream: authentication and access are checked before the response opens, so you get an ordinary status code and a JSON body instead.
Next
- Errors — status codes and the error envelope.
- Embed the chat widget — the same endpoints, no code.