On this page
API reference
Base: https://dokkraft.blocsland.com/api/v1 — authentication Authorization: Bearer dfk_… on every endpoint. Responses are JSON (Content-Type: application/json), except streamed PDFs.
Identifiers used in URLs are the public_id (template, folder), not internal ids.
Creation idempotency
The four creation POSTs (single documents and batches) accept an Idempotency-Key header — a unique value per intent, e.g. a UUID generated on your side. If a network timeout makes you replay the POST with the same key and the same payload, you receive the first call's response (Idempotent-Replayed: true) instead of a numbered duplicate — the memory lasts 24 h. With download=true, the replay re-serves the PDF of the document created by the first call.
| Response | Meaning |
|---|---|
First call's response + Idempotent-Replayed: true |
Identical intents — no duplicate created |
422 idempotency_key_reused |
Same key with a different payload — a key carries a single intent, generate a new one |
409 idempotency_in_progress |
A call with this key is still running — retry in a moment |
POST /templates/{public_id}/pdf
Generates a PDF without saving anything: the template body is rendered with the passed variables.
| Parameter | Type | Description |
|---|---|---|
data |
object |
Variable values ({"client.name": "ACME"}) |
references |
object |
Linked records by id ({"client": "42"}) — resolved as in document mode |
tableData |
array |
Table rows |
download |
boolean |
true → attachment, otherwise inline stream |
Response: PDF (application/pdf).
POST /templates/{public_id}/documents
Creates a numbered, archived document ("Unfiled" root, or the folder argument), with its PDF.
| Parameter | Type | Description |
|---|---|---|
name |
string |
Document name (default Document API) |
date |
string |
Issue date (Y-m-d) |
folder |
string |
Archiving folder public_id |
data, references, tableData |
object / array |
Variables, registries, table rows |
download |
boolean |
Return the PDF instead of JSON |
async |
boolean |
true → the PDF goes to the queue: the 201 responds as soon as the number is reserved (pdfStatus: "queued"), poll GET /documents/{id} then fetch the PDF — or declare a webhook to be notified instead. Without async, rendering is synchronous (~2 s median, the request waits for the PDF) |
201 response: the document record (integrator contract, id = public_id — chainable with GET /documents/{id}/pdf).
{
"id": "01HXXXX-public-id",
"name": "ACME invoice",
"numero": "INV-2026-0042",
"status": "emis",
"templateId": "tmpl_01HYYYY",
"folderId": null,
"date": "2026-09-22",
"amount": 1250.0,
"pdfStatus": "ready",
"pdfUrl": "/api/v1/documents/01HXXXX-public-id/pdf",
"emittedAt": "2026-09-22T10:15:00+00:00"
}
Idempotency: an Idempotency-Key header is accepted — see the dedicated section at the top of this page.
Errors: 422 async combined with download (incompatible — one responds without the PDF, the other waits for synchronous rendering), 422 missing required fields ({message, errors} format — see Errors), 503 pdf_busy when the synchronous render pool is saturated (Retry-After: 5 header — retry or switch to async=true, which takes no slot).
Template required fields: variables and tracking dimensions marked required in the schema must resolve to a non-empty value, otherwise 422 — but a folder/workspace cascade, a schema default, a formula or the dedicated date satisfy the requirement: data is only necessary for what is not pre-filled (error keys detailed in Errors). This rule applies to single POSTs and to every batch item.
POST /folders/{public_id}/documents
Same as the previous endpoint, but the folder is the entry point: its template (linked locally or inherited from an ancestor), its registry cascade and variables apply automatically.
| Parameter | Type | Description |
|---|---|---|
name, date |
string |
As above |
data, references, tableData |
object / array |
Cascade overrides |
download |
boolean |
Return the PDF instead of JSON |
async |
boolean |
As above |
Errors: 404 unknown folder (or outside the key's workspace), 422 no linked template or async combined with download, 422 missing required fields and 503 pdf_busy — same contracts as the previous endpoint (the folder's cascade can on its own satisfy the required fields).
POST /templates/{public_id}/documents/batch
Batch generation (asynchronous). Creates several documents at once and immediately replies 202: documents are emitted outside the request, their PDFs rendered on the queue. Track progress with GET /batches/{batch_id} — this is the way to go as soon as you need more than a handful of documents.
| Parameter | Type | Description |
|---|---|---|
items |
array (1–50) |
One object per document: name, date, folder, data, references, tableData — the same fields as the single endpoint |
folder |
string |
Default archiving folder public_id (each item may override it) |
Response 202: the batch resource.
{
"id": "4e7c9a52-…",
"status": "pending",
"total": 2,
"counts": { "queued": 2, "emitted": 0, "failed": 0 },
"items": [
{ "position": 0, "status": "queued", "document": null }
]
}
Limits: 50 items per batch, 2 active batches (pending/processing) per workspace, 10 batches/minute. The monthly documents quota is consumed per actually emitted document — the fast-fail 429 quota_exceeded carries remaining and required when fewer documents remain than items requested.
Errors: 422 validation (malformed items, unknown folder), 429 batch_limit_reached (too many active batches) or quota_exceeded (document quota pre-flight).
POST /folders/{public_id}/documents/batch
Same as above, but the folder is the entry point: its template (linked locally or inherited from an ancestor) and cascade apply to every item.
GET /batches/{batch_id}
Batch status (integrator polling). Each call counts against the api_calls quota — polling every 2-3 s is plenty. An active batch with no update for 15 min (lost server-side job) is picked up automatically: just keep polling. The batch.completed webhook removes the need to poll — see Webhooks.
Response 200: the full batch resource. status goes pending → processing → completed (all emitted), partial (at least one failure) or failed. Each item carries its document (number, pdfStatus, pdfUrl) as soon as it is emitted; a failed item never blocks the others — its error explains why.
GET /documents/{public_id}
A single document created via the API — the same format as the single POST response (id, numero, pdfStatus, pdfUrl, …). 404 when the document belongs to another workspace.
GET /documents/{public_id}/pdf
The document's PDF, streamed (?download=true for an attachment). Companion to batch mode: the file only exists after the queued render.
| Response | Meaning |
|---|---|
200 |
The PDF (application/pdf stream) |
202 + Retry-After: 5 |
Render in progress — poll again (or receive a webhook as soon as it is ready) |
409 pdf_draft / pdf_failed / pdf_missing |
Draft (no PDF), failed render or missing file |
410 pdf_expired |
PDF purged by the plan's retention (regenerate from the app) |
POST /embed/sessions
Creates a studio-in-iframe session: exchanges the API key for a signed URL to load into your product.
| Parameter | Type | Description |
|---|---|---|
template_id |
string |
public_id of the template to edit |
allowed_domains |
array |
Allowed origins (1 to 10) — https://app.example.com |
ttl_minutes |
integer |
Lifetime (5 to 1440, default 120) |
permissions |
object |
variables (frozen|editable), ai, save, export_pdf |
201 response: { "url", "token", "expires_at", "permissions" }.
DELETE /embed/sessions/{token}
Revokes an embed session: the iframe stops working immediately (leaked key, end of contract…). Answers 200 {"revoked": true} even for an unknown or already-revoked token — same answer in every case, no enumeration possible.
GET /templates/{public_id}/schema
No authentication. Variable schema of the template (generation fields, referenced registries) — handy to build a dynamic form on the integrator side.