Inkspan › Guides › How to generate a PDF from HTML in Python
Rendering HTML to PDF is the single most common reason a team reaches for a document API. Doing it yourself means running headless Chrome or wkhtmltopdf inside your container, installing a font stack so your output does not silently fall back to Times New Roman, and keeping that renderer alive under concurrency. The Inkspan /v1/generate/html endpoint takes an HTML string and streams back a PDF, so none of that lives in your deploy.
Inkspan renders with WeasyPrint, which implements CSS paged media properly — @page size and margins, running headers, and page counters all work. That makes it a better fit for documents like invoices and reports than a screenshot-oriented browser renderer.
import requests
r = requests.post(
'https://api.inkspan.dev/v1/generate/html',
headers={'X-API-Key': 'ink_live_...'},
json={
"html": "<h1>Invoice INV-1042</h1><p>Total: $61.00</p>"
},
)
r.raise_for_status()
with open('document.pdf', 'wb') as f:
f.write(r.content)
| Name | Type | Required | Description |
|---|---|---|---|
html | string | yes | The HTML document or fragment to render. |
filename | string | no | Filename in the response's Content-Disposition. Defaults to document.pdf. |
Returns a PDF as a binary stream with Content-Type: application/pdf and Content-Disposition: attachment; filename=document.pdf. Write the response body straight to disk — do not parse it as JSON.
https:// URLs, or inline them as data: URIs.@page { size: A4; margin: 18mm; } in your HTML to control them.file:// URLs are rejected. Host assets publicly or inline them.| Status | Meaning | Fix |
|---|---|---|
401 | Invalid or missing API key | Send your key in the X-API-Key header. |
402 | Monthly quota exceeded | The response reports used/quota. Upgrade at inkspan.dev/#pricing. |
422 | Missing or invalid parameter | Check required fields in the table above — the body names the offending field. |
Failed requests do not count against your quota; only successful ones are metered.
No. The renderer is not a browser, so client-side JS does not execute. Render your markup server-side before sending it — which is faster and more deterministic anyway.
Use CSS paged media: @page { @bottom-center { content: counter(page) ' of ' counter(pages); } }.
Practical limits come from request size rather than a fixed cap. Very large documents are better assembled as several calls and combined with /v1/pdf/merge.
Related Generate guides: generate a PDF from a template · convert Markdown to PDF