payment_required
HTTP 402 · not billed
{ "error": {
"code": "payment_required",
"message": "…",
"docs_url": "https://pdfcraft.dev/errors#payment_required"
} }When you get it
The last subscription payment failed, so the account is past_due.
The last subscription payment failed, so the account sits in past_due. Work stops; nothing is lost. It is a distinct code from invalid_api_key on purpose — the key is perfectly valid, and telling you it is not would send you looking in exactly the wrong place.
How to fix it
- Update the card in the billing portal. Rendering resumes the moment payment clears; there is no re-enable step and no support ticket.
- Nothing is deleted while an account is past_due. Keys are not revoked, the plan is not downgraded, render history stays, and already-signed output URLs keep working until their TTL expires.
- Queued async jobs are not silently dropped, but new ones are refused — so a worker that queues on a schedule should treat a 402 as "pause", not as "retry harder".
- Handle it separately from 429 in your client. Both mean stop sending, but a 402 is not going to clear on its own and no amount of backoff will fix it.
Is it billed?
No. A request is billed only when Chromium actually ran. Usually a card expiry rather than a decline, and usually a fortnight after the card expired — the provider retries for several days before the subscription flips, so the first 402 tends to arrive well after the email did.
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 |