API reference
Base URL https://html-pdf.com · send your HTML, get a PDF back · 1 credit = 1 PDF page.
Authentication
Send your key in the Authorization header (or X-API-Key):
Authorization: Bearer hp_live_...
Get a key by logging in at https://html-pdf.com/login, or with one call (ideal for agents):
curl -X POST https://html-pdf.com/v1/signup \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
{ "api_key": "hp_live_...", "credits": 0, "pending_credits": 100, "unit": "1 credit = 1 PDF page" }
The key is shown once. The email owner receives a link that confirms the email and adds the 100 free credits, then shows usage, keys and buying. Until the email is confirmed, conversions return 403 email_not_confirmed. If the email already has an account you get 409 account_exists and a login link is emailed instead.
POST /v1/pdf
Render HTML to PDF.
curl https://html-pdf.com/v1/pdf \
-H "Authorization: Bearer $HTML_PDF_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Invoice #042</h1><p>Total: $10</p>",
"format": "A4",
"margin": "15mm",
"footer_html": "<div style=\"font-size:9px;width:100%;text-align:center\"><span class=\"pageNumber\"></span> / <span class=\"totalPages\"></span></div>"
}' \
-o invoice.pdf
Options
| field | type | default | description |
|---|---|---|---|
html |
string | html or url |
Full document or fragment. Fragments are wrapped in <html><body>. |
url |
string | html or url |
Public http(s) page to render instead of html, e.g. "https://example.com/invoice/42". Private/internal addresses are rejected. Can't be combined with css. |
css |
string | Extra CSS injected into <head>. |
|
format |
string | A4 |
A3, A4, A5, A6, Letter, Legal, Tabloid. |
width, height |
string | Custom size, e.g. "210mm", "8.5in", "600px". Overrides format. |
|
landscape |
bool | false |
|
margin |
string or object | ~1 cm | "10mm" or {"top":"1in","right":"0.5in","bottom":"1in","left":"0.5in"}. |
print_background |
bool | true |
Print background colors and images. |
prefer_css_page_size |
bool | false |
Use the size from CSS @page { size: ... }. |
scale |
number | 1 |
0.1 to 2. |
page_ranges |
string | all | e.g. "1-3, 5". |
header_html, footer_html |
string | Repeated on every page. Placeholders: <span class="pageNumber">, totalPages, date, title. Needs enough top/bottom margin. Default font size is tiny; set font-size inline. |
|
media |
string | print |
print or screen CSS media. |
wait_delay |
number | 0 |
Seconds (0 to 10) to wait before rendering, for JS-built pages. |
wait_for_expression |
string | JS expression; render when it returns true, e.g. "window.ready === true". |
|
max_pages |
int | Fail (no charge) if the PDF would have more pages. | |
filename |
string | document.pdf |
Used in Content-Disposition. |
response |
string | binary |
binary (PDF body), base64 (JSON), or url (JSON with a link valid 24h). |
Unknown fields are rejected with 400 invalid_json so typos don't fail silently.
Response
200 application/pdf with headers:
| header | |
|---|---|
X-Pages |
pages in the PDF |
X-Credits-Used |
credits charged (= pages) |
X-Credits-Remaining |
balance after this request |
X-Conversion-Id |
e.g. cv_... |
With "response": "base64":
{ "id": "cv_...", "pages": 1, "credits_used": 1, "credits_remaining": 99, "filename": "document.pdf", "pdf_base64": "JVBERi0..." }
With "response": "url":
{ "id": "cv_...", "pages": 1, "credits_used": 1, "credits_remaining": 99, "url": "https://html-pdf.com/f/...", "expires_at": "2026-09-30T12:00:00Z" }
URL to PDF
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \
-d '{"url": "https://example.com", "media": "screen", "format": "Letter"}' -o page.pdf
The page is opened in headless Chrome and printed. Use "media": "screen" to keep the on-screen look, and wait_delay for pages that render with JavaScript. Pages behind a login won't work: fetch their HTML yourself and send it as html.
Images, fonts and assets
Reference remote assets by absolute https:// URL, or inline them as data: URIs. To upload local assets, send multipart/form-data with index.html plus the files, and an optional options field containing the JSON options:
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \
-F "index.html=@index.html" -F "logo.png=@logo.png" -F "style.css=@style.css" \
-F 'options={"format":"Letter"}' -o out.pdf
In index.html, reference them by name: <img src="logo.png">.
Page breaks
Use CSS: break-before: page, break-inside: avoid, @page { size: A4; margin: 20mm } (with prefer_css_page_size: true).
GET /v1/credits
{ "balance": 99, "used_total": 1, "email": "you@example.com", "unit": "1 credit = 1 PDF page", "top_up_url": "https://html-pdf.com/dashboard",
"auto_topup": { "enabled": false, "threshold": 100, "pack": { "usd": 10, "credits": 1000 }, "card": null, "settings_url": "https://html-pdf.com/dashboard#autotopup" } }
auto_topup.card is the saved card's label (e.g. "visa •••• 4242"), or null before the first purchase.
GET /v1/conversions
Last 100 conversions: id, status, pages, bytes, duration_ms, source (api, mcp, web), created_at. Document content is never stored.
POST /v1/checkout
Get a Stripe payment link for a credit pack, e.g. for an agent to send to its human. Credits are added as soon as the payment succeeds.
curl -X POST https://html-pdf.com/v1/checkout -H "Authorization: Bearer $HTML_PDF_KEY" -d '{"usd": 10}'
{ "checkout_url": "https://checkout.stripe.com/...", "usd": 10, "credits": 1000 }
Packs (usd → credits): 10 → 1,000 · 25 → 3,000 · 50 → 7,000 · 100 → 15,000 · 250 → 50,000.
Running low? The account owner gets an email below 20 credits and at 0, and can enable auto top-up in the dashboard to refill automatically from a saved card.
POST /v1/autotopup
Turn auto top-up on or off so an automation never runs out: when the balance drops below threshold, the card saved at the first purchase is charged for the usd pack (at most once every 10 minutes; it turns itself off if a payment fails). Send any of the three fields; the others keep their value. threshold can be at most the pack size, so one top-up always lifts the balance above it.
curl -X POST https://html-pdf.com/v1/autotopup -H "Authorization: Bearer $HTML_PDF_KEY" \
-d '{"enabled": true, "threshold": 200, "usd": 25}'
Returns {"auto_topup": {...}} as in GET /v1/credits. The account owner is emailed about every change. Without a saved card you get 409 no_saved_card with a checkout_url: buy once, then turn it on. Agents should ask the user before turning it on.
POST /v1/keys
Create an additional API key for the account of the key you authenticate with. Revoke keys in the dashboard.
Errors
All errors are JSON:
{ "error": { "code": "insufficient_credits", "message": "You have no credits left.", "hint": "Buy credits at ...", "top_up_url": "https://html-pdf.com/dashboard", "docs_url": "..." } }
| status | code | meaning |
|---|---|---|
| 400 | missing_html, invalid_url, html_and_url, invalid_json, invalid_* |
Fix the request. |
| 400 | disposable_email |
POST /v1/signup with a temporary (temp-mail) address. Use a permanent email. |
| 401 | missing_api_key, invalid_api_key |
|
| 402 | insufficient_credits |
No credits left, or the PDF has more pages than your balance (then the error has pages and balance). Not charged. checkout_url is a ready Stripe payment link to give the user, for the pack that fits your recent usage (from $25 for 3,000 pages, see checkout_pack); also top_up_url, or POST /v1/checkout for other packs. |
| 403 | email_not_confirmed |
Free credits are added when the account's email is confirmed. Open the emailed link, then retry. |
| 409 | account_exists |
Signup with an existing email; login link emailed. |
| 409 | no_saved_card |
POST /v1/autotopup before any purchase. Pay checkout_url once, then retry. |
| 413 | too_large |
Body over 20 MB. |
| 422 | render_failed, max_pages_exceeded |
Not charged. |
| 429 | rate_limited |
120 req/min per account. Retry after Retry-After. |
| 503 | busy, engine_unavailable |
Not charged. Retry. |
| 504 | render_timeout |
Over 30 s. Not charged. |
Failed requests never cost credits.
Limits
- Request body: 20 MB
- Render time: 30 s
- Rate: 120 requests/minute per account
- Download links (
response: "url", MCP): 24 hours
Security note
The renderer cannot reach private networks. Treat the HTML you send like code running in a sandboxed browser: it may load public URLs and run JavaScript.