HTML to PDF in Python
Python's local options all come with baggage: wkhtmltopdf is
abandoned and renders 2010-era CSS, WeasyPrint needs system libraries that fight your
Docker image, and driving headless Chrome from Python means babysitting browser processes.
A rendering API is one requests.post.
1. Get an API key
import requests
res = requests.post(
"https://inkpdf.dev/v1/keys/free",
json={"email": "you@company.com"},
)
print(res.json()["apiKey"]) # shown once — save itShortcut: the official client
pip install inkpdf-client — stdlib only, no dependencies:
from inkpdf import InkPDF
client = InkPDF(os.environ["INKPDF_KEY"])
open("invoice.pdf", "wb").write(client.invoice(invoice_data))
open("report.pdf", "wb").write(client.markdown("# Q2 Report", theme="serif"))2. Convert HTML to PDF
import os
import requests
res = requests.post(
"https://inkpdf.dev/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['INKPDF_KEY']}"},
json={
"html": "<h1>Hello from Python</h1><p>Rendered by real Chromium.</p>",
"options": {"format": "Letter"},
},
)
res.raise_for_status()
with open("hello.pdf", "wb") as f:
f.write(res.content)3. Generate an invoice from a dict (no HTML)
Post plain data against a built-in template — totals and tax are computed for you:
invoice = {
"template": "invoice",
"data": {
"brand": {"name": "Acme Studio", "color": "#0f766e"},
"invoiceNumber": "INV-2043",
"issueDate": "2026-07-01",
"dueDate": "2026-07-31",
"currency": "USD",
"to": {"name": "Northwind Traders", "address": "1 Market St, SF"},
"items": [
{"description": "Design work", "quantity": 10, "unitPrice": 95},
],
"taxRate": 8.5,
},
}
res = requests.post(
"https://inkpdf.dev/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['INKPDF_KEY']}"},
json=invoice,
)4. Django view: PDF as a response
from django.http import HttpResponse
def invoice_pdf(request, invoice_id):
data = build_invoice_data(invoice_id)
res = requests.post(
"https://inkpdf.dev/v1/pdf",
headers={"Authorization": f"Bearer {settings.INKPDF_KEY}"},
json={"template": "invoice", "data": data},
timeout=60,
)
res.raise_for_status()
response = HttpResponse(res.content, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="{invoice_id}.pdf"'
return responseThe same pattern works in Flask (send_file(BytesIO(res.content), …))
and FastAPI (Response(res.content, media_type="application/pdf")).
Markdown reports
Generating reports from data pipelines? Send Markdown and pick a theme:
res = requests.post(
"https://inkpdf.dev/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['INKPDF_KEY']}"},
json={"markdown": report_md, "theme": "serif"},
)Error handling
Errors are JSON with stable codes — validation_error (400),
quota_exceeded (429), render_error
(408/422/500). Check res.json()["message"] on any non-200;
it says exactly what to fix. Only successful renders count against your quota.
Try it without writing code
The playground renders live against the real pipeline — 20 free renders a day, no signup.