SPEC 01 · AUTHENTICATION
Authentication
Create an API key in the dashboard and send it with every request — either header works:
Authorization: Bearer pdfr_live_...
# or
x-api-key: pdfr_live_...Keys are stored as SHA-256 hashes; the plaintext is shown once at creation. Revoke and reissue anytime.
SPEC 02 · QUICKSTART
Quickstart
cURL
curl -X POST https://www.pdf-relay.com/api/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Invoice #1042</h1>", "options": {"format": "A4"}}' \
--output invoice.pdfNode.js
const res = await fetch("https://www.pdf-relay.com/api/v1/pdf", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PDFRELAY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: "<h1>Invoice #1042</h1>",
options: { format: "A4" },
}),
});
if (!res.ok) throw new Error(`Render failed: ${res.status}`);
await fs.promises.writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));Python
import os, requests
res = requests.post(
"https://www.pdf-relay.com/api/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['PDFRELAY_API_KEY']}"},
json={"html": "<h1>Invoice #1042</h1>", "options": {"format": "A4"}},
timeout=60,
)
res.raise_for_status()
with open("invoice.pdf", "wb") as f:
f.write(res.content)SPEC 03 · REQUEST
Request schema
POST /api/v1/pdf with a JSON body (max 2 MB by default). Provide exactly one of html or url:
| Field | Type | Description |
|---|---|---|
html | string | HTML to render. |
url | string | Public http(s) URL to render. Private and internal addresses are blocked (SSRF guard). |
options | object | PDF options — see below. All optional. |
Success returns 200 with application/pdf bytes.
SPEC 04 · OPTIONS
PDF options
| Option | Type | Default | Description |
|---|---|---|---|
format | A4 | Letter | Legal | A3 | A5 | Tabloid | A4 | Paper size. |
landscape | boolean | false | Landscape orientation. |
printBackground | boolean | true | Print CSS backgrounds. |
scale | number 0.1–2 | 1 | Render scale. |
pageRanges | string | all | e.g. "1-3". |
margin | object | none | {top, right, bottom, left} in CSS units, e.g. "1cm". |
preferCSSPageSize | boolean | false | Honor @page size rules. |
waitUntil | load | domcontentloaded | networkidle | load | When the page counts as ready. |
SPEC 05 · HEADERS
Response headers
| Header | Meaning |
|---|---|
X-Pdfrelay-Cache | hit (served from cache, free) or miss (fresh render). |
X-Pdfrelay-Quota-Limit | Documents included in the current plan/period. |
X-Pdfrelay-Quota-Remaining | Documents left this period. |
X-Pdfrelay-Quota-Reset | ISO 8601 timestamp of the period reset. |
SPEC 06 · ERRORS
Errors
Errors are JSON: {"error": {"code": "...", "message": "..."}}
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request / invalid_json | Bad body. |
| 401 | missing_api_key / invalid_api_key / revoked_api_key / expired_api_key | Auth failure. |
| 402 | quota_exceeded | Monthly document quota reached — upgrade or wait for reset. |
| 403 | insufficient_scope | Key lacks the pdf:render scope. |
| 408 | render_timeout | Render exceeded the time limit. |
| 413 | payload_too_large | Body over the size cap. |
| 422 | url_blocked | URL failed SSRF checks (private/internal address). |
| 429 | rate_limited | Too many requests — Retry-After header set. |
| 502 | navigation_failed | Target URL failed to load. |
SPEC 07 · CACHING
Caching
Identical requests — same HTML or URL plus the same options, regardless of key order — are served from a short-TTL cache. Cache hits are free: they don’t consume quota and are served even when you’re over quota. Watch X-Pdfrelay-Cache to observe it.
SPEC 08 · LIMITS
Limits
- Request body: 2 MB (default).
- Render timeout: 30 s (default).
- Rate limit: 30 requests/minute per API key —
429withRetry-Afterbeyond that. - Monthly document quotas per plan — see pricing.