What is accepted
.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.
Mode A — multipart, for a file you have in hand
Mode A — multipart, for a file you have in hand
documents:create. 201 on a fresh key, 200 on a replay.Mode B — a signed upload grant, for bytes you would rather not proxy
Mode B — a signed upload grant, for bytes you would rather not proxy
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.Mode C — a document you already have
Mode C — a document you already have
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:
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:Content is always a download
.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.
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.

