internal_error
HTTP 500 · not billed
{ "error": {
"code": "internal_error",
"message": "…",
"docs_url": "https://pdfcraft.dev/errors#internal_error"
} }When you get it
Something on our side broke.
Something on our side broke: a bug, a dependency that stopped answering, or a resource that ran out. It is never a statement about your request — anything wrong with a request is rejected at the boundary as a 400, and a page that fails to render is a 422. A 500 means we did not get far enough to have an opinion about your input, so resending the identical body is a reasonable thing to do.
How to fix it
- Retry with exponential backoff and jitter. Plain fixed-interval retries from many clients at once turn one bad minute into a longer one.
- Send an Idempotency-Key. A 500 can be returned after the work already happened — the connection dropped, or the response was lost on the way back — and the key is what stops the retry producing a second billable render.
- Never billed, whatever stage it failed at. If you see a usage count move on a request that returned 500, that is itself a bug worth reporting.
- If it persists, send the render id from the response. It is in our log next to the actual cause, which is the thing the generic message deliberately does not expose.
Is it billed?
No. A request is billed only when Chromium actually ran. Rare enough that a run of them is a signal worth reporting rather than routing around. The common exception is a burst immediately after a deploy on our side; if a retry a minute later succeeds, that was it.
Reproducing it
curl -i -X POST https://api.pdfcraft.dev/v1/render \
-H "Authorization: Bearer $PDFCRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>hi</h1>"}'The -i matters: Retry-After and X-Renders-Remaining are headers, and a client that only reads the body throws away the two numbers that tell it what to do next.
Every code
| HTTP | Code | Billed? |
|---|---|---|
| 400 | invalid_request | free |
| 401 | invalid_api_key | free |
| 402 | payment_required | free |
| 404 | not_found | free |
| 408 | render_timeout | free |
| 422 | render_failed | billed |
| 429 | rate_limited | free |
| 429 | quota_exceeded | free |
| 429 | demo_busy | free |
| 415 | unsupported_file | free |
| 422 | extraction_failed | free |
| 500 | internal_error | free |