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
- Uploading a document
- Retrieving scans
- Listing templates
- Checking the balance
- Webhooks
- Error codes
Overview
- Base URL: https://app.dokuscan.com/api/v1
- Responses are JSON. Send the header Accept: application/json so that errors are returned as JSON too.
- Timestamps use the ISO 8601 format.
- Access is part of the Business package (from 2,000 scans).
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. |
- The URL must be a publicly reachable HTTPS address.
- DokuScan sends a POST with a JSON body and waits up to 10 seconds. Any 2xx status counts as success.
- After 10 consecutive failures the webhook is deactivated automatically.
- A secret is shown when you create the webhook. It is shown in full only once.
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.