{"openapi":"3.1.0","info":{"title":"doclinth API","version":"1.0.0","description":"Generate pixel-perfect PDFs from your templates and structured data via a single API call."},"servers":[{"url":"https://doclinth.com","description":"doclinth API"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/generate":{"post":{"operationId":"generatePdf","summary":"Generate a PDF","description":"Renders one of your templates with the supplied data and returns the PDF bytes (default) or a 24-hour signed URL.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"A unique value that makes the request safe to retry. A repeat within 24 hours returns 409 instead of generating a second PDF; an exception is options.output \"url\", where a repeat replays the original document's signed URL (200 with replayed: true).","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRequest"}}}},"responses":{"200":{"description":"The generated PDF, or a signed URL when options.output is \"url\".","headers":{"X-Request-Id":{"description":"Unique id for this request; echoed on every response and shown in your dashboard Generation logs. Use with GET /v1/generations/{id}.","schema":{"type":"string"}},"X-Doclinth-Warnings":{"description":"Present only when template variables were unresolved and rendered blank. Value is `missing=<comma-separated paths>` (binary responses; URL responses use the `warnings` JSON field instead).","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix ms timestamp when the window resets.","schema":{"type":"integer"}}},"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"$ref":"#/components/schemas/UrlResponse"}}}},"202":{"description":"Async generation queued (when options.webhook_url is set).","headers":{"X-Request-Id":{"description":"Unique id for this request; equals the returned generation_id. Use with GET /v1/generations/{id}.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"generation_id":{"type":"string"},"status":{"type":"string","enum":["queued"]}}}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Monthly PDF quota reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Custom watermark on a free plan (watermark_not_allowed). Paid plans only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No template with that id exists for your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This Idempotency-Key was already used within the last 24 hours. Exception: an options.output \"url\" repeat replays the original document's signed URL (200 with replayed: true) instead of 409.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid request (bad JSON, missing template_id, data too large, invalid_options, template error), or missing_data_fields when options.strict is set and the template references fields absent from `data`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — retry after the Retry-After delay.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The render service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"Rendering took longer than 20 seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates":{"get":{"operationId":"listTemplates","summary":"List your templates","description":"Lists your non-archived templates (most recently updated first) so integrators can discover template ids from code. Use the returned id as `template_id` in POST /v1/generate.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Your templates, with their ids.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix ms timestamp when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TemplateSummary"}}}}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — retry after the Retry-After delay.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTemplate","summary":"Create a template from a prompt","description":"AI-authors a new template from a natural-language prompt and saves it as a DRAFT. /v1/generate keeps rendering the last published version until you review the draft in the dashboard and publish it. Consumes your monthly AI-authoring allowance (not your PDF quota). The response includes the template id and its generated sample_data — call GET /v1/templates/{id} for the full data contract, then POST /v1/generate.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateRequest"}}}},"responses":{"201":{"description":"The created draft template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedTemplate"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Monthly AI-authoring allowance reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"quality \"pro\" requires a paid plan (upgrade_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid body (prompt missing/too long, or bad quality value).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — retry after the Retry-After delay.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Template generation failed (ai_failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates/{id}":{"get":{"operationId":"getTemplate","summary":"Retrieve a template","description":"Returns one template's metadata, its sample_data, the rendered html (published version, falling back to the draft), and a derived `variables` list of every path the template references — the canonical answer to \"what shape should my `data` be\".","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The id of a template you own.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The template's metadata and sample data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDetail"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No template with that id exists for your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — retry after the Retry-After delay.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates/{id}/schema":{"get":{"operationId":"getTemplateSchema","summary":"Retrieve a template's typed data contract","description":"Returns the template's derived Draft 2020-12 JSON Schema. Set format to typescript, zod, or pydantic to receive generated source text.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["typescript","zod","pydantic"]}}],"responses":{"200":{"description":"JSON Schema or generated source text.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonSchema"}},"text/plain":{"schema":{"type":"string"}}}},"401":{"description":"Missing, invalid, or revoked API key."},"404":{"description":"No template with that id exists for your account."},"422":{"description":"Unknown codegen format."}}}},"/v1/templates/{id}/fixtures":{"get":{"operationId":"listTemplateFixtures","summary":"List named template fixtures","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Fixture list.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TemplateFixture"}}}}}}}}},"post":{"operationId":"createTemplateFixture","summary":"Create a named template fixture","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateFixtureInput"}}}},"responses":{"201":{"description":"Fixture created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateFixture"}}}},"409":{"description":"Fixture name already exists."},"422":{"description":"Invalid fixture or expectations."}}}},"/v1/templates/{id}/fixtures/{name}":{"put":{"operationId":"updateTemplateFixture","summary":"Update or rename a fixture","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateFixtureInput"}}}},"responses":{"200":{"description":"Updated fixture.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateFixture"}}}},"404":{"description":"Fixture not found."}}},"delete":{"operationId":"deleteTemplateFixture","summary":"Delete a fixture","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Fixture deleted."},"404":{"description":"Fixture not found."}}}},"/v1/templates/{id}/tests":{"post":{"operationId":"runTemplateTests","summary":"Queue document layout stress tests","description":"Generates adversarial payloads from the template JSON Schema, appends named fixtures, and renders every variant sequentially. Test renders do not consume generation quota.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Run queued.","content":{"application/json":{"schema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued"]}}}}}},"404":{"description":"Template not found."},"429":{"description":"Daily per-template run limit reached."},"503":{"description":"Document-test workers are not configured."}}}},"/v1/templates/{id}/tests/{runId}":{"get":{"operationId":"getTemplateTestRun","summary":"Poll a document-test run","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Run summary, per-variant diagnostics, and one-hour artifact URLs for failures.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateTestRun"}}}},"404":{"description":"Run not found."}}}},"/v1/generations/{id}":{"get":{"operationId":"getGeneration","summary":"Retrieve a generation","description":"Returns status metadata for a single generation, looked up by the value of its X-Request-Id header (also the generation_id from an async 202). Metadata only — async PDFs are delivered to your webhook and are not re-downloadable here.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The request id / generation id to look up.","schema":{"type":"string"}}],"responses":{"200":{"description":"The generation's status metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Generation"}}}},"401":{"description":"Missing, invalid, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No generation with that id exists for your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited — retry after the Retry-After delay.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your secret API key, e.g. `dl_live_…` (or `dl_test_…` for test mode)."}},"schemas":{"GenerateRequest":{"type":"object","required":["template_id"],"properties":{"template_id":{"type":"string","description":"The id of a template you own."},"data":{"type":"object","additionalProperties":true,"description":"Values merged into the template's placeholders (max 256 KB)."},"options":{"type":"object","additionalProperties":false,"properties":{"output":{"type":"string","enum":["binary","url"],"default":"binary","description":"\"binary\" (default) returns the PDF bytes; \"url\" returns a 24-hour signed URL."},"webhook_url":{"type":"string","format":"uri","description":"Public https endpoint. When set, the API responds 202 and POSTs the signed result to this URL asynchronously."},"strict":{"type":"boolean","default":false,"description":"When true, return 422 missing_data_fields (and consume no quota) instead of rendering a PDF with blank fields for template variables absent from `data`."},"validate":{"type":"boolean","default":false,"description":"When true, validate data against the template's Draft 2020-12 contract before rendering. Invalid payloads return 422 invalid_template_data with field-level errors."},"format":{"type":"string","enum":["A4","A6","Letter"],"description":"Page size for this render. Overrides the template's page_format. Default: template value, then A4."},"landscape":{"type":"boolean","default":false,"description":"Landscape orientation. Overrides the template's page_landscape. Default: template value, then false (portrait)."},"margins":{"type":"object","description":"Page margins as CSS-like lengths (mm, in, or px). Each side optional; converted to mm and bounded 0–50mm. Overrides template page_margins; default 16mm all sides.","properties":{"top":{"type":"string","pattern":"^\\d+(\\.\\d+)?(mm|in|px)$","description":"e.g. \"20mm\", \"0.5in\", \"12px\"."},"right":{"type":"string","pattern":"^\\d+(\\.\\d+)?(mm|in|px)$"},"bottom":{"type":"string","pattern":"^\\d+(\\.\\d+)?(mm|in|px)$"},"left":{"type":"string","pattern":"^\\d+(\\.\\d+)?(mm|in|px)$"}},"additionalProperties":false},"metadata":{"type":"object","description":"PDF document metadata (title, author, subject, keywords). Applied post-render via pdf-lib.","properties":{"title":{"type":"string","maxLength":256},"author":{"type":"string","maxLength":256},"subject":{"type":"string","maxLength":256},"keywords":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":64}}},"additionalProperties":false},"filename":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9 ._-]{0,120}\\.pdf$","description":"Download filename (ASCII-safe, ends with .pdf). Used in Content-Disposition (binary) and signed-URL download name (url mode). Storage path stays opaque."},"watermark":{"type":"object","description":"Custom diagonal text watermark. Paid plans only (free returns 403 watermark_not_allowed). Ignored for test-mode keys (TEST stamp always wins). Free plan keeps the forced brand badge.","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":40,"description":"Watermark text (1–40 characters)."},"opacity":{"type":"number","minimum":0.05,"maximum":0.5,"default":0.12,"description":"Opacity clamped to [0.05, 0.5]. Default 0.12."}},"additionalProperties":false}}}}},"UrlResponse":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Signed URL to the generated PDF."},"expires_at":{"type":"string","format":"date-time","description":"When the signed URL and stored object expire (24 hours)."},"warnings":{"type":"array","items":{"type":"string"},"description":"Template variables that were unresolved in `data` and rendered blank, e.g. [\"items\", \"customer.name\"]. Omitted when nothing was missing."}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable error code."},"message":{"type":"string","description":"Human-readable description."}}}}},"TemplateSummary":{"type":"object","description":"A template in a listing response. Source HTML and sample data are never returned.","properties":{"id":{"type":"string","format":"uuid","description":"Use this as `template_id` in POST /v1/generate."},"name":{"type":"string"},"description":{"type":"string","nullable":true},"page_format":{"type":"string","enum":["A4","A6","Letter"]},"published":{"type":"boolean","description":"Whether the template has a published version (what /v1/generate renders)."},"updated_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"CreateTemplateRequest":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"Natural-language description of the document to author, e.g. \"A consulting invoice with hourly line items, a notes section, and 8% tax\"."},"quality":{"type":"string","enum":["fast","pro"],"default":"fast","description":"\"fast\" (default) uses the economical model; \"pro\" uses a stronger model and requires a paid plan."}}},"CreatedTemplate":{"type":"object","description":"A newly created draft template. Publish it in the dashboard before /v1/generate will render it.","properties":{"id":{"type":"string","format":"uuid","description":"Use this as `template_id` in POST /v1/generate."},"name":{"type":"string","description":"Auto-derived from your prompt."},"description":{"type":"string","nullable":true},"sample_data":{"type":"object","additionalProperties":true,"description":"Generated example `data` payload — the shape POST /v1/generate expects."}}},"TemplateDetail":{"type":"object","description":"A single template's metadata, sample data, rendered HTML, and derived variable list.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"page_format":{"type":"string","enum":["A4","A6","Letter"]},"sample_data":{"type":"object","additionalProperties":true,"description":"Example `data` payload for this template — the shape POST /v1/generate expects."},"html":{"type":"string","description":"The template's Handlebars source — the published version, falling back to the draft for never-published templates (exactly what /v1/generate renders)."},"variables":{"type":"array","items":{"type":"string"},"description":"Every data path the template references, e.g. [\"customer.name\", \"items\", \"total\"]. Paths inside {{#each}}/{{#with}} are relative to their block — pair with sample_data for the exact nested shape."},"json_schema":{"$ref":"#/components/schemas/JsonSchema","description":"Draft 2020-12 contract for the data object. Every template is a typed API."},"published":{"type":"boolean","description":"Whether the template has a published version (what /v1/generate renders)."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"JsonSchema":{"type":"object","description":"A Draft 2020-12 JSON Schema derived from a template's Handlebars structure and sample data.","additionalProperties":true},"TemplateTestRun":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","running","passed","failed","error"]},"variants_total":{"type":"integer"},"variants_failed":{"type":"integer"},"next_variant":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["auto","fixture"]},"status":{"type":"string","enum":["pass","fail","error"]},"diagnostics":{"type":"object","additionalProperties":true},"artifact_url":{"type":["string","null"],"format":"uri"},"duration_ms":{"type":"integer"}}}}}},"TemplateFixtureInput":{"type":"object","required":["name","data"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"data":{"type":"object","additionalProperties":true},"expectations":{"type":"object","additionalProperties":false,"properties":{"page_count":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","required":["min","max"],"properties":{"min":{"type":"integer","minimum":1},"max":{"type":"integer","minimum":1}}}]},"must_contain":{"type":"array","items":{"type":"string"}},"must_not_contain":{"type":"array","items":{"type":"string"}},"max_render_ms":{"type":"integer","minimum":1}}}}},"TemplateFixture":{"allOf":[{"$ref":"#/components/schemas/TemplateFixtureInput"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}]},"Generation":{"type":"object","description":"Status metadata for a single generation (no PDF bytes).","properties":{"id":{"type":"string","description":"The request id / generation id."},"status":{"type":"string","enum":["queued","success","error"]},"error_code":{"type":"string","nullable":true,"description":"Stable error code when status is \"error\"; null otherwise."},"duration_ms":{"type":"integer","nullable":true,"description":"Render time in milliseconds."},"created_at":{"type":"string","format":"date-time"},"warnings":{"type":"array","items":{"type":"string"},"description":"Template variables that were unresolved and rendered blank."}}}}}}