Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.
Redact PII inside whole documents and images. The surface is async: POST an upload, get a file_id back immediately, poll until the job is ready, then download the redacted file in the same format you sent.
For a runnable walkthrough with curl / Python / TypeScript, see the Files example. This page is the reference.
File output is irreversible. Unlike /mask there is no envelope and no recovery; detected entities are removed from the document in place. It is the file analogue of /redact.

Endpoints

All routes accept a Bearer key or a browser session cookie. A file belongs to the uploading user; any of your keys can poll or download it. Cross-user access returns 404 (never 403), so file ids can't be probed.
MethodPathPurpose
POST/api/v1/files/redactUpload (multipart) and enqueue a redaction job. Returns 201.
GET/api/v1/filesList your files, newest first (keyset pagination).
GET/api/v1/files/{file_id}Poll one job's state and metadata.
GET/api/v1/files/{file_id}/downloadStream the redacted file (ready only).
POST/api/v1/files/{file_id}/pinKeep the file beyond the TTL.
POST/api/v1/files/{file_id}/unpinRestore the TTL.
DELETE/api/v1/files/{file_id}Delete the row and stored bytes now. Returns 204.

Upload

POST /api/v1/files/redact, multipart/form-data:
Form fieldRequiredDescription
fileyesThe document. The part's declared content type must be one of the supported formats below.
glossary_idUUID of a persisted glossary you own; 404 if not found or not yours.
glossaryInline custom terms as a single string field: either a JSON array of strings (["Al-Noor, Riyadh Branch", "Project X"], lossless, terms may contain commas) or a plain comma-separated list.
Supported formats (checked against the declared content type, then against the file's magic bytes):
KindFormats
DocumentsPDF, DOCX, XLSX, PPTX, CSV
ImagesPNG, JPEG, TIFF (OCR'd)
Scanned PDF pages are OCR'd automatically, and PII inside raster images embedded in a PDF or Office document is redacted too.
Success returns 201 with:
FieldTypeDescription
file_idUUIDThe job id.
statestringAlways "queued" on upload.
created_atdatetimeUpload time (UTC).
expires_atdatetimeWhen the redacted output's TTL elapses (24 h after upload by default).
poll_urlstringRelative path to poll (/api/v1/files/{file_id}).

Job states

queued → processing → ready ↘ failed ready ─(TTL elapses, unpinned)→ expired
stateMeaning
queuedDurably enqueued; a worker will claim it.
processingA worker is extracting, masking, and reassembling.
readyRedacted and independently re-verified; download_url is set.
failedTerminal; error_code says why. No output is ever returned.
expiredThe TTL elapsed on an unpinned file: the redacted bytes are destroyed, the row is kept for a while so you get 410 instead of a bare 404.
Every output is re-extracted by an independent verifier before it is marked ready; a file that cannot be proven clean fails closed (redaction_incomplete / verify_failed) rather than being returned.

File metadata

GET /api/v1/files/{file_id} (and each row of the list response):
FieldTypeDescription
file_idUUIDJob id.
statestringSee the state table.
original_filenamestringAs uploaded ("untitled" if the part had no filename).
content_typestringThe declared upload content type.
size_bytes_inintegerUploaded size.
size_bytes_outinteger | nullRedacted output size; null until ready.
word_countinteger | nullWords extracted and masked; the billing basis.
entity_countinteger | nullEntities redacted.
created_atdatetimeUpload time.
processed_atdatetime | nullWhen processing finished.
expires_atdatetimeTTL boundary.
is_persistedbooleantrue while pinned (exempt from the TTL sweep).
error_codestring | nullSet when state is failed; see the error table.
download_urlstring | null/api/v1/files/{file_id}/download when ready; null otherwise.
poll_urlstringSelf link.

Listing and pagination

GET /api/v1/files?limit=50&cursor=<file_id> returns { "files": [...], "next_cursor": "<id>" | null }. limit is clamped to 1–50. Pass next_cursor back as cursor for strictly older rows; an unknown or malformed cursor returns 400 invalid_cursor.

Quotas and errors

At upload time:
StatusdetailTrigger
400unsupported_formatContent type not supported, or magic bytes don't match (corrupt, legacy binary Office, or password-protected file).
402insufficient_tokensRead-only balance pre-check failed: no free quota and no token balance. Nothing is enqueued or charged.
404Glossary not foundThe glossary_id isn't yours or doesn't exist.
413file_too_largeOver the 100 MB cap (rejected up front from Content-Length where possible, and mid-stream otherwise).
429too_many_concurrent_jobsMore than 2 jobs queued/processing at once.
429quota_activeMore than 50 files in queued/processing/ready states.
429too_many_files_todayThe 100-uploads-per-day cap (counted per Asia/Riyadh day, any state, resets at Riyadh midnight).
On download: 404 not_ready while the job hasn't reached ready, 410 expired after the TTL destroyed the bytes.

Retention, pinning, billing

  • TTL: redacted output lives 24 hours, then the janitor destroys the bytes (state becomes expired). Pin a file (up to 5 pinned) to keep it; unpin to restore the TTL. DELETE removes it immediately.
  • Billing: metered by the extracted word_count at the same per-token rate as the text endpoints, plus a small per-page surcharge for paged formats (10 tokens per PDF page or PPTX slide; a standalone image counts as one page). The real debit happens when the worker processes the job; a failed job nets to zero.
  • At rest: uploaded input and redacted output are encrypted at rest; the output is destroyed at TTL expiry.
Two deliberate differences from the text API: file redaction is checksum-lenient (OCR mangles digits; an identifier-shaped number is redacted even if its check digit fails), and your entity toggles do not apply; files always run the full entity set.