Прескокни до содржината

Quickstart

Оваа содржина сè уште не е достапна на вашиот јазик.

From a fresh API key to a streamed answer with citations. Four steps, all curl — nothing to install beyond curl and jq.

Open the API Keys screen and create one. Copy it immediately: the key is shown once and cannot be retrieved later, because only a hash of it is stored. If you lose it, regenerate rather than hunt for it.

Put it in your environment, along with the origin you are reading these docs on:

Terminal window
export KOVAC_API_KEY="the-key-you-just-copied"
export KOVAC_BASE="https://your-platform-host"

Every snippet below uses $KOVAC_API_KEY. Keep it that way in your own code too — a key pasted literally into a script tends to end up in a commit, and from there in a backup.

Every run targets an agent by its assistant_id. List the agents your tenant has:

Terminal window
curl -s "$KOVAC_BASE/api/manage/agents" \
-H "Authorization: Bearer $KOVAC_API_KEY" | jq '.[] | {name, assistant_id}'

Pick one that has knowledge attached to it — an agent with no collections configured can still answer, but it has nothing to cite. The Agents screen shows each agent’s attached collections, and Attach knowledge to an agent covers how to add them.

Terminal window
export ASSISTANT_ID="the-assistant-id-you-picked"

A thread holds one conversation’s message history and state. Runs happen on a thread, and a second run on the same thread sees everything the first one said:

Terminal window
export THREAD_ID=$(curl -s -X POST "$KOVAC_BASE/api/agent/threads" \
-H "Authorization: Bearer $KOVAC_API_KEY" \
-H "Content-Type: application/json" \
-d '{}' | jq -r '.thread_id')

Threads belong to the identity that created them. A thread created with this key is not visible to a different key, even one in the same tenant — so give each service its own key rather than sharing one and expecting shared history.

Start a run and read the server-sent events as they arrive:

Terminal window
curl -N -X POST "$KOVAC_BASE/api/agent/threads/$THREAD_ID/runs/stream" \
-H "Authorization: Bearer $KOVAC_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"assistant_id\": \"$ASSISTANT_ID\",
\"input\": {\"messages\": [{\"role\": \"user\", \"content\": \"What does the handbook say about expenses?\"}]},
\"stream_mode\": [\"messages\", \"values\"]
}"

-N matters: without it curl buffers, and you get the whole stream at the end instead of watching it arrive.

The response is a text/event-stream. Each event has a name, a JSON payload and an id:

event: metadata
data: {"run_id":"7d183a7b-...","attempt":1}
id: 7d183a7b-..._event_1
event: messages/partial
data: [{"content":"According to","type":"AIMessageChunk","id":"lc_run--..."}]
id: 7d183a7b-..._event_14
event: values
data: {"messages":[...],"files":{}}
id: 7d183a7b-..._event_58
event: end
data: {"status":"success"}
id: 7d183a7b-..._event_129

stream_mode decides which names you see, and the mode you ask for is not always the event name you get:

  • messages emits messages/partial (token chunks as the model produces them), messages/complete (a message finished) and messages/metadata (per-message run detail). This is what makes a UI feel live.
  • values emits values — the full thread state after each step. Easier to work with if you only want the finished answer: keep the last one.
  • updates emits updates — just what changed at each step.

metadata and end are always sent. Ask for the modes you want, as above, and ignore what you do not need.

end carries the verdict: {"status": "success"} or {"status": "error"}. A run that fails emits an error event first, and the HTTP response is still 200 — the request succeeded, the run did not. See Errors & limits.

If the agent has collections attached, it will call its search tool before answering. Two things show up in the stream as a result.

First, the tool’s result message — a JSON array, one entry per retrieved passage:

[
{
"ref": "c9f3a12b",
"chunk_id": "c9f3a12b-4d5e-6f70-8192-a3b4c5d6e7f8",
"document_id": "dddddddd-1111-2222-3333-444444444444",
"content": "Expenses over 100 EUR require prior approval...",
"source_uri": "upload://handbook.pdf",
"collection": "handbook",
"metadata": { "page": 4, "section": "Expenses" },
"confidence": "supported"
}
]

metadata carries whatever the parser could establish about where the passage came from. page and section are null for formats that have no such structure — a Markdown file, say — so do not assume they are present.

Second, the answer itself, which cites those passages inline by ref:

According to the handbook, expenses over 100 EUR require prior approval
from your line manager [c9f3a12b].

ref names one exact passage, which is the whole point of it: a document and a page do not, because a page is usually several passages. Resolve each bracketed ref back to its row in the tool result, and render it as whatever your interface wants — a numbered marker, a footnote, a link.

Answers never contain a file path or URL. If you are building your own interface, do not expect one, and do not print source_uri to a reader: it is a storage location, not a citation. It is still in the payload because it is what you resolve a link against — which is the argument for setting it to the document’s real home at upload time rather than letting it default.

confidence is supported or weak, or null when the passage was not scored. Treat null as “unknown”, never as weak.

Verifiable citations covers the full payload, and the API for fetching a rendered image of the cited page with the passage outlined.

Every claim drawn from retrieved text should carry a citation. If an answer has none, the agent either did not search or found nothing — Ingest a document and query it walks through telling those two apart.

The same two calls in Python, JavaScript, PHP and Java — pre-filled with a real assistant_id, in both raw-HTTP and official-SDK form — are on any agent’s API tab in the platform. The API reference can also send these requests for you, against your own tenant, with a key you paste into the page.