Errors
Every error is JSON with the same shape:
{
"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:
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}]}'
{"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:
{"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:
{"error":{"type":"too_many_pages","message":"document has 325 pages; the limit is 200"}}
A misspelt template:
{"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 prints the message and every field problem, and exits with code 1. With --json it prints the error object.