# Render API reference

> Every Quire endpoint, query parameter and response header, with the 1 MB, 5 s and 200-page limits, PNG previews, free validation and monthly quotas.

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

```bash
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 you@example.com` (see [Accounts and billing](/docs/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

```bash
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
```

```text
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

1. **Explicit:** you named a template. Your data must pass its schema, or you get `422 invalid_data` with every problem listed.
2. **Detected:** your data passes one or more gallery schemas. The template with the most required fields wins.
3. **Fallback:** the generic `document` layout, 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](/docs/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

```bash
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)

```bash
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":[]}'
```

```json
{
  "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

```bash
curl https://api.quirepdf.dev/v1/templates/receipt
```

```json
{
  "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

```bash
curl https://api.quirepdf.dev/v1/health
```

```json
{ "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.

```bash
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](/docs/accounts-and-billing). Every error type is listed in [Errors](/docs/errors).