Prefer clicking? Upload a document covers this same workflow from the dashboard’s Knowledge Base screen — no
curl required. This page uses HTTP throughout, which is the complete surface and what you want for scripting.What a knowledge document is
One uploaded PDF, plus everything derived from it. Uploading creates three things:
The store directory is named by a SHA-256 hash of the
org_id, so one organisation cannot read another’s vectors. Ingest runs as a FastAPI background task, so the upload request returns immediately and the document ripens from processing to ready afterwards.
Chunking and batching are DEFAULT_CHUNK_SIZE 1000, DEFAULT_OVERLAP 200, and DEFAULT_BATCH_SIZE 100 in apps/api/app/rag/ingest_pipeline.py. They are function defaults, not environment variables — changing them means changing the code.
Upload
Multipart POST, one PDF per request.201 with status: "processing" means the file is stored and ingest is scheduled. It does not mean the document is searchable yet. Poll the list route until status is ready. Failure codes are in Knowledge and RAG.
Ingest failures never surface as an HTTP error, because ingest happens after the response. They land on the document as status: "failed" with an error_message. The ones you will see:
List
/api/v1/knowledge, not /api/v1/knowledge/. status is one of processing, ready, or failed. chunk_count is populated only once ingest succeeds; error_message only when it failed. There is no single-document GET route and no pagination — the list returns every document in the organisation.
Delete
500 — deliberately, so you never end up with orphaned vectors that no document row can name. Retry the delete rather than deleting the row by hand.
404 Document not found means no document with that id in your organisation.
Deleting a document does not update agents that reference it. An agent whose
config.knowledge_base.document_ids still names a deleted document keeps working — retrieval simply finds nothing for that id. Update the agent as well.Attaching to an agent
Retrieval only happens for agents that ask for it. The attachment lives inconfig.knowledge_base on the agent, defined by AgentKnowledgeBase in apps/api/app/models/schemas.py.
Attach on an existing agent with a PATCH:
top_k is clamped again at both ends of the wire: KnowledgeRetrieveRequest bounds it 1–10, the runtime clamps it to the same range, and the retrieval service clamps it a third time. Values outside the range are corrected, not rejected.
Retrieval modes
The two modes differ in who decides to search, and the difference is audible on a call.- context
- tool
The default. Before each LLM turn, the runtime takes the caller’s last utterance verbatim, retrieves against it, and rewrites that message in the context with the excerpts folded in. After the assistant’s turn ends the original message is restored, so excerpts do not accumulate across the conversation.Every turn costs one embedding call and one Chroma query. If retrieval returns nothing, the raw user text is used unchanged — the turn still happens.Pick this when the documents are the subject of the call and nearly every turn needs them.
POST /api/v1/rag/retrieve, authenticated with X-API-Key rather than a JWT, because the runtime is a service and not a user. That route is not for operators — it takes an explicit org_id in the body and is documented in Knowledge base (RAG).
Retrieval scoping: when document_ids is set, the query asks Chroma for up to four times top_k candidates (capped at 25) and then filters to the named documents, so narrowing to one document still returns a full set of hits. An empty document_ids list on the retrieve request returns zero chunks; omitting the field searches everything.
Limits
Two environment variables are required for anything to work:
Changing
KB_EMBEDDING_MODEL after documents exist does not re-embed them. Old chunks keep their original embeddings while new queries are embedded with the new model, and comparing across two embedding spaces gives meaningless distances. Re-upload every document after a model change.
Chroma is embedded in the API process, not a separate service. It persists to the voicera_oss_chroma_data volume, which must be in your backup set — see Daily operations.