Render API reference
The API lives at https://api.quirepdf.dev. Every request and error body is JSON, except rendered files, which come back as raw PDF or PNG bytes.
Authentication
curl https://api.quirepdf.dev/v1/usage \
-H "Authorization: Bearer $QUIRE_API_KEY"
Send your key as a Bearer token on render, validate, usage, keys and billing calls. Health, the template catalog and sign-in need no key. Keys start with qk_live_; get one with npx quirepdf-cli login [email protected] (see Accounts and billing).
A missing, revoked or mistyped key returns 401 unauthorized.
Endpoints
| Method | Path | Key | What it does |
|---|---|---|---|
POST |
/v1/render |
yes | Render data; the template is optional |
POST |
/v1/render/{name} |
yes | Render with a named template |
POST |
/v1/validate |
yes | Check data without rendering (free) |
GET |
/v1/templates |
no | List templates |
GET |
/v1/templates/{name} |
no | One template’s metadata, JSON Schema and sample data |
GET |
/v1/health |
no | Engine status |
GET |
/v1/usage |
yes | Plan and renders used this month |
GET, POST |
/v1/keys |
yes | List or create API keys |
DELETE |
/v1/keys/{id} |
yes | Revoke a key |
POST |
/v1/login |
no | Start a sign-in (email link + code) |
POST |
/v1/billing/checkout |
yes | Get a hosted checkout URL for a paid plan |
Render
curl "https://api.quirepdf.dev/v1/render?template=invoice" \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"number": "INV-2026-0142",
"issued": "1 Oct 2026",
"due": "15 Oct 2026",
"seller": { "name": "Northwind Studio", "address": ["221 Market Street", "San Francisco, CA 94105"] },
"customer": { "name": "Ravi Kumar", "address": ["14 Residency Road", "Bengaluru 560025"] },
"items": [{ "description": "Pro plan", "qty": 1, "unit_price": 49 }]
}' \
-D - -o invoice.pdf
HTTP/1.1 200 OK
content-type: application/pdf
x-render-ms: 6.1
x-pages: 1
x-template: invoice
x-template-source: explicit
x-quota-limit: 100
x-quota-remaining: 99
The request body is the document data: one JSON object, at most 1 MB. POST /v1/render/invoice does the same as POST /v1/render?template=invoice.
Query parameters
| Parameter | Values | Default | Notes |
|---|---|---|---|
template |
a template name, e.g. invoice |
none | Omit it to auto-detect. Naming one validates strictly against its schema. |
format |
pdf, png |
pdf |
png returns a single page |
page |
1, 2, … | 1 |
PNG only. Sending page without format=png is a 400 bad_request. |
How the template is chosen
- Explicit: you named a template. Your data must pass its schema, or you get
422 invalid_datawith every problem listed. - Detected: your data passes one or more gallery schemas. The template with the most required fields wins.
- Fallback: the generic
documentlayout, which renders any JSON object.
When the result is a fallback but a template almost matched (two problems or fewer), x-template-hint names it and says what’s missing. Templates and layout explains detection and the generic layout in detail.
Response headers
| Header | Example | Meaning |
|---|---|---|
content-type |
application/pdf |
application/pdf or image/png |
x-template |
invoice |
The template that rendered the document |
x-template-source |
detected |
explicit, detected or fallback |
x-template-hint |
invoice (seller is required) |
Only on fallback, when a template almost matched. Non-ASCII characters show as ?; /v1/validate returns the exact text. |
x-pages |
3 |
Page count of the whole document, also for PNG |
x-render-ms |
6.1 |
Time spent rendering on the server |
x-quota-limit |
2000 |
Your plan’s monthly render limit |
x-quota-remaining |
1873 |
Renders left this month, after this one |
PDF or PNG
curl "https://api.quirepdf.dev/v1/render?format=png&page=2" \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d @report.json -o report-p2.png
Use PNG for previews, thumbnails and visual tests. Each call returns one page at 2× scale (an A4 page is 1190 × 1684 pixels). Read x-pages from the first response to know how many pages there are. Asking for a page that doesn’t exist returns 400 bad_request. A PNG render counts against your quota the same as a PDF.
Pages are A4. The certificate template is landscape A4 and always one page.
Validate (free)
curl "https://api.quirepdf.dev/v1/validate?template=invoice" \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"number":"INV-1","issued":"1 Oct 2026","due":"15 Oct 2026","customer":{"name":"Ravi Kumar","address":[]},"items":[]}'
{
"valid": false,
"template": "invoice",
"source": "explicit",
"hint": null,
"errors": [
{ "path": "seller", "message": "seller is required" },
{ "path": "items", "message": "items needs at least 1 item" }
]
}
/v1/validate resolves the template exactly as /v1/render would and checks the data against it, without rendering. It returns 200 whether or not the data is valid, so read valid. Without ?template=, it tells you which template auto-detection would pick, plus any near-miss hint.
Validation is free: it never counts against your quota. It still needs an API key.
Templates
curl https://api.quirepdf.dev/v1/templates/receipt
{
"template": {
"name": "receipt",
"title": "Receipt",
"category": "billing",
"description": "Proof-of-payment receipt with an amount-paid panel and PAID badge, …",
"tags": ["receipt", "payment", "billing", "saas", "ecommerce"],
"version": "1.0.0"
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["number", "paid_on", "seller", "customer", "payment", "items"],
"properties": {
"number": { "type": "string", "minLength": 1, "description": "Receipt number, e.g. RCPT-2026-0388" }
}
},
"sample": { "number": "RCPT-2026-0388", "paid_on": "3 Oct 2026" }
}
This response is trimmed: the real one lists every property in schema and a complete sample.
GET /v1/templates returns {"templates": [...]} with the metadata of all seven templates. GET /v1/templates/{name} adds the full JSON Schema (draft 2020-12) and a complete, valid sample you can render as is. An unknown name returns 404 unknown_template.
Health
curl https://api.quirepdf.dev/v1/health
{ "ok": true, "templates": 7, "accounts": true, "version": "0.1.0" }
Limits
| Limit | Value | Error |
|---|---|---|
| Request body | 1 MB | 413 payload_too_large |
| Render time | 5 seconds | 504 timeout |
| Document length | 200 pages | 422 too_many_pages |
| Field errors per response | 50 | (the list is cut at 50) |
For scale: a 14-page invoice with 300 line items renders in about 48 ms (measured on a laptop, October 2026), so the time limit only stops runaway documents.
Quotas and rate limits
Each plan has a monthly render quota, counted per account in calendar months (UTC):
| Plan | Renders per month |
|---|---|
| Free | 20 |
| Starter | 2,000 |
| Pro | 10,000 |
| Scale | 50,000 |
Only successful renders count. A request that fails with any error, every /v1/validate call, and every test render is free. When the quota runs out, renders return 429 quota_exceeded until the next month or until you upgrade.
Test renders
Add test=true to /v1/render or /v1/render/{name} for a free test render: the same document with a “TEST RENDER · quirepdf.dev” watermark on every page. Test renders are unlimited on every plan, aren’t counted, and keep working after the quota runs out, so use them while you build. The response carries x-quire-test: true and no quota headers. Renders made with the playground’s demo key are always watermarked.
curl "https://api.quirepdf.dev/v1/render?test=true" \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d @order.json -o order-test.pdf
Each API key can make 10 render or validate calls a second, with bursts of up to 20. Past that you get 429 rate_limited with a Retry-After header in seconds. Rate-limited calls don’t count against your quota. The shared demo key that powers the website playground allows 20 renders an hour per visitor.
Account endpoints
Usage, keys, sign-in and checkout are covered in Accounts and billing. Every error type is listed in Errors.