Knowledge Base API
Add documents to a knowledge base someone already created in the workspace, ask questions against it, and hold conversations with it, all from your own code with an API key.
Everything here needs an API key and the 15-minute pass it exchanges for:
how to get both. Paths below are relative to /external/v1/kb.
This is the partner API, reached with an account API key. The chat widget you embed in a web page is a different product with a different credential, issued by a signed-in person for one knowledge base: see Knowledge Base widget. The two are not interchangeable: a widget token will not authenticate here, and an API key will not drive a widget.
Every endpoint
The Scope column is what an administrator ticks on the key. A missing scope is a 403 naming
it; a knowledge base outside the key's reach is a 404, the same as one that does not exist, so
nobody can probe ids to learn what an account holds.
Finding and adding
| Method | Path | Scope | What it does |
|---|---|---|---|
GET | /knowledge-bases | kb:read | The knowledge bases this key may use, id and name only. |
POST | /knowledge-bases/{kbId}/documents | kb:write | Send a document in. multipart/form-data, 25 MB at most. |
GET | /knowledge-bases/{kbId}/ingest-jobs/{jobId} | kb:read | Check an ingest job's progress. |
GET | /knowledge-bases/{kbId}/documents | kb:read | List the documents in a knowledge base. |
GET | /knowledge-bases/{kbId}/documents/{documentId} | kb:read | One document's details. |
DELETE | /knowledge-bases/{kbId}/documents/{documentId} | kb:delete | Remove a document. |
Ingestion is asynchronous. The upload answers 202 with a document_id and a job_id, and
the document becomes searchable when that job reaches INDEXED. Poll the ingest job rather than
assuming the document can be queried at once. A job that ends FAILED carries the stage it
stopped at and the error.
A document the key's integration may not open answers 403 access_denied, even though the
knowledge base itself is within reach.
kb:delete is a scope of its own because removal requests usually arrive through your system
rather than ours, so your system has to be able to act on them, and an administrator should agree
to that on purpose.
What a delete does. The call answers 202 as soon as the removal is accepted. The document's
status becomes deleting, everything indexed from it is removed in the background, and the status
then reads deleted: from that point it contributes no passages or citations to any answer. The
document's record stays, and the original uploaded file is not deleted by this call. To make
the document searchable again, send it in again. Deleting a document that is already deleting or
deleted is not an error.
Asking
| Method | Path | Scope | What it does |
|---|---|---|---|
POST | /knowledge-bases/{kbId}/query | kb:query | Ask a question, get a grounded answer with citations. |
POST | /knowledge-bases/{kbId}/retrieve | kb:query | Get the relevant passages without an answer. |
query writes an answer; retrieve returns the source passages, with their scores, and leaves
the writing to you. Reach for retrieve when you have your own model, or when you want to show
sources without generating an answer. top_k sets how many passages retrieve returns.
Both are charged one request each once they answer, including an answer that declines. Both can
answer 402 when billing declines, or a retryable 503 while the knowledge base is re-indexing.
Both count against the writes budget, 10 a second, because they are POST calls.
Conversations
| Method | Path | Scope | What it does |
|---|---|---|---|
POST | /knowledge-bases/{kbId}/conversations | kb:query | Start a conversation. |
POST | /knowledge-bases/{kbId}/conversations/{id}/turns | kb:query | Send a message, get the reply. |
GET | /knowledge-bases/{kbId}/conversations/{id} | kb:query | Read a conversation with its citations. |
GET | /knowledge-bases/{kbId}/conversations | kb:query | List open conversations. Pass status=archived for closed ones. |
DELETE | /knowledge-bases/{kbId}/conversations/{id} | kb:query | Archive it. There is no way to reopen it. |
A conversation carries its own history: Elie assembles the earlier turns and feeds them into each
answer, so you send only the new message. Sending the turns to query instead loses that context.
Citations in a conversation are checked against current access every time you read it, so one you
could see last week may be gone today if that document's access changed.
Turns are synchronous: the reply comes back in the response, and each one is charged. Two turns
sent to the same conversation at once get a 409 for the second; a turn sent to an archived
conversation gets a 422.
A conversation is visible to every key of the integration that started it, and to nobody else:
not other integrations in the same account, and not people signed into the workspace. If you serve
many end users, keep your own mapping of your user to conversation_id. Elie has no concept of
your end users.
Access
| Method | Path | Scope | What it does |
|---|---|---|---|
PATCH | /knowledge-bases/{kbId}/documents/{documentId}:access | kb:write | Change who can find a document. |
GET | /knowledge-bases/{kbId}/access-directory | kb:read | The roles, departments and groups this knowledge base accepts. |
Use access-directory to fill a picker. It returns only the people this knowledge base already
grants, so you are never offered one that could not reach the document anyway.
:access replaces the document's whole access set and returns a job_id. The change takes effect
when that job completes, because every stored passage of the document has to be rewritten. Don't
read the document back expecting the new access to apply yet.
Ingesting a document
curl -X POST "$BASE/external/v1/kb/knowledge-bases/$KB/documents" \
-H "Authorization: Bearer $PASS" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@handbook.pdf" \
-F "visibility=internal" \
-F "access_role=role_abc"
name overrides the stored filename. visibility is public (everyone who can open the
knowledge base) or internal (narrowed by the access_role, department and people_group
fields you send, each of which you can repeat). As with every upload, Idempotency-Key makes a
retry safe: the same key and the same file replay the original answer instead of adding a second
copy.
On an internal document, a reader reaches it by matching any one of the three, and one you
leave blank does not narrow anything. So sending access_role=role_abc and nothing else means
anyone with that role, not only people who match all three.
visibility defaults to internal, so this covers a bare upload with no access fields at all.
With no access_role, no department and no people_group, the document is accepted and indexed
and then reaches only two audiences: the integration that uploaded it, and tenant
administrators. Everyone else in the workspace is excluded.
That is easy to miss, because your own queries still find it. The document answers your tests
perfectly while the colleagues it was meant for see nothing. Send at least one access field, or
send visibility=public if everyone who can open the knowledge base should reach it.
Asking a question
curl -X POST "$BASE/external/v1/kb/knowledge-bases/$KB/query" \
-H "Authorization: Bearer $PASS" \
-H "Content-Type: application/json" \
-d '{"question":"What is the returns window?"}'
{
"data": {
"answer": "Returns are accepted within 30 days.",
"citations": [
{ "index": 1, "document_id": "doc_01H8XK3M9P", "knowledge_base_id": "kb_01H8XK3M9P", "source_span": null }
],
"provenance": "generated",
"abstained": false
},
"meta": { "version": "v1" }
}
abstained set to true means Elie declined to answer because the knowledge base did not
support one. That is a successful call, not an error, and it is the behaviour you want: declining
beats inventing. Handle it explicitly rather than showing an empty string.
What is not here, and will not be
No endpoint creates, renames or deletes a knowledge base, changes its settings, edits who can open it, connects a source, or manages curated question and answer pairs. No scope unlocks any of it: that is workspace configuration, and it stays where a person can see what they are changing.
Full schemas
Field-level request and response shapes are in the workspace: open Developers, then API explorer. The API reference there is generated from the running service, so it cannot drift. This page is the capability reference: what exists, what each call needs, and where the surprises are.