API Reference

One REST endpoint family for turning documents into structured JSON. Upload a PDF, scan or image — get back typed fields with per-field confidence scores.

v2.3.039 endpointsBase: https://doxroute.com/api/v1JSON over HTTPS

Quickstart

From zero to your first extraction. Should take under five minutes.

  1. Create an account

    Sign up at doxroute.com/login.html, or straight from the API:

    # returns your user object and sets a session cookie
    curl -X POST https://doxroute.com/api/v1/auth/register \
      -H "Content-Type: application/json" \
      -d '{"email":"you@company.com","password":"YourPassword123","name":"You"}'
  2. Create an API key

    Go to Settings → API keys and click Create key. The full key (dxr_k1_…) is shown once — store it somewhere safe. Free accounts get 2 keys, Pro 10, Enterprise unlimited.

  3. Send your first document

    Leave doc_type out and DoXroute classifies the document itself and picks the matching schema.

    curl -X POST https://doxroute.com/api/v1/extract \
      -H "X-Api-Key: dxr_k1_YOUR_KEY" \
      -F "file=@invoice.pdf"
    
    # {"job_id":"a3f1...","status":"queued","filename":"invoice.pdf"}
  4. Poll for the result

    Most documents finish in about 3–5 seconds.

    curl https://doxroute.com/api/v1/jobs/a3f1... \
      -H "X-Api-Key: dxr_k1_YOUR_KEY"
    {
      "job_id": "a3f1...",
      "status": "completed",
      "result": {
        "invoice_number": "INV-2025-0042",
        "invoice_date": "2025-03-14",
        "currency": "EUR",
        "total": 12480.50,
        "shipper_name": "SCHMIDT GMBH",
        "incoterm": "CIF",
        "_confidence": { "overall": 0.97, "fields": { "total": 0.99 } },
        "_meta": {
          "schema_used": "invoice",
          "auto_classified": true,
          "extraction_mode": "vision",
          "extraction_time_ms": 3310
        }
      }
    }
  5. Skip the polling (optional)

    Register a webhook and DoXroute posts the result to your endpoint as soon as the job finishes, signed with HMAC-SHA256.

Authentication

Two mechanisms, same permissions. Every endpoint below requires one of them unless marked otherwise.

MethodUse it forHow
API keyServers, scripts, the SDKHeader X-Api-Key: dxr_k1_…
JWT cookieThe web dashboardSet automatically by /auth/login — HttpOnly, Secure, SameSite=Lax, 24h
Keys are hashed at rest. Only a short prefix is indexed for lookup, so a leaked database does not expose usable keys. Rotate a key by creating a new one and deleting the old.

Conventions

TopicBehaviour
Base URLhttps://doxroute.com/api/v1 — HTTPS only, HTTP redirects with 301
Uploadsmultipart/form-data. Max 50 MB per file
FormatsPDF, PNG, JPG, TIFF, DOCX, XLSX, PPTX
Responsesapplication/json, UTF-8
DatesNormalised to ISO 8601 (YYYY-MM-DD) in extraction output
AmountsJSON numbers, never strings. Currency in a separate field
TracingEvery response carries X-Request-ID; include it in support requests
Rate limit30 req/s per IP with a burst of 20, plus per-plan monthly quotas

Extraction

Four ways to submit documents. All accept an optional doc_type; omit it to let the classifier route the document.

POST/extractqueue a document, returns a job id
FieldTypeNotes
filefileRequired. The document
doc_typestringOptional. One of the document types. Empty = auto-classify
curl -X POST https://doxroute.com/api/v1/extract \
  -H "X-Api-Key: dxr_k1_YOUR_KEY" \
  -F "file=@bill_of_lading.pdf" -F "doc_type=bill_of_lading"
POST/extract/syncblocks until the result is ready

Same fields as /extract, but the response body is the extraction itself. Convenient for testing; use the async endpoint in production so a slow document cannot tie up your request.

POST/extract/batchmany files, one batch id

Repeat the files field once per document. Maximum 50 files per call; per-plan batch caps also apply. Returns {"batch_id", "job_ids", "count"}.

curl -X POST https://doxroute.com/api/v1/extract/batch \
  -H "X-Api-Key: dxr_k1_YOUR_KEY" \
  -F "files=@inv1.pdf" -F "files=@inv2.pdf" -F "files=@inv3.pdf"
POST/extract/splitsplit a multi-document PDF, then extract each part
FieldTypeNotes
filefileRequired. PDF only
split_methodstringauto (default), blank (blank-page separators), text
doc_typestringOptional, applied to every part

Returns {"split": true, "parts": n, "batch_id": "…"}, or a single job_id when no split is detected.

Document types

Seven trade-ready schemas ship with the product. Fetch the live list from GET /schemas (no authentication required).

doc_typeCovers
invoiceCommercial, proforma and shipping invoices, facturas
packing_listPacking lists and packing slips
bill_of_ladingB/L, sea waybills, conocimiento de embarque
delivery_noteDelivery notes, albaranes, bons de livraison
purchase_orderPurchase orders, órdenes de compra
customs_declarationDUA / SAD customs declarations
receiptTill receipts, tickets de compra
genericUniversal schema — used when nothing else fits
Automatic routing. When doc_type is omitted, the first page is classified before extraction and the matching schema is applied. The result reports _meta.schema_used and _meta.auto_classified. Measured at 100% on a 56-document, 7-type internal corpus.
GET/schemaslist document types and labels — public

Jobs

GET/jobslist your jobs

