Skip to main content
A document is a stored file plus the normalised representation the platform parsed out of it. Parse once, read many times — extractions, reports and knowledge ingestion all read the same representation rather than each re-parsing the bytes.

What is accepted

Audio is transcribed. .doc, .ppt and .xls — the pre-2007 formats — are not accepted. The ceiling is 20 MB unless your deployment lowers it. An oversized upload is 413; an unreadable type is 415, with a rejection sentence written for a human.
Both are refused at POST, before the bytes are stored. The extraction worker would reject an unreadable file too — but by then a document row exists and you have a 201 followed by a failure you have to poll for. A 400-family answer with the reason in it is better.

POST /documents — one endpoint, three modes

The mode is chosen by what the body carries, and they are mutually exclusive. Naming two is a 400 rather than a precedence rule nobody can remember: an ambiguous request is a request whose author believed something that is not true.
Needs documents:create. 201 on a fresh key, 200 on a replay.
Three steps.
Step 2 takes the raw body as the file — no multipart, no field name, no metadata. The endpoint exists so a client can stream bytes at a URL, and every additional thing it parses is another thing an unauthenticated caller can reach. POST is accepted as well as PUT, for clients that cannot PUT.Step 2 creates nothing. The upload becomes a Document only when an authorised caller confirms it in step 3.
POST /documents sets no replay header. The status is the signal: a 200 where a create answers 201 is the replay. See Idempotency.

Signed grants

Two endpoints take no API key, and that is deliberate. The signed token in the path is the credential. A grant names one upload or one document, for one purpose, inside one isolation boundary, and it is minted only after a credentialed caller has passed both the scope check and the credit gate. Every grant is re-checked against the row on redemption, for something a signature cannot cover. A valid grant for a document that has since been deleted is a 404. A signature proves the link was issued; it does not prove the thing it points at still exists and is still yours. Mint a download link with:
Needs documents:read. It is a POST rather than a GET because it mints a capability: it is not safe to repeat from a browser’s history bar, and a link handed out by a GET ends up cached by something.

Reading a document

The representation

Parsed once, on ingest, and read from then on:
Asking for the representation of a document that has not been processed yet is a 409, and so is asking for one whose processing failed — with different messages, so you can tell “wait” from “this will never arrive”. The parser is not named in the response. Which engine read your file is an implementation detail that would become a contract the moment it was published.

Content is always a download

Both are fixed and neither is caller-controllable. A tenant can upload an .html or an .svg, and serving one inline from an origin that also serves the API is stored XSS with a customer’s own file as the payload. There is no legitimate request for “render my upload in your origin”.

Paging

GET /documents returns next_cursor and reads it back as cursor — not after, which is what the conversation-messages endpoint uses.
Cursors are opaque base64url. Do not construct one.

What happens next

Extractions

A document plus a JSON Schema becomes validated fields with evidence.

Knowledge ingestion

Publish a document into the index so agents can retrieve from it.
Uploading a document does not make it retrievable by an agent. Publication is a deliberate, separate act — see Knowledge ingestion.