Start signing rounds from your own systems
The DOXIO REST API runs your templates with data, tells you what happens through signed webhooks, and hands you the signed PDF back. Read access is available from Tým; starting runs and webhooks from Kancelář.
Quick start
1. Create a template and a key
Save a document as a template in the app, then create an API key in Settings → API. The key is shown once.
2. Start a run
POST the recipients and prefill values to the template. The organization signs automatically when the template says so; everyone else gets an e-mail.
3. Listen and download
Register a webhook for run.completed and download the signed PDF from the run.
Authentication
Send the key as a bearer token on every request. Keys belong to the organization; revoke them any time in Settings → API. Never put a key in browser code.
curl -X POST https://doxio.pro/api/v1/templates/TEMPLATE_ID/runs \
-H "Authorization: Bearer $DOXIO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-0001" \
-d '{
"recipients": { "client": { "email": "jana@example.com", "name": "Jana Nováková", "locale": "cs" } },
"prefill": { "contract_no": "2026-0001" },
"external_id": "ORDER-2026-0001"
}'Endpoints
https://doxio.pro/api/v1
| Method | Path | What it does |
|---|---|---|
| GET | /templates | List your active templates with their slots and fields |
| GET | /templates/{id} | One template |
| POST | /templates/{id}/runs | Start a signing round from a template |
| GET | /runs | List runs (50 per page, filter by external_id and status) |
| GET | /runs/{id} | One run with every signer's status |
| POST | /runs/{id}/cancel | Cancel a run: every open signing link stops working |
| GET | /runs/{id}/signed.pdf | Download the signed PDF of a completed run |
| GET | /runs/{id}/certificate.pdf | Download the separate certificate (separate certificate mode only) |
GET /templates
List your active templates with their slots and fields
GET /templates/{id}
One template
POST /templates/{id}/runs
Start a signing round from a template
GET /runs
List runs (50 per page, filter by external_id and status)
GET /runs/{id}
One run with every signer's status
POST /runs/{id}/cancel
Cancel a run: every open signing link stops working
GET /runs/{id}/signed.pdf
Download the signed PDF of a completed run
GET /runs/{id}/certificate.pdf
Download the separate certificate (separate certificate mode only)
curl https://doxio.pro/api/v1/runs/RUN_ID \
-H "Authorization: Bearer $DOXIO_API_KEY"
curl -o signed.pdf https://doxio.pro/api/v1/runs/RUN_ID/signed.pdf \
-H "Authorization: Bearer $DOXIO_API_KEY"Errors
Errors are JSON: an error code, plus issues for invalid input or field_errors when the data does not fit the template. Every response is marked no-store.
- 400 invalid_input
- 400 invalid_json
- 400 invalid_idempotency_key
- 400 invalid_cursor
- 401 unauthorized
- 402 plan_required
- 402 billing_inactive
- 403 forbidden
- 404 not_found
- 409 org_signature_not_configured
- 409 mandate_required
- 409 quota_exceeded
- 409 template_unavailable
- 409 idempotency_in_progress
- 409 idempotency_conflict
- 409 run_completed
- 409 run_completing
- 409 run_not_cancellable
- 413 payload_too_large
- 415 unsupported_media_type
- 422 invalid_fields
- 429 rate_limited
- 500 internal_error
Idempotency
Send an Idempotency-Key header when starting a run. Repeating the same request with the same key within 24 hours returns the original run (200) instead of creating a second one; the same key with a different body is refused with 409 idempotency_conflict.
Webhooks
Add an https endpoint in Settings → API. DOXIO POSTs a JSON event with the current run; failed deliveries are retried after 1 min, 5 min, 30 min, 2 h, 12 h and 24 h. An endpoint that fails 50 times in a row is disabled and the owner is e-mailed.
Events
- run.signer_signed
- A signer signed (including the automatic organization signature).
- run.completed
- Every signer signed; the signed PDF is ready.
- run.declined
- A signer declined; the run is over.
- run.cancelled
- The run was cancelled through the API.
- run.expired
- A signer's link lapsed, so the round cannot finish on its own.
- run.org_signature_failed
- The automatic organization signature could not be applied; DOXIO retries daily.
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: doxio-webhooks/1
X-Doxio-Event: run.completed
X-Doxio-Delivery: 7b0c… (unique per delivery — deduplicate on it)
X-Doxio-Timestamp: 1790000000 (unix seconds)
X-Doxio-Signature: v1=<hex HMAC-SHA256(secret, timestamp + "." + body)>Verifying a webhook
Compute HMAC-SHA256 over the timestamp, a dot and the raw request body with your endpoint secret, compare it in constant time with the v1 value of X-Doxio-Signature, and reject timestamps older than 5 minutes. Deduplicate on X-Doxio-Delivery.
const { createHmac, timingSafeEqual } = require("node:crypto");
// rawBody: the request body exactly as received (a string, BEFORE JSON.parse).
function verifyDoxioWebhook(rawBody, headers, secret, nowMs = Date.now()) {
const timestamp = String(headers["x-doxio-timestamp"] ?? "");
if (!/^\d+$/.test(timestamp) || Math.abs(nowMs / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret).update(timestamp + "." + rawBody).digest();
return String(headers["x-doxio-signature"] ?? "")
.split(",")
.some((part) => {
const match = /^\s*v1=([0-9a-f]{64})\s*$/.exec(part);
return match !== null && timingSafeEqual(Buffer.from(match[1], "hex"), expected);
});
}Worked example
secret = whk_test_secret_0123456789abcdefghijklmnopqrstu
timestamp = 1790000000
body = {"id":"evt_1","type":"run.completed","created_at":"2026-09-28T12:00:00.000Z","data":{"run":{"id":"r1"}}}
signature = v1=54849a4be185576b3f3670eb41e9005d41f751baa60c65028ebf4e4af63a5a01Limits
- 120 requests per minute per key and 600 per minute per organization (429 with Retry-After).
- Request bodies up to 256 KB, JSON only.
- Up to 10 active keys and 5 webhook endpoints per organization.
- Webhook deliveries are kept for 30 days. Webhooks never carry files.