API documentation

With the DokuScan API you upload documents programmatically, retrieve the results and get notified by webhook. Access is part of the Business package.

Contents


Overview


Authentication

Create API keys in the application under "API & Webhooks" (up to 5 keys). A key is shown in full only when it is created. Send it with every request as a bearer token or in the X-API-Key header.

curl https://app.dokuscan.com/api/v1/account \
  -H "Authorization: Bearer dsk_xxxxxxxx" \
  -H "Accept: application/json"

If the key is missing or invalid the API responds with 401. If the account does not have the Business package it responds with 403.


Uploading a document

POST /scans creates a scan. Processing runs in the background. Retrieve the result with GET /scans/{id} or receive it by webhook.

Parameter Description
template_id (required) ID of the template, see GET /templates.
file (required) File as multipart/form-data. PDF, JPG, JPEG or PNG, up to 20 MB.
curl -X POST https://app.dokuscan.com/api/v1/scans \
  -H "Authorization: Bearer dsk_xxxxxxxx" \
  -H "Accept: application/json" \
  -F "template_id=12" \
  -F "file=@lieferschein.pdf"

Response on success (201):

{
  "id": 4711,
  "status": "uploaded",
  "template_id": 12,
  "filename": "lieferschein.pdf",
  "created_at": "2026-10-18T09:15:00+00:00"
}

A scan uses one scan credit. Multi-page PDFs are not split via the API; the file counts as one scan.


Retrieving scans

GET /scans/{id} returns one scan. GET /scans returns a list, newest first, 25 entries per page. Filters: status, template_id. Page: page.

{
  "id": 4711,
  "status": "completed",
  "filename": "lieferschein.pdf",
  "template_id": 12,
  "confidence": 92,
  "extracted_data": {
    "lieferant": "Baustoffe Huber GmbH",
    "datum": "12.10.2026",
    "rows": [
      { "artikel": "Zement CEM II 25 kg", "menge": 40, "einheit": "Sack" },
      { "artikel": "Kantholz 8x10 cm", "menge": 24, "einheit": "Stk" }
    ]
  },
  "reviewed_data": null,
  "validation_errors": null,
  "is_partial": false,
  "error_message": null,
  "created_at": "2026-10-18T09:15:00+00:00",
  "updated_at": "2026-10-18T09:15:21+00:00"
}
Status Meaning
uploaded Uploaded, waiting for processing.
processing Being processed.
completed Done, extracted_data is filled.
validation_failed Done, but at least one validation rule was violated. The data is available, details are in validation_errors.
failed Failed, the credit is returned. error_message gives the reason.

The structure of extracted_data follows the fields of the template: single values are under the field key, table rows as a list under rows or under the key of the table field. reviewed_data contains manually corrected values if a scan was reviewed. confidence is a value from 0 to 100, is_partial marks a partial result.


Listing templates

GET /templates returns the active templates of the account. extraction_mode is scalar, tabular, mixed or vision.

{
  "data": [
    {
      "id": 12,
      "name": "Lieferschein Huber",
      "extraction_mode": "mixed",
      "usage_count": 58,
      "created_at": "2026-09-20T10:02:11.000000Z"
    }
  ]
}

Checking the balance

GET /account returns the remaining scan balance.

{
  "remaining_scans": 1842,
  "total_scans": 2000,
  "used_scans": 158
}

Webhooks

Webhooks notify your application as soon as a scan is finished or fails. You can create them in the application under "API & Webhooks" or via the API: GET /webhooks, POST /webhooks (name, url, events), DELETE /webhooks/{id} and GET /webhooks/{id}/logs.

Event When
scan.completed A scan was processed (status completed or validation_failed).
scan.failed Processing failed.

Example body for scan.completed:

{
  "event": "scan.completed",
  "timestamp": "2026-10-18T09:15:21+00:00",
  "data": {
    "scan_id": 4711,
    "filename": "lieferschein.pdf",
    "template_id": 12,
    "status": "completed",
    "confidence": 92,
    "extracted_data": { "lieferant": "Baustoffe Huber GmbH", "rows": [] },
    "validation_errors": [],
    "is_partial": false
  }
}

Verifying the signature

Every request carries the headers X-DokuScan-Event and X-DokuScan-Signature. The signature is sha256= plus the HMAC-SHA256 of the raw request body with your secret. Verify it before processing the content.

// PHP
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_DOKUSCAN_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}

// Node.js (Express, raw body)
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.get('X-DokuScan-Signature') || ''));

Error codes

Code Meaning
401 API key missing or invalid.
402 Not enough scan credit.
403 The account has no API access (Business package).
404 Scan or template not found.
409 This file was already uploaded with this template.
422 Invalid parameters. The response names the affected fields.
429 More than 200 scans in the last hour.
500 Internal error. Retry the request later.

Errors have the form {"error": "…"}. For 404 and 422 the response contains the field message, and for 422 additionally errors.


Go to the Help Center · View pricing

Updated: October 2026