Dokr API Reference
Dokr turns trade documents - invoices, purchase orders, packing lists, certificates, bills of lading - into structured data, runs them through your workflows, and delivers the results to your ERP and document storage. This reference covers the full v1 REST API.
Your first document
Submit a file with a single request. Processing is asynchronous - you get an ID back immediately, then poll status or register a webhook.
curl -X POST https://dokr.ai/v1/documents/submit \ -H "Authorization: Bearer dk_live_..." \ -F "file=@invoice.pdf"
Response - immediate, processing continues in the background:
{
"id": "doc_01K5R9WQ3ZB7YXN",
"status": "RECEIVED",
"file_name": "invoice.pdf",
"priority": "standard",
"created_at": "2026-07-07T13:22:05.123Z"
}
Authentication
Every request needs your API key in the Authorization header:
Authorization: Bearer dk_live_your_key_here
Keys are issued per account and start with dk_live_ (production) or dk_test_ (testing). View and rotate your key in the dashboard under Settings. Keys are stored hashed - if you lose yours, rotate it; we cannot recover it.
Requests without a valid key return 401. If your account has an IP allowlist configured, requests from other addresses return 403.
The processing model
A submitted document is deduplicated, classified into a document type, and its fields extracted and validated. What happens after extraction is governed entirely by the workflow published for that document type on the canvas in your dashboard.
So you can use Dokr purely as an extraction API (submit → read fields), or as a full straight-through-processing pipeline (submit → workflow logic → ERP/storage), per document type.
Rate limits & quotas
| Limit | Default | When exceeded |
|---|---|---|
| API requests | 300 / min per key | 429, retry after Retry-After |
| Document submissions | 60 / min per account | 429 submit_rate_too_high |
| Monthly documents | per plan | 429 monthly_document_limit |
| Monthly AI budget | per plan | 429 monthly_ai_budget |
| File size | 50 MB | 413 |
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; 429 responses add Retry-After. The window is one minute - back off and retry.
Idempotent retries
POST /documents/submit accepts an optional Idempotency-Key header (up to 255 chars). Retrying with the same key returns the original result instead of creating a duplicate. Derive the key from your source system's stable identifier - e.g. email message ID + attachment ID.
Documents
The core resource - submit files, track them through the pipeline, and read what was extracted.
multipart/form-data. Returns a document object with status: "RECEIVED".
| Field | Req | Description |
|---|---|---|
file | yes | PDF, DOCX, or XLSX. Max 50 MB. |
document_class | no | Force a document type (skip auto-classification). Must be an existing class ID. |
variant_key | no | Force a specific document variant. |
priority | no | standard (default) or express. Recorded on the document and returned in responses; it does not currently change queue order or carry a timing guarantee. |
webhook_url | no | Per-document webhook override for status callbacks. |
metadata | no | JSON string of arbitrary key–value pairs, echoed back on the document. |
skip_stages | no | Comma-separated stages to skip: DEDUPLICATION, MATCHING, POSTING. |
match_mode | no | REQUIRED (default), ADVISORY, or SKIP. |
training | no | true - process as training: extraction runs, but it never delivers and is excluded from operational views. |
submitter_email | no | Email of the submitter (used for {submitter} email steps in workflows). |
inbound_email_id | no | Link to a captured email (see Email capture). |
Headers: Idempotency-Key (recommended). Errors: 409 exact duplicate (includes original_document_id), 413 too large, 422 invalid value, 429 rate/quota.
The document object
{
"id": "doc_01K5R9WQ3ZB7YXN",
"status": "COMPLETED",
"document_class": "dc_006",
"document_class_name": "Supplier Invoice",
"variant_key": "meridian__invoice_layout_b",
"file_name": "invoice.pdf",
"file_size_bytes": 318744,
"file_format": "pdf",
"priority": "standard",
"metadata": {},
"skip_stages": [],
"match_mode": "REQUIRED",
"shipment_id": "ship_...",
"is_amendment": false,
"original_document_id": null,
"error_reason": null,
"pages_total": 3,
"classification_confidence": 0.96,
"created_at": "2026-07-07T13:22:05.123Z",
"updated_at": "2026-07-07T13:24:41.902Z"
}
Optional include=fields embeds extracted fields and tables in the same response - one round trip instead of two.
Current status plus the full event timeline - every stage, the agent that ran it, and a human-readable detail line.
{
"id": "doc_...",
"status": "EXTRACTING",
"pipeline": [
{"state": "RECEIVED", "agent": "IngestionAgent",
"detail": "Document received. 318,744 bytes.", "timestamp": "..."},
{"state": "CLASSIFYING", "agent": "ClassificationAgent",
"detail": "Matched: Supplier Invoice (confidence 0.96)", "timestamp": "..."}
]
}
Document lifecycle
Any stage can divert to NEEDS_REVIEW (review gates, failed matches, low confidence, bank-detail mismatches). Terminal states: COMPLETED, FAILED, NEEDS_REVIEW (until actioned), EXACT_DUPLICATE, CANDIDATE_NEW_CLASS (see Discovery), and SPLIT (a bundle split into parts, each processing independently).
Available once extraction completes (409 extraction_not_complete before that). Every field with confidence, provenance, and position, plus extracted line-item tables.
{
"document_id": "doc_...",
"document_class_name": "Supplier Invoice",
"field_count": 21,
"avg_confidence": 0.93,
"fields": [
{
"field_name": "invoice_number",
"field_value": "INV-8842-117",
"confidence": 0.98,
"human_corrected": false,
"corrected_value": null,
"used_in_match": true,
"match_result": "pass",
"page": 1,
"bbox": [0.1, 0.2, 0.9, 0.25]
}
],
"tables": [
{
"table_name": "line_items",
"columns": ["item_code", "quantity", "unit_price", "total"],
"rows": [{"item_code": "CMP-4471", "quantity": 10}],
"row_count": 12,
"confidence": 0.87
}
]
}
{"corrected_value": "INV-8842-118", "corrected_by": "ops@yourcompany.com"}
Corrections feed the learning loop: the variant records the confirmation, and repeated corrections improve extraction for future documents of the same shape. The response includes the variant's learning stage before and after.
| Parameter | Description |
|---|---|
status | Filter by pipeline status, e.g. COMPLETED, NEEDS_REVIEW. |
document_class | Filter by document type ID. |
shipment_id | Documents linked to a shipment. |
field_name + field_value | Search by extracted value; field_value supports a trailing * wildcard. |
date_from, date_to | ISO 8601 dates, inclusive. |
page, page_size | Standard paging (page_size 1–100, default 20). |
after | Cursor paging - pass the last document ID; exact under concurrent inserts. |
Response: {total, page, page_size, pages, documents: [...], next_cursor}.
Given an email's internetMessageId, returns whether it was captured and every document submitted from it, with status and ERP reference. This is how the Outlook add-in shows "already processed".
Re-queues a FAILED document through the full pipeline (409 not_retryable if it isn't failed).
Removes the document and its pipeline history. If it's part of a split bundle, the whole bundle is deleted. Returns {"deleted": ["doc_...", ...]}.
Review queue
Documents that need a human land in NEEDS_REVIEW with the reasons attached.
List, filterable by document_class, paged. Each item carries nigo_conditions - the plain-English reasons it stopped.
{
"id": "doc_...",
"nigo_conditions": [
"Invoice amount exceeds PO by 5% (tolerance 2%)",
"Receiving report missing"
]
}
{"target_stage": "MATCHING", "approved_by": "ops@yourcompany.com", "note": "Fixed the invoice date"}
Re-queues at the chosen stage: EXTRACTING, VALIDATING, MATCHING, POSTING, or COMPLETED.
Marks the document FAILED. Both endpoints return the action record (previous status, new status, who, when).
Master data
Sync your vendor, customer, and item masters so Dokr can match extracted values, verify bank details, and evaluate workflow conditions (e.g. customer credit_limit ≥ 5000) without live ERP calls. If you've connected Business Central, Dokr syncs these automatically - these endpoints are for direct integrations.
| Endpoint | Key field | Max / request |
|---|---|---|
POST/master-data/vendors | vendor_no | 5,000 |
POST/master-data/customers | customer_no | 5,000 |
POST/master-data/items | item_no | 20,000 |
Bulk upsert on POST, list on GET. Example (vendors):
[
{
"vendor_no": "V-3391",
"name": "Brightwater Components Ltd",
"vat_number": "GB447201953",
"iban": "GB71BARC20031874062178",
"country": "GB",
"aliases": ["Brightwater", "BWC"],
"active": true,
"extra": {"credit_limit": 75000, "payment_terms": "net30", "blocked": false}
}
]
Response: {"created": 5, "updated": 3, "ignored_over_cap": 0, "total": 1200}.
aliaseshelp matching when documents use trading names.extrais a flat dictionary of scalars (lowercasea-z0-9_keys, max 40, values ≤ 200 chars). Everyextrakey becomes a workflow condition variable.ibanon vendors powers the bank-detail guard: an invoice whose IBAN doesn't match routes to review.- Upserts are keyed on the number field. Items also take
uomandvendor_item_no; customers are vendors minus IBAN /vendor_item_no.
Shipments
Dokr links related documents (PO, order confirmation, packing list, invoice…) into shipments by their reference keys, and runs match checks across them.
Filters: status (OPEN, MATCHED, POSTED, FILED, COMPLETE), match_result (PASS, PASS_PARTIAL, FAIL, or null), reference_key, date_from/date_to, paging.
{
"reference_key": "PO/2026/5583",
"match_checks": [
{"name": "PO/Invoice Quantity Match", "status": "PASS",
"detail": "All quantities match within 2% tolerance"},
{"name": "PO/Invoice Amount Match", "status": "FAIL",
"detail": "PO total: £62,400 vs Invoice: £63,180"}
],
"match_summary": "PASS_PARTIAL - 6/7 checks passed"
}
GET /shipments/{shipment_id}/documents returns the documents in the shipment.
Email capture
When documents arrive by email, capture the email first so Dokr can link every attachment back to its source thread.
{
"message_id": "CAH8x-Qm42k9@mail.example.com",
"subject": "Invoice for approval",
"sender": "vendor@example.com",
"received_at": "2026-07-07T12:30:00Z",
"body_text": "Please process...",
"raw_eml_base64": null
}
Returns {"id": "em_7fq2m9", "deduped": false, "status": "captured"}. Idempotent on message_id. raw_eml_base64 (optional, max 15 MB) stores the original email for archiving. Then pass the returned id as inbound_email_id when submitting each attachment - the Outlook add-in does all of this automatically.
Webhooks
Get pushed when things happen instead of polling.
{
"url": "https://portal.yourcompany.com/hooks/dokr",
"events": ["document.completed", "document.needs_review", "match.fail"],
"secret_key": "whsec_your_secret",
"description": "Finance portal integration"
}
| Event | Fires when |
|---|---|
document.completed | A document reaches COMPLETED. |
document.needs_review | A document routes to review. |
document.failed | A document reaches FAILED. |
match.pass / match.fail | A shipment's cross-document match passes / fails. |
shipment.complete | Every document in a shipment is terminal. |
An empty events list subscribes to everything. If you set a secret_key, every delivery is signed: X-Dokr-Signature: sha256=<hex(HMAC-SHA256(secret, raw_body))>. Compute the HMAC over the raw body and compare. Deliveries time out after 5 seconds - respond 2xx quickly and process asynchronously.
Manage with GET /webhooks/, GET/PATCH/DELETE /webhooks/{id}, and test with POST /webhooks/{id}/test (payloads carry event: "webhook.test" and don't count toward delivery stats).
Document types & variants
{
"classes": [
{"id": "dc_006", "name": "Supplier Invoice", "slug": "supplier_invoice",
"treatment": "PROCESS", "active": true, "variant_count": 12}
]
}
treatment is how the type is handled: PROCESS (extract and run workflow), STORE (archive only), STORE_AND_FORWARD, or GENERATED (system output). GET /document_classes/{id} includes the type's variants.
Variants are the distinct layouts Dokr has seen within a type - one vendor's invoice format is one variant. Each learns independently:
GET /variants/ lists them with learning_stage, confirmed_instance_count, avg_confidence, and touchless_rate. GET /variants/{id}/documents lists the documents processed under a variant.
Discovery - unknown types
When a document doesn't fit any known type, it parks as CANDIDATE_NEW_CLASS instead of being forced into a bad match.
The queue, each item carrying the classifier's confidence, the suggested new type name, and the reasoning.
Accept the suggestion (optionally overriding confirmed_class_name / confirmed_class_slug): creates the type and re-queues the document for extraction.
Decline; the document routes to review for manual handling.
Instructions - per-type rules
Lightweight conditional rules attached to a document type - a simpler alternative to canvas workflow logic for one-off policies.
{
"document_class_id": "dc_006",
"condition_field": "total_amount",
"condition_operator": "gt",
"condition_value": "50000",
"action": "REQUIRE_APPROVAL",
"action_value": "finance@yourcompany.com",
"description": "Large invoices need sign-off"
}
Operators: eq ne lt le gt ge contains in. Actions: REQUIRE_APPROVAL, SKIP_POSTING, SKIP_MATCHING, FLAG_WARNING, NOTIFY_EMAIL. Omit the condition fields for an unconditional rule. Standard GET/PATCH/DELETE on /instructions/{id}, plus POST /instructions/{id}/test with {"document_id": "doc_..."} to dry-run against a real document.
For anything beyond a single condition - branching, lookups, multi-destination delivery, approval chains - use the workflow canvas.
Event stream
A replayable, ordered stream of every pipeline event on your account. Ideal for your own monitoring or syncing state into a data warehouse.
| Parameter | Description |
|---|---|
after_id | Events with ID greater than this (default 0 = from the start). |
limit | Batch size, 1–200 (default 50). |
document_id, state | Optional filters. |
Response: {events: [...], next_after_id, has_more}. Store the highest id you've processed and pass it as after_id next time - you'll never skip or double-process an event, even across restarts.
Agents & system
GET /agents/ lists the background agents (classification, matching, etc.) with their last run; POST /agents/{name}/run triggers one on demand (async - returns a run_id); GET /agents/runs and GET /agents/runs/{run_id} give run history and results.
Unauthenticated: {"status": "ok", "product": "Dokr", "version": "1.0.0"}.
Error reference
Errors return standard HTTP codes with a JSON body describing the problem.
| Code | Meaning |
|---|---|
401 | Missing, malformed, or invalid API key; deactivated account. |
402 | The feature isn't on your plan. |
403 | Request IP not on your account's allowlist. |
404 | Resource not found (cross-account IDs always 404). |
409 | Conflict: exact duplicate, extraction not yet complete, document not in the required state. |
413 | File exceeds 50 MB. |
422 | Invalid parameter value (the body says which). |
429 | Rate/quota: rate_limited, submit_rate_too_high, monthly_document_limit, monthly_ai_budget. Back off per Retry-After. |
5xx | Our fault. Safe to retry submits with the same Idempotency-Key. |
Integration recipes
Extraction only
Submit with delivery stages skipped, read the fields, keep everything else in your stack.
curl -X POST https://dokr.ai/v1/documents/submit \
-H "Authorization: Bearer dk_live_..." \
-F "file=@invoice.pdf" -F "skip_stages=MATCHING,POSTING"
# poll until COMPLETED, then:
curl https://dokr.ai/v1/documents/{id}?include=fields \
-H "Authorization: Bearer dk_live_..."
Email-driven intake
Capture the email, submit each attachment with an idempotency key, link them.
POST /v1/emails/capture → em_7fq2m9
POST /v1/documents/submit (inbound_email_id=em_7fq2m9,
Idempotency-Key: <msgid>-<attachment>)
GET /v1/documents/by-source?message_id=… → status per attachment
Straight-through processing
Publish a workflow on the canvas (lookups → guards → branch → ERP destination with field mapping), sync your masters, submit documents, and subscribe to document.needs_review + document.completed. Documents that pass your rules post themselves; the rest arrive in the review queue with reasons attached.