Knowledge bases and search
A knowledge base is the unit everything else hangs off. Files belong to one, RBAC grants are made against one, and both the index and search are scoped to one. Folders sit inside a knowledge base and are optional.
Before you start. You need an API key from the
quickstart. The index and search only report content for files
that have been parsed — an unparsed document appears in the index
with latest_parse_job: null and produces no search hits.
Two endpoints do most of the work on this page: GET /v1/knowledge-bases/{kbId}/index
tells you the state of every document, and POST /v1/knowledge-bases/{kbId}/search
finds the evidence to cite.
Knowledge bases
Code
Names are unique per organisation, compared case-insensitively. A clash returns
409 with duplicate_knowledge_base_name, one of the stable error
codes.
| Call | Notes |
|---|---|
POST /v1/knowledge-bases | name required; description and metadata optional |
GET /v1/knowledge-bases | Returns { "knowledge_bases": [...] }, newest first, filtered to what your key or role can reach |
PATCH /v1/knowledge-bases/{kbId} | Rename. name is required even when you only want to change description |
DELETE /v1/knowledge-bases/{kbId}?confirm_permanent=true | Permanent, and it takes every file with it |
Delete is permanent
There is no soft delete for a knowledge base. The call removes every file in it — active and trashed alike — deletes their managed filesystem roots, and then removes the folders, imports, RBAC grants and jobs attached to the knowledge base. Nothing can be restored afterwards.
Without confirm_permanent=true the request fails with 400 and the code
confirmation_required. That query parameter is the only safety catch.
The response reports exactly what went:
Code
The per-file counts are summed across the deleted files; the remaining keys are the knowledge-base-level rows.
Folders
Folders live inside one knowledge base and nest through parent_folder_id.
Code
POST /v1/knowledge-bases/{kbId}/folders
is the only folder operation on the public API. To see which folder a file is
in, read the index — every entry carries folder_id and folder_name. To read
one folder, pass ?folder_id= to the index. To move a file between folders,
use PATCH /v1/files/{fileId}/move.
The index
GET /v1/knowledge-bases/{kbId}/index returns every file in the knowledge
base, with its parse and extraction state and links to its
artifacts, in one request.
Code
Code
latest_parse_job and latest_extract_job are null when the file has never
been through that stage. Inside parse_artifacts.links, an individual link is
null when that artifact does not exist for this document — workbook_json
and tables_json only appear for spreadsheets, pages and imgs only when
the parse produced page images or figures. extract_artifacts.links holds a
single result_json.
Every link is a path, not an absolute URL. Prefix it with your API base and
send the same x-api-key header.
storage_path is an opaque bundle key. Do not build file paths out of it —
fetch artifacts through the links.
Start here, not with the file list
An agent connected to a knowledge base should begin at the index, which is the same advice the agent guidance gives its tools. Listing files and then fetching each file, its jobs and its artifacts is three round trips per document for information the index already returned in one, and the per-file walk gives you less — job status and artifact links do not appear on the file record.
Polling the index is also how you watch a bulk parse finish. One call shows
which documents are completed, which are still running, and which failed
and why.
Search
POST /v1/knowledge-bases/{kbId}/search is lexical Postgres full-text search
over the Evidence Index — the segments derived from parse artifacts. It calls
no model. It therefore spends no credits and returns in the time
a database query takes.
Code
| Field | Type | Notes |
|---|---|---|
query | string | Up to 4000 characters. A blank query applies the filters with no text matching |
limit | integer | Defaults to 30, capped at 80 |
file_ids | array of uuid | Restrict to specific documents |
folder_id | uuid | Restrict to one folder |
years | array of integer | Matches segments tagged with any of these years |
source_type | string | For example pdf |
table_only | boolean | Only segments the parser identified as tables |
The snake_case fields are also accepted in camelCase: fileIds, folderId,
sourceType, tableOnly. A malformed body returns 400 with
invalid_request_body.
Matching is term-based, not semantic. The query is lowercased and split into
tokens, common filler words are dropped, and a small synonym list is applied,
so bills also matches invoice and charges. Table segments get a small
ranking boost. Only segments from a file's current version are searched, and
deleted files are excluded.
The response
Code
| Field | What it is |
|---|---|
fileId | The document. docId is the same value, kept for older clients |
fileVersionId, artifactManifestId | The exact version and parse run the text came from |
segmentId | The element inside that parse artifact — the citation anchor |
markdown | The segment text as parsed |
score | Postgres ts_rank_cd against the query. Comparable within one response, not across responses |
coordinates | pageNumber and the xmin/ymin/xmax/ymax box for PDFs; for spreadsheets, sheetIndex, sheetName, startRow, endRow, startCol, endCol and rangeA1 |
tableMetadata | isTable, plus headers, rowLabels and noteRefs when the segment is a table |
noteRefs | Footnote markers found in this segment |
neighbors | The segments either side, and the segment ids of any notes this table points at |
neighbors is what you widen a hit with. A single row of a table rarely
carries its own context; fetch previousSegmentId, nextSegmentId or
noteSegmentIds from the parse artifact when the hit alone is too narrow.
Index freshness
Search reads a derived index, rebuilt from the latest parse artifacts — the artifacts are the source of truth, the index never is. A completed parse enqueues an incremental index update for that file on its own, and a parsed-bundle import does the same for what it imported, so the index follows the corpus without your involvement. Right after a parse or import completes, search may briefly lag the newest content while that update runs.
Every search response embeds the index snapshot it answered from, so a client that cares can see exactly what state it searched.
Citing a hit
A hit already contains everything a citation needs: fileId, segmentId and
coordinates, pinned to fileVersionId and artifactManifestId.
To get from there back to the source:
- Full segment list for the document —
GET /v1/files/{fileId}/segmentsreturns the segments of the newest parse, so you can pull a neighbour by id. - The parsed markdown — the index gives you
parse_artifacts.links.result_md, or fetchGET /v1/artifacts/{manifestId}/result_mdwith theartifactManifestIdfrom the hit. Using the manifest id from the hit rather than the newest one keeps the citation pointing at the text you actually searched.
Importing existing documents
Bringing an already-parsed corpus into a knowledge base — from a previous DocAI deployment or another system — is a DocAI web application feature: Document Library → Import corpus, with Connect folder for a directory that keeps changing. There is no integration API for it.
From the API, the path for existing documents is the ordinary one: upload
each file with POST /v1/files, then parse the set in one call
with bulk parse. Parsing is charged per page.
Where to next
- Parsing — how a document becomes the artifacts the index lists and search reads.
- Extraction — pull the same fields out of every document a knowledge base holds.
- MCP server — the index and search as tools your coding agent can call directly.

