Dazr Sign API: developer docs
Prepare and send documents for signature from your own software, in the name of the people who use it. Signers sign with Dazr Sign as usual: signed in with Dazr Identity, in turn, once.
Quick start
- Enable the Dazr Sign API. In the Sign portal, open Developers and choose Enable Dazr Sign API for your app. Every app of your organisation is listed there and in the Dazr Identity console, with the same client ID and secret; each API is enabled on its own. You can also register a new app there.
- Ask for the
signscope. Send people through the usual Sign in with Dazr Identity flow withscope=openid sign(addoffline_accessfor a refresh token). The consent screen says your app may prepare and send documents for signature in their name, and see their status. This works with the Dazr Sign API alone; asking for people’s data too (profile,emailand the others) needs the Dazr Identity API enabled as well. - Call the API with the access token. The base address is
https://sign.dazr.eu/api/sign/v1. SendAuthorization: Bearer <access token>and JSON. - Create and send a document. Upload a PDF with text tags and a list of recipients, then send it, or open the prepare popup so the person places the fields.
// Node 18+: create a document from a PDF with text tags, then send it
import fs from 'node:fs';
const api = 'https://sign.dazr.eu/api/sign/v1';
const headers = { Authorization: 'Bearer ' + accessToken, 'Content-Type': 'application/json' };
let res = await fetch(api + '/documents', {
method: 'POST',
headers: { ...headers, 'Idempotency-Key': 'order-1042' },
body: JSON.stringify({
title: 'Service agreement',
file: fs.readFileSync('agreement.pdf').toString('base64'),
recipients: [
{ name: 'Alex Example', email: 'alex@example.eu' },
{ name: 'Sam Client', email: 'sam@client.example' },
],
}),
});
const { document } = await res.json();
res = await fetch(api + '/documents/' + document.id + '/send', { method: 'POST', headers });
console.log((await res.json()).document.status); // "sent"
Text tags
Type the fields into the PDF as text. Each tag is replaced by a field of the same size or larger, and the tag text is painted over, so signers never see it.
| Tag | Field |
|---|---|
{{signature:1}} | Signature of the first recipient |
{{initials:2}} | Initials of the second recipient |
{{name:1}} {{date:1}} | Name and the date of signing, filled in by Dazr Sign |
{{text:2:Company}} | A text field with the label Company |
{{checkbox:2:Terms:optional}} | An optional checkbox |
{{company:1}} | Company name (filled in when the person signs on behalf of an organisation) |
The number is the recipient’s position in recipients, starting at 1. A tag for a recipient you did not list adds an empty recipient, to fill in in the prepare popup. Send "text_tags": false to ignore tags. You can also send fields with positions in points (1/72 inch) from the top-left corner of the page: { "recipient": 1, "type": "signature", "page": 1, "x": 72, "y": 600, "width": 180, "height": 48 }. A value on a text, company or checkbox field fixes it: the signer sees it and cannot change it.
Popups: prepare and sign
Create a draft with "prepare": { "origin": "https://app.example.eu" } and you get a prepare_url. Open it in a popup: the person places recipients and fields in a compact editor, then clicks Send or Save as template. For a signer whose turn it is, POST /documents/{id}/recipients/{recipient_id}/signing_url gives a signing popup. Both links are valid for 10 to 15 minutes and work once. The person signs in with Dazr Identity inside the popup if needed, as the address the document was sent to.
When it is done, the popup sends { type: 'dazr-sign', event, document_id, template_id, recipient_id, state } with postMessage to the page that opened it, only if that page is on one of your app’s popup origins (by default, the origins of your redirect URIs), and closes. event is sent, saved, cancelled, signed or declined. With redirect_uri (one of your registered redirect URIs) and state, a popup that has lost its opener goes back there with the same values as query parameters.
<script src="https://sign.dazr.eu/sdk.js"></script>
<script>
document.querySelector('#sign').addEventListener('click', async () => {
// Ask your server for the URL after the click: the popup opens at once.
const url = fetch('/my-server/prepare-url').then(r => r.json()).then(j => j.prepare_url);
const result = await DazrSign.open(url);
if (result.event === 'sent') showSent(result.document_id);
// 'closed': the window was closed; check the status with the API or a webhook
});
</script>
If your page sends Cross-Origin-Opener-Policy: same-origin, use same-origin-allow-popups instead, or the popup cannot report back.
Qualified signatures
Each signer has a level: "advanced" (the default: the email address is confirmed by signing in with Dazr Identity) or "qualified". A qualified signer downloads the PDF in Dazr Sign, signs it with their own qualified electronic signature (an electronic ID card, a signing app such as itsme, a qualified certificate in Adobe Acrobat Reader) and uploads the signed file. Dazr Sign checks it against the EU trusted lists and accepts it only if it is a qualified signature on exactly the file it handed out. Dazr does not issue qualified signatures.
Set the level per recipient: { "name": "Mario Rossi", "email": "mario@example.eu", "level": "qualified" }. A qualified signer has no initials fields. name_rule on the document says what happens when the name on the certificate differs from the recipient’s name: "warn" (the default: accepted, with name_matches false) or "block" (refused). Templates keep the level of each role, and from_template can raise a role to "qualified".
Every signature stays valid: later signatures and the final Dazr seal are added as incremental updates, and the audit trail of such a document is a separate sealed PDF at GET /documents/{id}/audit.pdf. Recipients in documents and webhooks have level and, once a qualified signer has signed, certificate: subject (common_name, given_name, surname, serial_number, country, organization, organization_identifier), issuer, serial, sha256, trust_service_provider, trusted_list_country, signing_time, trusted_timestamp, format (PAdES or CAdES) and name_matches. Any other level gives 400 invalid_level.
Webhooks
Set a webhook URL for your app in the Sign portal under Developers. You get its signing secret once. Dazr Sign sends sign.document.sent, viewed, signed, completed, declined, voided and expired for the documents your app created, with the document’s status and its recipients’ status, never its contents. Failed deliveries are retried after 5 minutes, 30 minutes, 2, 6, 12 and 24 hours; the delivery log shows every attempt and can resend one.
// Node: check the Dazr-Signature header (t=<unix time>,v1=<HMAC-SHA256 hex>)
import crypto from 'node:crypto';
export function verify(rawBody, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const want = crypto.createHmac('sha256', secret).update(m[1] + '.' + rawBody).digest();
return crypto.timingSafeEqual(want, Buffer.from(m[2], 'hex'));
}
// The body: { id, type: 'sign.document.completed', created, data: { document: { id, status, recipients: [...] } } }
Reference
All paths start with https://sign.dazr.eu/api/sign/v1. Times are ISO 8601 in UTC.
| Request | What it does |
|---|---|
POST /documents | Creates a draft from a PDF: file in base64, a multipart upload (file plus data as JSON), or an upload_id. Optional: title, message, recipients, fields, signing_order, deadline_days, reminder_days, send, prepare. |
POST /documents/from_template | Creates a draft from a template: template_id, recipients as { role, name, email }, values by field ID or label. |
GET /documents | Lists your app’s documents for this person, newest first: limit (up to 100), cursor, status. |
GET /documents/{id} | The document with its status, recipients and fields. |
POST /documents/{id}/send | Sends a draft. |
POST /documents/{id}/remind | Reminds the signers whose turn it is (once an hour at most). |
POST /documents/{id}/void | Cancels a document out for signature, with an optional reason. |
DELETE /documents/{id} | Deletes a draft or a closed document. |
POST /documents/{id}/prepare_url | A new prepare popup for a draft: origin, redirect_uri, state. |
POST /documents/{id}/recipients/{recipient_id}/signing_url | A signing popup for a signer whose turn it is: origin, redirect_uri, state. |
GET /documents/{id}/original.pdf | The PDF as it was sent. |
GET /documents/{id}/signed.pdf | The sealed copy, once everyone has signed. |
GET /documents/{id}/audit.json | The audit trail: events, hashes and signers. |
GET /documents/{id}/audit.pdf | The separate, sealed audit trail of a document with qualified signers, once everyone has signed. |
POST /uploads | Starts an upload in parts for PDFs over 4 MB: size. Then PUT /uploads/{upload_id}/parts/{n} with the raw bytes of each part. |
GET /templates | The person’s templates, with their roles and fields. |
GET /templates/{id} | One template. |
POST /templates | Saves one of your app’s documents as a template: document_id, name. |
DELETE /templates/{id} | Deletes a template. |
Send an Idempotency-Key header when you create a document: the same key and body within 24 hours return the same document (header Idempotent-Replayed: true) instead of a second one.
Errors and limits
Errors have an HTTP status and a body like { "error": { "code": "not_found", "message": "..." } }, sometimes with details such as index.
| Status | Codes |
|---|---|
400 | invalid_field, invalid_recipients, invalid_email, not_pdf, pdf_unreadable, pdf_encrypted, invalid_origin, invalid_redirect_uri, unknown_role, unknown_field |
401 | unauthenticated, invalid_token (expired or revoked, or the person removed your app) |
403 | insufficient_scope, sign_api_disabled |
404 | not_found (also for documents your app did not create), template_not_found |
409 | not_draft, not_sent, not_your_turn, not_signable, signer_without_fields, no_signers, void_first, idempotency_in_progress |
413 | pdf_too_large, too_large, quota |
422 | idempotency_key_reused |
429 | rate_limited, with a Retry-After header |
- PDFs up to 25 MB and 200 pages, without a password. Requests up to 4 MB; larger PDFs go through
/uploads. - Up to 20 recipients and 400 fields per document, 100 templates per person. Documents and templates count towards the person’s Dazr storage.
- 120 requests a minute for each person per app, 30 new documents every 10 minutes and 30 sends an hour.
- Access tokens last 10 minutes. Use the refresh token (
offline_access) to get a new one.
Transferring an app
An app can move to another organisation registered on Dazr, under Transfer app in the app’s settings. With the Dazr Sign API enabled, it can only move to a verified organisation, and an owner or admin there must accept within 14 days. The Dazr Identity developer docs describe every step.
- Documents your app created before the transfer stay with the people who own them. The app no longer sees them through the API, and no webhooks are sent for them.
- The webhook URL and the popup origins stay. The webhook signing secret is renewed and shown once to the admin who accepts.
- Access tokens and refresh tokens issued before the transfer stop working, and people approve the app again at their next sign-in. Templates belong to people and are not affected.
- Document counts in the Sign portal start again at the transfer.