Errors
Every failure takes one shape: an HTTP status, a stable code to branch on, and a sentence written for whoever is reading the log. Branch on the code. The sentence may be reworded; the code will not be.
{"error":"this request costs $0.0040 and the account balance will not cover it",
"code":"insufficient_funds"}Every code
What to retry, and how
Both carry Retry-After: one second for going too fast, and the seconds remaining until 00:00 UTC for a spent daily allowance. Honour it rather than backing off blindly — for a daily ceiling, backing off is not going to be enough.
no_session, session_expired, upstream_error and upstream_timeout mean we could not reach the platform for you. None of them is charged. Retry with a backoff; if a whole class of call keeps failing, it is us and not you.
A 400, 401, 402 or 403 will answer the same way every time you send it. Retrying is only a way to spend your daily allowance on the same mistake.
Three that read alike and are not
401 unauthenticated is every key problem there is — missing, malformed, unknown id, wrong secret. One answer for all four, on purpose: probing the endpoint should not tell you whether an id exists.
403 key_revoked means stop retrying and reissue. 403 not_on_plan means your plan carries no price for that class of request, so it is refused rather than served free.
503 no_session and 503 session_expired are the same condition found at two different moments — we cannot reach the platform for you right now. Neither is charged, and neither means your request was wrong.
And a 200 that is not one
The platform reports some of its own failures inside an HTTP 200. A body with a non-zero status_code is one of those, and it is a successful call as far as we are concerned — we asked, and they answered. Check the body’s own status fields as well as ours.
Only a 2xx is billed. Every code on this page is recorded in your usage at $0.00 — see billing and limits for what does cost, and what still counts against the day.