# Errors

> Every Quire API error type with its HTTP status, what causes it and how to fix it, plus the field-path format used for data validation errors.

Every error is JSON with the same shape:

```json
{
  "error": {
    "type": "invalid_data",
    "message": "data has 2 problems",
    "fields": [
      { "path": "seller", "message": "seller is required" },
      { "path": "items[0].qty", "message": "items[0].qty must be a number" }
    ]
  }
}
```

Branch on `type`, which is stable. `message` is written for people and may change wording. `fields` appears only on `invalid_data`.

## Error types

| Type | Status | When it happens | How to fix it |
|---|---|---|---|
| `invalid_json` | 400 | The body isn't valid JSON. The message gives the line and column. | Send the body through a JSON serialiser instead of building it by hand. |
| `bad_request` | 400 | A parameter is wrong: an unknown `format`, `page` without `format=png`, a page past the end of the document, a malformed sign-in or checkout body. | Read the message; it names the problem. |
| `disposable_email` | 400 | Sign-in with an address from a disposable-inbox service. | Use a permanent email address. |
| `unauthorized` | 401 | The `Authorization` header is missing, or the key is wrong or revoked. | Send `Authorization: Bearer qk_live_…`. Check the key with `npx quirepdf-cli usage`. |
| `bad_signature` | 401 | A billing webhook failed its signature check. | Only the billing provider calls this endpoint; you won't see it in your own code. |
| `unknown_template` | 404 | The template you named doesn't exist. | Check the spelling against `GET /v1/templates`, or leave `template` out. |
| `not_found` | 404 | You tried to revoke a key id that isn't an active key on your account. | List your keys with `GET /v1/keys` and use an `id` from there. |
| `no_subscription` | 404 | You opened the billing portal on an account that has never had a paid plan. | Start one with `npx quirepdf-cli upgrade`. |
| `not_enabled` | 404 | An account endpoint was called on an engine running without accounts. | You won't see this on `api.quirepdf.dev`. |
| `already_subscribed` | 409 | You started a checkout while already on a paid plan. | Change plans or cancel in the billing portal: `npx quirepdf-cli billing`. |
| `last_key` | 409 | You tried to revoke your only active key. | Create a new key first, then revoke the old one. |
| `login_gone` | 410 | A sign-in poll for a login that expired (15 minutes), was cancelled after five wrong codes, or whose key was already collected. | Start again with `npx quirepdf-cli login`. |
| `payload_too_large` | 413 | The body is larger than 1 MB. | Send less data: drop unused fields, or split the document. |
| `invalid_data` | 422 | The data doesn't match the template's schema, or the body isn't a JSON object. | Fix each entry in `fields`. Call `/v1/validate` to check for free. |
| `too_many_pages` | 422 | The document would be longer than 200 pages. | Split the data into several documents. |
| `template_error` | 422 | The template failed to compile with your data. | This points to a bug in a template, not in your request. Send us the data that triggers it. |
| `quota_exceeded` | 429 | You've used this month's renders. | Upgrade with `npx quirepdf-cli upgrade`, or wait for the next calendar month (UTC). Test renders (`test=true`) keep working meanwhile. |
| `rate_limited` | 429 | Too many requests in a short time: more than 10 a second (bursts of 20) on one API key, more than 10 sign-ins an hour from one network or 5 for one mailbox, or the playground's demo key past 20 renders an hour. Rate-limited calls don't count against your quota. | Wait the number of seconds in the `Retry-After` header, then retry. For the playground, get your own free key with `npx quirepdf-cli login`. |
| `export_error` | 500 | Writing the PDF or PNG failed after layout. | Retry once. If it keeps failing, send us the data. |
| `internal` | 500 | Something failed on our side. | Retry with a short backoff. |
| `billing_not_configured` | 501 | Checkout isn't available on this server. | Contact support if you see it on `api.quirepdf.dev`. |
| `billing_unavailable` | 502 | The payment provider didn't respond while starting checkout or opening the billing portal. | Try again in a minute. |
| `timeout` | 504 | Rendering took longer than 5 seconds. | Send fewer rows, or split the document. |

Failed requests never count against your quota. Only a `200` render does.

## Field errors

`invalid_data` lists every problem at once, up to 50, so you can fix them in one pass. Each entry has a `path` and a `message`:

| Path | Points to |
|---|---|
| `seller` | The top-level `seller` key |
| `customer.name` | `name` inside `customer` |
| `items[0].qty` | `qty` in the first element of `items` |
| `(root)` | The body itself |

The message always starts with the path, so you can show it to a person as is. These are the messages you'll see:

| Message | Meaning |
|---|---|
| `seller is required` | A required field is missing |
| `items[0].qty must be a number` | Wrong type (also `a string`, `an object`, `an array`, `a boolean`) |
| `status must be one of: draft, due, paid, overdue, void` | Not one of the allowed values |
| `accent has an invalid format` | Doesn't match the expected pattern, e.g. `#3b5bdb` |
| `items needs at least 1 item` | Array too short (or `can have at most N items`) |
| `tax.rate must be at most 1` | Number out of range (also `at least`, `greater than`, `less than`) |
| `seller.name must be at least 1 character long` | String too short or too long |

When there's exactly one problem, `message` is that problem. With several, it's `data has N problems`.

## Examples

A request naming the invoice template, missing four fields and with a text quantity:

```bash
curl "https://api.quirepdf.dev/v1/render?template=invoice" \
  -H "Authorization: Bearer $QUIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"number":"INV-1","items":[{"description":"Pro plan","qty":"two","unit_price":49}]}'
```

```json
{"error":{"type":"invalid_data","message":"data has 5 problems","fields":[
  {"path":"issued","message":"issued is required"},
  {"path":"due","message":"due is required"},
  {"path":"seller","message":"seller is required"},
  {"path":"customer","message":"customer is required"},
  {"path":"items[0].qty","message":"items[0].qty must be a number"}]}}
```

The body must be an object. Sending an array:

```json
{"error":{"type":"invalid_data","message":"(root) must be an object","fields":[
  {"path":"(root)","message":"(root) must be an object"}]}}
```

A document that runs too long:

```json
{"error":{"type":"too_many_pages","message":"document has 325 pages; the limit is 200"}}
```

A misspelt template:

```json
{"error":{"type":"unknown_template","message":"no template named \"invoce\""}}
```

## Errors in the SDKs and CLI

The SDKs raise one error class, `QuireError`, with `status`, `type`, `message` and `fields` taken from the response above. They add client-side types with `status` 0:

| Type | Meaning |
|---|---|
| `network` | The API couldn't be reached |
| `timeout` | No response within the client timeout (30 s by default). The API's own `timeout` has status 504. |
| `aborted` | TypeScript only: your `AbortSignal` cancelled the request |
| `http_error` | The server replied with an error that wasn't JSON, such as a proxy page |

The SDKs never retry. The [CLI](/docs/cli) prints the message and every field problem, and exits with code 1. With `--json` it prints the error object.