Python SDK

doclinth is the official Python client for the doclinth PDF generation API. It ships sync and async clients (httpx), Pydantic response models, automatic retries with safe idempotency, and webhook signature verification — so a retried request can never double-print or double-bill.

Install

pip install doclinth

Requires Python 3.10+. Set DOCLINTH_BASE_URL or pass base_url (no host is hardcoded).

Generate a PDF

from doclinth import Doclinth

client = Doclinth(
    api_key="dl_live_…",
    base_url="https://doclinth.com",
)

# Binary (default) — returns the PDF bytes.
pdf = client.generate(
    template_id="b1c2d3e4-…",
    data={"invoice_number": "INV-1042", "total": 2592},
)
with open("invoice.pdf", "wb") as f:
    f.write(pdf)

# Signed URL — returns UrlResult(url=…, expires_at=…).
result = client.generate(
    template_id="b1c2d3e4-…",
    data={"total": 2592},
    output="url",
)

FastAPI

Hold an AsyncDoclinth on the app lifespan and return the PDF bytes as a response:

from contextlib import asynccontextmanager
from fastapi import FastAPI, Response
from doclinth import AsyncDoclinth

@asynccontextmanager
async def lifespan(app: FastAPI):
    client = AsyncDoclinth(
        api_key="dl_live_…",
        base_url="https://doclinth.com",
    )
    app.state.doclinth = client
    yield
    await client.aclose()

app = FastAPI(lifespan=lifespan)

@app.post("/invoices/{invoice_id}.pdf")
async def invoice_pdf(invoice_id: str) -> Response:
    pdf = await app.state.doclinth.generate(
        template_id="b1c2d3e4-…",
        data={"invoice_number": invoice_id, "total": 2592},
    )
    return Response(content=pdf, media_type="application/pdf")

Django

Use the sync client from a view and return an HttpResponse:

from django.conf import settings
from django.http import HttpResponse
from doclinth import Doclinth

client = Doclinth(
    api_key=settings.DOCLINTH_API_KEY,
    base_url="https://doclinth.com",
)

def invoice_pdf(request, invoice_id: str) -> HttpResponse:
    pdf = client.generate(
        template_id="b1c2d3e4-…",
        data={"invoice_number": invoice_id, "total": 2592},
    )
    return HttpResponse(pdf, content_type="application/pdf")

Webhook receiver (FastAPI)

When you pass webhook_url, Doclinth POSTs the signed result with X-Doclinth-Signature: sha256=<hex>. Verify it with verify_webhook:

import json
from fastapi import FastAPI, Header, HTTPException, Request
from doclinth import WEBHOOK_SIGNATURE_HEADER, verify_webhook

app = FastAPI()
SECRET = "whsec_…"  # from the dashboard

@app.post("/webhooks/doclinth")
async def doclinth_webhook(
    request: Request,
    x_doclinth_signature: str | None = Header(default=None, alias=WEBHOOK_SIGNATURE_HEADER),
) -> dict[str, str]:
    body = await request.body()
    if not x_doclinth_signature or not verify_webhook(body, x_doclinth_signature, SECRET):
        raise HTTPException(status_code=401, detail="invalid signature")
    payload = json.loads(body)
    # payload includes generation_id, url / status, …
    return {"ok": "true"}

Retries & idempotency

By default the client retries 429 and 5xx responses (and network errors) up to twice with exponential backoff, honoring Retry-After. To make those retries safe, it attaches an Idempotency-Key automatically. Provide your own key to dedupe across process restarts:

client.generate(template_id="b1c2d3e4-…", data=data, idempotency_key=order_id)

Set max_retries=0 to disable retries (and the automatic key). templates.create only retries a pre-work 429, never a 5xx.

Errors

Non-2xx responses raise a DoclinthError with a stable code and status:

from doclinth import Doclinth, DoclinthError

try:
    client.generate(template_id=template_id, data=data)
except DoclinthError as e:
    if e.code == "quota_exceeded":
        ...  # prompt an upgrade
    raise

See the error reference for all codes.

Also available

Prefer TypeScript? See the Node.js SDK. The API is plain HTTP with a bearer key — generate from curl, Go, PHP, or your AI agent over MCP. Full contract: OpenAPI 3.1.