error code and a human message. Branch on the code — the message is written for a person and may be reworded.
The shape
x-request-id header is present on errors too, including on a 500. Capture it — without it a report of an intermittent failure cannot be traced back to the request that produced it.
Codes
Individual services add codes of their own for a failure that is specific to them —
scrape_failed and fetch_failed are both 502 and both mean the page could not be retrieved. They are documented on each service’s page.
A 400 carries a details object naming what was wrong: {"error":"validation_error","message":"Invalid request body","details":{"issues":["prompt"]}} means the prompt field failed. Only validation errors carry it, and only field names, allowed values and limits — never the values you sent, and never anything about your organization.
What a failure costs
Only work that produced a result is billed. A validation error, an auth failure, a rate limit, a timeout, or an upstream failure costs nothing.
429s without being charged for it. The quantity billed is what the handler actually consumed, which is why a partially successful call (an extraction that needed a retry) reports exactly what it used in the response body.
Retrying
- Retry
429,500,502and503with exponential backoff and jitter. - Do not retry
400,401or403— the same request will fail identically until you change something. - Do not retry
402in a loop. It means spending is blocked until a human adds a card, fixes one, or raises a budget. - Retries are billed like any other request: a successful retry of a failed call is one billable unit, because the failed attempt was free.

