API Reference
v1 Home
// v1 · REST API

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.

Base URL https://dokr.ai/v1

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.

curlPOST /documents/submit
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"
}
01

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.

02

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.

+ Extraction always runs. Fields are always available via the API once processing completes, whether or not a workflow exists.
+ Delivery only happens through a published workflow. Nothing is posted to an ERP and nothing is filed to storage unless a workflow with an explicit destination has been published for that type. There is no default destination.

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.

03

Rate limits & quotas

LimitDefaultWhen exceeded
API requests300 / min per key429, retry after Retry-After
Document submissions60 / min per account429 submit_rate_too_high
Monthly documentsper plan429 monthly_document_limit
Monthly AI budgetper plan429 monthly_ai_budget
File size50 MB413

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.

04

Documents

The core resource - submit files, track them through the pipeline, and read what was extracted.

POST/documents/submit

multipart/form-data. Returns a document object with status: "RECEIVED".

FieldReqDescription
fileyesPDF, DOCX, or XLSX. Max 50 MB.
document_classnoForce a document type (skip auto-classification). Must be an existing class ID.
variant_keynoForce a specific document variant.
prioritynostandard (default) or express. Recorded on the document and returned in responses; it does not currently change queue order or carry a timing guarantee.
webhook_urlnoPer-document webhook override for status callbacks.
metadatanoJSON string of arbitrary key–value pairs, echoed back on the document.
skip_stagesnoComma-separated stages to skip: DEDUPLICATION, MATCHING, POSTING.
match_modenoREQUIRED (default), ADVISORY, or SKIP.
trainingnotrue - process as training: extraction runs, but it never delivers and is excluded from operational views.
submitter_emailnoEmail of the submitter (used for {submitter} email steps in workflows).
inbound_email_idnoLink 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"
}
GET/documents/{document_id}

Optional include=fields embeds extracted fields and tables in the same response - one round trip instead of two.

GET/documents/{document_id}/status

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

RECEIVED DEDUPLICATING CLASSIFYING EXTRACTING VALIDATING LINKING MATCHING POSTING FILING NOTIFYING COMPLETED

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).

GET/documents/{document_id}/fields

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
    }
  ]
}
PATCH/documents/{document_id}/fields/{field_name}/correct
{"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.

GET/documents/
ParameterDescription
statusFilter by pipeline status, e.g. COMPLETED, NEEDS_REVIEW.
document_classFilter by document type ID.
shipment_idDocuments linked to a shipment.
field_name + field_valueSearch by extracted value; field_value supports a trailing * wildcard.
date_from, date_toISO 8601 dates, inclusive.
page, page_sizeStandard paging (page_size 1–100, default 20).
afterCursor paging - pass the last document ID; exact under concurrent inserts.

Response: {total, page, page_size, pages, documents: [...], next_cursor}.

GET/documents/by-source?message_id={rfc5322-message-id}

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".

POST/documents/{document_id}/retry

Re-queues a FAILED document through the full pipeline (409 not_retryable if it isn't failed).

DELETE/documents/{document_id}

Removes the document and its pipeline history. If it's part of a split bundle, the whole bundle is deleted. Returns {"deleted": ["doc_...", ...]}.

05

Review queue

Documents that need a human land in NEEDS_REVIEW with the reasons attached.

GET/review/

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"
  ]
}
POST/review/{document_id}/approve
{"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.

POST/review/{document_id}/reject

Marks the document FAILED. Both endpoints return the action record (previous status, new status, who, when).

06

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.

EndpointKey fieldMax / request
POST/master-data/vendorsvendor_no5,000
POST/master-data/customerscustomer_no5,000
POST/master-data/itemsitem_no20,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}.

  • aliases help matching when documents use trading names.
  • extra is a flat dictionary of scalars (lowercase a-z0-9_ keys, max 40, values ≤ 200 chars). Every extra key becomes a workflow condition variable.
  • iban on 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 uom and vendor_item_no; customers are vendors minus IBAN / vendor_item_no.
07

Shipments

Dokr links related documents (PO, order confirmation, packing list, invoice…) into shipments by their reference keys, and runs match checks across them.

GET/shipments/

Filters: status (OPEN, MATCHED, POSTED, FILED, COMPLETE), match_result (PASS, PASS_PARTIAL, FAIL, or null), reference_key, date_from/date_to, paging.

GET/shipments/{shipment_id}
{
  "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.

08

Email capture

When documents arrive by email, capture the email first so Dokr can link every attachment back to its source thread.

POST/emails/capture
{
  "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.

09

Webhooks

Get pushed when things happen instead of polling.

POST/webhooks/
{
  "url": "https://portal.yourcompany.com/hooks/dokr",
  "events": ["document.completed", "document.needs_review", "match.fail"],
  "secret_key": "whsec_your_secret",
  "description": "Finance portal integration"
}
EventFires when
document.completedA document reaches COMPLETED.
document.needs_reviewA document routes to review.
document.failedA document reaches FAILED.
match.pass / match.failA shipment's cross-document match passes / fails.
shipment.completeEvery 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).

10

Document types & variants

GET/document_classes/
{
  "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:

ZERO_SHOT LEARNING LEARNED OPTIMISED

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.

11

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.

GET/documents/discovery/

The queue, each item carrying the classifier's confidence, the suggested new type name, and the reasoning.

POST/documents/{id}/promote-class

Accept the suggestion (optionally overriding confirmed_class_name / confirmed_class_slug): creates the type and re-queues the document for extraction.

POST/documents/{id}/dismiss-discovery

Decline; the document routes to review for manual handling.

12

Instructions - per-type rules

Lightweight conditional rules attached to a document type - a simpler alternative to canvas workflow logic for one-off policies.

POST/instructions/
{
  "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.

13

Event stream

GET/events

A replayable, ordered stream of every pipeline event on your account. Ideal for your own monitoring or syncing state into a data warehouse.

ParameterDescription
after_idEvents with ID greater than this (default 0 = from the start).
limitBatch size, 1–200 (default 50).
document_id, stateOptional 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.

14

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.

GET/health

Unauthenticated: {"status": "ok", "product": "Dokr", "version": "1.0.0"}.

15

Error reference

Errors return standard HTTP codes with a JSON body describing the problem.

CodeMeaning
401Missing, malformed, or invalid API key; deactivated account.
402The feature isn't on your plan.
403Request IP not on your account's allowlist.
404Resource not found (cross-account IDs always 404).
409Conflict: exact duplicate, extraction not yet complete, document not in the required state.
413File exceeds 50 MB.
422Invalid parameter value (the body says which).
429Rate/quota: rate_limited, submit_rate_too_high, monthly_document_limit, monthly_ai_budget. Back off per Retry-After.
5xxOur fault. Safe to retry submits with the same Idempotency-Key.
16

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.

Questions or issues: support@dokr.ai · Describes API v1 as of July 2026.