Query: status (queued · processing · completed · failed), limit (default 50).

GET/jobs/{job_id}status, result, confidence scores
GET/jobs/{job_id}/pdfthe original document
GET/jobs/{job_id}/bboxbounding boxes for visual review
GET/jobs/exportexport many jobs at once

Query: format (csv or xlsx), status, limit.

GET/queue/statsqueue depth and worker state
Jobs and their results are retained for 30 days, then purged automatically. Export anything you need to keep.

Batch

GET/batch/{batch_id}aggregate status of a batch

Returns per-job status plus counters for completed, failed and pending.

GET/batch/{batch_id}/exportall results in one file

Query: formatcsv (default) or xlsx.

Custom extractors

Describe the fields you need in plain language; DoXroute generates a reusable schema. Extractors are private to the account that created them.

POST/extractorsgenerate a schema from a description
FieldTypeNotes
namestringRequired
descriptionstringRequired, at least 10 characters. What to pull out of the document
sample_fieldsstringOptional comma-separated hints, e.g. policy_number,insured_name
curl -X POST https://doxroute.com/api/v1/extractors \
  -H "X-Api-Key: dxr_k1_YOUR_KEY" \
  -F "name=Marine insurance certificate" \
  -F "description=Extract policy number, insured party, vessel, voyage, sum insured and currency"
GET/extractorslist your extractors
GET/extractors/{extractor_id}full definition including the generated schema
DELETE/extractors/{extractor_id}delete an extractor

Webhooks

Get pushed a payload when a job finishes instead of polling.

POST/webhooksregister an endpoint
curl -X POST https://doxroute.com/api/v1/webhooks \
  -H "X-Api-Key: dxr_k1_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.com/hooks/doxroute","events":["job.completed","job.failed"]}'
GET/webhookslist endpoints
GET/webhooks/{webhook_id}one endpoint, with delivery stats
PATCH/webhooks/{webhook_id}enable or disable
DELETE/webhooks/{webhook_id}remove an endpoint

Verifying the signature

Each delivery carries X-DoXroute-Signature: the HMAC-SHA256 of the raw body, keyed with the secret returned when the webhook was created. Compare in constant time and reject anything that does not match.

import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)
Webhook URLs must be public HTTP(S). Private ranges, localhost, cloud metadata addresses and non-HTTP schemes are rejected at registration time.

Account & API keys

POST/auth/registercreate an account — public

Body: {"email", "password", "name"}. New accounts always start on the free plan.

POST/auth/loginstart a session — public

Body: {"email", "password"}. Five consecutive failures lock the account for five minutes and return 429.

POST/auth/logoutclear the session cookie
GET/auth/mecurrent user, plan and usage
POST/auth/api-keyscreate a key — body {"name"}
GET/auth/api-keyslist keys (prefixes only)
DELETE/auth/api-keys/{key_id}revoke a key immediately

Billing

POST/billing/checkoutstart a Stripe Checkout session

Body: {"plan": "pro"} or {"plan": "enterprise"}. Returns {"url"} — redirect the browser there.

GET/billing/portalStripe customer portal for self-service billing
POST/billing/webhookStripe callback — signature-verified, not for client use

Data export & deletion

GET/auth/exportmachine-readable copy of your data

Returns the account record, API key metadata, extractors, webhooks and job history as JSON.

DELETE/auth/accountdelete the account and all associated data
Deletion is immediate and irreversible: jobs, results, uploaded files, extractors, webhooks and API keys are all destroyed. Any active Stripe subscription should be cancelled first via the billing portal.

Admin

Restricted to accounts with the admin flag. Everyone else receives 403.

GET/admin/userslist users with plan and usage
PATCH/admin/users/{user_id}change plan or status
DELETE/admin/users/{user_id}delete a user
GET/admin/statsplatform-wide counters
GET/healthservice, queue and version — public

Plans & limits

 FreePro — €49/moEnterprise — €149/mo
Documents / month505,00025,000
Custom extractors350unlimited
API keys210unlimited
Files per batch5100200
Webhooksyesyes

Quotas reset on the first of each month. Exceeding the document quota returns 403 with an upgrade message; the request is not counted.

Errors

Conventional HTTP status codes. The body is always {"detail": "…"}.

CodeMeaningWhat to do
400Malformed request or unsupported fileCheck the field names and file type
401Missing, invalid or expired credentialsCheck the X-Api-Key header or log in again
403Plan limit reached, or not your resourceUpgrade, or verify the id belongs to your account
404Not found — also returned for resources you do not ownCheck the id
413File larger than 50 MBSplit or compress the document
429Rate limited, or login temporarily lockedBack off and retry; login locks clear after five minutes
500Processing failedRetry once, then send us the X-Request-ID
502Upstream model unavailableAutomatic failover usually handles this; retry

Failed jobs return status: "failed" with an error field rather than an HTTP error, since the request itself succeeded.

Python SDK

A thin wrapper over the REST API. Ask support for the package if you would rather not write HTTP calls.

from doxroute import DoXroute

client = DoXroute(api_key="dxr_k1_YOUR_KEY")

# one document, blocking until done
result = client.extract("invoice.pdf", wait=True)
print(result["total"], result["currency"])

# a folder, in one batch
batch = client.extract_batch(["a.pdf", "b.pdf", "c.pdf"])
client.wait_for_batch(batch["batch_id"])
client.export_batch(batch["batch_id"], format="xlsx", path="results.xlsx")
Questions, a schema that does not fit, or an on-premises deployment? Write to hello@doxroute.com.