# doclinth — full API reference for agents doclinth turns HTML/Handlebars templates plus structured JSON into pixel-perfect PDFs. You create a template once (by hand or with AI), then generate documents from data forever. The render path is deterministic — it never calls an LLM — so the same template + data always produce the same PDF. Base URL: https://doclinth.com Machine-readable spec: https://doclinth.com/openapi.json ## Authentication Every request carries a bearer API key: Authorization: Bearer dl_live_YOUR_KEY Use `dl_test_` keys for test mode (renders are watermarked and never consume PDF quota). Create and revoke keys at https://doclinth.com/dashboard/keys. Errors branch on a stable `code`; the body shape is always `{ "error": { "code", "message" } }`. ## Endpoints ### POST /v1/generate — render a PDF Request body: { "template_id": "…uuid…", // required; a template you own "data": { … }, // values merged into placeholders (max 256 KB) "options": { "output": "binary" | "url", // default "binary" (PDF bytes); "url" → 24h signed URL "webhook_url": "https://…", // when set, responds 202 and POSTs the result async "strict": true | false, // true → 422 missing_data_fields instead of blank fields "format": "A4" | "A6" | "Letter", // overrides template page_format "landscape": true | false, // overrides template page_landscape "margins": { "top": "20mm", "right": "16mm", "bottom": "20mm", "left": "16mm" }, // mm|in|px; 0–50mm after conversion; default 16mm "metadata": { "title", "author", "subject", "keywords": ["…"] }, "filename": "invoice-1042.pdf", // ASCII-safe Content-Disposition / signed download name "watermark": { "text": "DRAFT", "opacity": 0.12 } // paid plans only; free → 403 watermark_not_allowed } } Unknown options keys → 422 invalid_options. Precedence for format/landscape/margins: request > template defaults > A4 / portrait / 16mm. Responses: - 200 with `application/pdf` bytes (default), or JSON `{ "url", "expires_at", "warnings"? }` when output is "url". - 202 `{ "generation_id", "status": "queued" }` when webhook_url is set. The signed result is POSTed to your webhook with an HMAC signature. - Send an `Idempotency-Key` header to make retries safe: a repeat within 24h returns 409 instead of generating a second PDF. Exception: when the original ran with `output:"url"`, a repeat replays the same document's signed URL (200 with `replayed: true`) instead of 409. ### GET /v1/templates — list your templates Returns `{ "data": [ { "id", "name", "description", "page_format", "published", "created_at", "updated_at" } ] }`. Source HTML and sample data are not included in the list (use the detail endpoint). ### POST /v1/templates — AI-author a template Request body `{ "prompt": "…", "quality": "fast" | "pro" }`. Authors a new template from a natural-language prompt and saves it as a DRAFT. Returns `{ "id", "name", "description", "sample_data" }`. Consumes your monthly AI-authoring allowance (separate from PDF quota); "pro" requires a paid plan. ### GET /v1/templates/{id} — a template's data contract Returns `{ "id", "name", "description", "page_format", "sample_data", "html", "variables", "json_schema", "latest_test_run", "published", "created_at", "updated_at" }`. `variables` lists every data path the template references (e.g. `["customer.name","items","total"]`); paths inside {{#each}}/{{#with}} blocks are relative to their block, so pair with `sample_data` for the exact nested shape. Call this before /v1/generate. ### Typed contracts, fixtures, and document tests - GET /v1/templates/{id}/schema — Draft 2020-12 JSON Schema. Add `?format=typescript|zod|pydantic` for generated source. - GET/POST /v1/templates/{id}/fixtures — list or create named edge-case payloads. PUT/DELETE /v1/templates/{id}/fixtures/{name} updates or removes one. - POST /v1/templates/{id}/tests — queue adversarial layout tests (202 + run_id). - GET /v1/templates/{id}/tests/{run_id} — status, diagnostics, and expiring failed proofs. Test variants change one dimension at a time (long strings, many rows, Unicode, unbroken tokens, extreme numbers, missing optionals, and broken images). They do not consume generation quota. Public one-off tester: https://doclinth.com/tools/pdf-stress-test. ### GET /v1/generations/{id} — generation status Look up one generation by its `X-Request-Id` (also the async `generation_id`). Returns `{ "id", "status", "error_code", "duration_ms", "created_at", "warnings" }`. Metadata only — async PDFs are delivered to your webhook, not re-downloadable here. ## Template syntax Templates are Handlebars documents (a single self-contained HTML file with inline