Quickstart
Four calls get a document parsed: find a knowledge base, upload, parse, poll. Everything after that — extract, split, search — reuses the same conventions, so read this page once and the rest of the API follows.
Code
Base URL
Cloud is https://api.providus.ai. Every path below is relative to it.
On-prem deployments serve the same /v1 surface from their own host, so set
DOCAI_API to that instead. Nothing else in this page changes.
Authentication
Every request carries an API key in the x-api-key header:
Code
Keys are created by an organization admin in Settings → API Keys. Staff members
cannot create, list, rotate or revoke them. Keys are prefixed docai, and the
secret is shown once at creation.
A key is organization-root
This is the most important thing to understand before you put a key anywhere.
DocAI has a role-based access control system: staff members hold permissions
such as files.read or parse.run, plus per-resource grants on individual
knowledge bases and agents. An API key bypasses all of it.
The check is one line, in access.ts:
Code
Every permission check and every resource-access check returns true for a root context. In practice:
- The key can read and write every knowledge base in the organization, not the ones someone granted it.
- There is no read-only key and no knowledge-base-scoped key today.
- Anything holding the key — a CI job, an editor config, a teammate's laptop — has organization-wide access.
Treat a key as production access. If you need per-user restriction, use the
session-based path with x-docai-org-id rather than a key.
An API key is already bound to one organization. Sending x-docai-org-id or
?org_id= alongside it is allowed only if the value matches; a mismatch is
rejected with api_key_org_mismatch.
The happy path
1. Find a knowledge base
Documents live in knowledge bases. List the ones the key can reach:
Code
Code
POST /v1/knowledge-bases creates one if you have none.
2. Upload a document
Multipart, with the file in a field called file and the target knowledge base
in knowledge_base_id:
Code
Responds 201:
Code
Optional form fields: folder_id, filename to override the uploaded name,
metadata as a JSON string, and auto_parse=true to queue the parse in the
same call. With auto_parse, the response also carries parse_job,
parse_usage and credit_reservation, and you can skip straight to step 4.
Filenames must be unique within a knowledge base folder; a clash returns
duplicate_filename.
3. Parse it
Code
Responds 202 — the job is queued, not done:
Code
The body accepts options (passed through to the parser, for example
{"backend": "glm-ocr"}) and idempotency_key.
4. Poll the job
Code
Code
GET /v1/files/{fileId}/jobs lists every job on a file, and
GET /v1/files/{fileId}/jobs/{jobId}/events returns the event stream if you
want stage-level detail rather than a percentage.
5. Read the artifacts
When the job completes, list the file's artifact manifests:
Code
Code
A null link means the parse did not produce that artifact. Fetch a non-null
one directly:
Code
pages and imgs are directories. Fetching one without a query returns an
entry listing; add ?file=<name> to pull a single member.
For structured text with page coordinates rather than raw markdown,
GET /v1/files/{fileId}/segments is usually what you want.
Skip the loop
GET /v1/knowledge-bases/{kbId}/index returns every file in a knowledge base
with parse status and artifact links in one call, which is faster than calling
GET /v1/files and then fetching each file individually. Prefer the index
endpoint whenever you need more than one file's status.
Jobs and operations
Two different asynchronous shapes exist, and confusing them is the most common integration mistake.
| Job | Operation | |
|---|---|---|
| What runs | parse or extract on one file | a queued document workflow |
| Started by | POST /v1/files/{id}/parse, POST /v1/files/{id}/extract | POST /v1/extract/runs, POST /v1/split/runs, POST /v1/split/rules/suggest, POST /v1/classify/runs, POST /v1/edit/runs, POST /v1/extract/schemas/generate |
| Identifier | job_id | operation_id |
| Poll at | GET /v1/files/{fileId}/jobs/{jobId} | GET /v1/operations/{operationId} |
| Owned by | the processing plane | the control plane |
| Result | artifact manifests, fetched separately | output, inline in the poll response |
They even spell "in flight" differently, which is a useful tell when you are
staring at a response: a job reports running, an operation reports
in_progress.
Job statuses
status is normalised to five values. raw_status carries the unnormalised
value as stored, if you need it.
status | raw_status | Meaning |
|---|---|---|
queued | queued | Accepted, waiting for a worker to lease it |
running | started, in_progress | A worker has it; percent and completed_pages advance |
completed | completed | Artifacts are written and result is populated |
failed | failed | error explains why; the reservation is finalised as failed |
cancelled | cancelled | Cancellation was honoured |
cancel_requested_at is set the moment cancellation is requested, which can be
before the status becomes terminal. Deleting a file is blocked while a parse or
extract job is active.
Operation statuses
Operations report queued, in_progress, completed, failed or
cancelled — no normalisation, so in_progress appears literally.
Code
Code
output is null until status is completed. It is withheld on every
other status, including failed and cancelled, so a client that reads
output on its first poll sees null rather than an empty result. Keep polling.
POST /v1/operations/{operationId}/cancel requests cancellation. It is
cooperative — the run stops at its next checkpoint — and an already-terminal
operation is left as it is rather than erroring. The response shape matches the
GET.
Document workflows are slow by design. A split window takes tens of seconds at the default reasoning level, and a run covers several windows, so budget in minutes and poll rather than holding a request open.
Idempotency
Parse, extract and the queued workflows all accept an idempotency_key. If you
omit it, one is derived for you, so replay protection is on by default.
Parse. The default key is parse:{file_version_id}:{options_hash}. Sending
the same request again returns the existing job with "idempotent": true and
does not queue a second one or reserve a second lot of credits. Two exceptions
worth knowing:
- A job that ended
failedorcancelledis retryable. Without an explicit key, a repeat request starts a fresh attempt under a new derived key and returnsretry_of_job_idpointing at the previous one. - With an explicit
idempotency_key, that key is matched exactly, and a terminal job under it is returned as-is rather than retried. Pass a new key to force a retry.
Because the derived key includes the file version, uploading new content via
PUT /v1/files/{fileId}/content gives a new version and therefore a new key —
re-parsing after a content change is never suppressed.
Extract. The default key is derived from the file version plus hashes of the schema and options, so the same schema against the same version replays instead of re-running.
Queued workflows (/v1/extract/runs, /v1/split/runs,
/v1/split/rules/suggest, and the rest) behave slightly differently, because
the result lives on the operation:
- Replaying a key whose operation already completed returns
200with the output inline, not202. - Replaying while it is still queued or running re-attaches:
202with the sameoperation_id. - Replaying a failed or cancelled operation starts a fresh attempt and
charges again. The credit ledger records each attempt under its own
:attempt-Nkey, so the charges are traceable.
Errors
Every failure returns the same envelope:
Code
Branch on code. It is stable across releases. error is a human-readable
sentence and may be reworded at any time, so matching on it will break. Some
responses add a message field with extra detail; authentication failures
generally do.
Codes you will meet early:
| Status | Code | Cause |
|---|---|---|
| 401 | invalid_api_key | Key is wrong, revoked, or expired |
| 403 | api_key_org_mismatch | x-docai-org-id does not match the key's organization |
| 403 | permission_required | Session-based caller lacks a permission |
| 402 | insufficient_credits | Balance minus active reservations is below the estimate |
| 402 | monthly_quota_exceeded | The organization's monthly quota would be exceeded |
| 404 | file_not_found, job_not_found, operation_not_found | Wrong id, another organization's id, or soft-deleted |
| 409 | file_must_be_parsed_before_extraction | Extract was called on a file with no parse artifacts |
| 409 | duplicate_filename | A live file of that name exists in the same folder |
The full list is on the error codes page, generated from the API's own registry.
Credits
Parse is billed per page: one credit per estimated page. The flow is reserve-then-commit, not charge-on-request.
- Reserve. When the parse is queued, DocAI estimates the page count and
reserves that many credits against the organization's account. If the
available balance — balance minus everything already reserved — is short, the
request fails
402 insufficient_creditsbefore any work starts. A monthly quota, where one is set, is checked at the same point and fails402 monthly_quota_exceeded. - Commit. The processing plane reports pages as it finishes them, and each
one debits the ledger against the reservation. A page cannot debit beyond
what was reserved; that would fail
reserved_credits_exhausted. - Finalise. When the job reaches a terminal state the reservation is
closed:
committedif pages were actually billed,releasedif the job completed without billing any,failedorcancelledotherwise. A released or cancelled reservation stops holding balance.
That is why the parse response carries both usage (the estimate and your
account position) and credit_reservation (what is being held).
The queued workflows are billed differently — a fixed one credit each, debited after validation and before any model work.
Search costs nothing. POST /v1/knowledge-bases/{kbId}/search is a Postgres
full-text read over parsed evidence and calls no model, so search first and
read only what you need.
Check the account at any time:
Code
Code
total_used_credits is the lifetime debit total; monthly_used_credits counts
from the current billing period start. monthly_quota_credits is null when no
quota applies. GET /v1/billing/ledger returns the individual entries.
Where to next
- Parsing. What a parse produces, how to fetch each artifact, and how to trust the coordinates.
- Extraction.
POST /v1/files/{fileId}/extracttakes a schema and returns JSON shaped to it, with citations back to the source segments. - Knowledge bases and search. Organise documents, read a whole knowledge base in one call, and search parsed evidence for nothing.
- MCP server. The same API as tools inside Claude Code, Cursor and Codex.
- API reference. Every endpoint, request body and response.

