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.

View this page as Markdown