Scraping API error codes explained: what each one means, whether to retry, and what it costs
Every error is a status plus a short code and a sentence. The full list: the cause of each one, whether a retry helps, and which ones refund the credit.
In short
- Errors are JSON: { error: { code, message } }. The code is stable and meant for your if-statements; the message is for humans.
- 4xx codes are about your request or your account and never fix themselves with a retry. 5xx codes are about the target site or the service and often do.
- Only failures caused on this side refund the credit: RENDER_UNAVAILABLE, RENDER_TOO_SLOW, INTERNAL_ERROR and SITE_PARSE_FAILED. Anything the target did is charged, because the fetch was made: a dead or slow site (FETCH_FAILED, TIMEOUT) or a site that blocked the request (SITE_BLOCKED, or blocked on /v1/scrape).
An error you can branch on is worth more than one you can read. Every failure from this API therefore carries a short, stable code next to a plain sentence, and the HTTP status tells you which side the problem is on.
{ "error": { "code": "QUOTA_EXCEEDED", "message": "Daily credit limit reached. Resets at 2026-09-23T00:00:00Z." } }
The full list
| Status | Code | Cause | Retry? | Credit |
|---|---|---|---|---|
| 400 | BAD_REQUEST | a parameter is missing, malformed or out of range; the message names it | no, fix the request | not charged |
| 400 | URL_BLOCKED | the URL targets a private or reserved network, localhost, or a non-http scheme | no | not charged |
| 401 | UNAUTHORIZED | no API key, or a key that does not exist | no, check the key | not charged |
| 403 | FORBIDDEN | the key was revoked, or the account is not allowed to do this | no | not charged |
| 404 | NOT_FOUND | unknown site or action on a structured endpoint, or a site said the item does not exist | no | charged on site endpoints |
| 429 | RATE_LIMITED | more than 60 requests in a minute on one key | yes, after a few seconds | not charged |
| 429 | QUOTA_EXCEEDED | the account’s daily credits are used up | after the reset time in the message | not charged |
| 502 | FETCH_FAILED | the target could not be reached: DNS, connection refused, TLS, too many redirects; a PDF that cannot be read; on a site endpoint, also the target answering with an error status | yes, later | charged |
| 502 | SITE_PARSE_FAILED | the page arrived but the parser could not read it; usually a layout change | yes, and tell the board | refunded |
| 502 | SITE_BLOCKED | a site endpoint got a CAPTCHA or a bot wall instead of the page (/v1/scrape returns that page as it came, with blocked) | yes, after a minute or more | charged |
| 502 | RENDER_UNAVAILABLE | render=true was asked for while the rendering browser is off, failed to start or stayed busy | yes, or drop render | refunded |
| 504 | TIMEOUT | the target did not answer within timeout (default 15 s, max 30 s) | yes, perhaps with a longer timeout | charged |
| 504 | RENDER_TOO_SLOW | the page could not finish rendering within a synchronous request (responses are cut at 60 s) | yes, as a job (POST /v1/jobs) | refunded |
| 500 | INTERNAL_ERROR | a bug on this side | yes; it is logged and looked at | refunded |
The rule behind the credit column
You pay for a fetch that was made on your behalf. If the target site is down, slow, answers with an error, or blocks the request with a CAPTCHA or a bot wall, the fetch was made and the credit stands, exactly as it would if you had made the request yourself. Only when the failure is caused here, because the parser is out of date, the rendering browser is unavailable or cannot finish within a synchronous request, or there is a bug, is the credit returned; that was not something you could have avoided.
Successful responses that carry a target status of 404 or 500 are not errors. /v1/scrape returns 200 with the target’s statusCode in the body, because fetching a page that says “not found” is a successful fetch. A wall comes back the same way (a CAPTCHA, a browser check, a 403 or a 429), with blocked naming which; it is charged like any page the target answered and is not kept as a shared copy for other callers.
A retry policy that works
RATE_LIMITED → wait 2–5 s, retry
SITE_BLOCKED → wait 60 s+, retry once; or accept a stored copy (max_age)
FETCH_FAILED → retry once after 10 s; then give up, the site is down or its file unreadable
TIMEOUT → retry once with a larger timeout (up to 30000)
SITE_PARSE_FAILED → do not loop; post the URL on the board
4xx (others) → fix the request or the key; retrying repeats the error
Log the code, not the message; messages may be reworded, codes will not.
Where errors show up in the dashboard
Every request, successful or not, is a row in the dashboard’s log with its code, status, host and timing, kept for 30 days. If you see a run of SITE_PARSE_FAILED on one site, that is the moment to say so on the board; parsers are fixed once, for everyone. The error reference is the short version of this page.
Questions people ask
- What is the difference between RATE_LIMITED and QUOTA_EXCEEDED?
- RATE_LIMITED is per minute and per key (60 requests); wait a few seconds. QUOTA_EXCEEDED is the daily credit cap for the whole account; it resets at 00:00 UTC and the message says when.
- Should I retry SITE_BLOCKED?
- Later, yes, with a pause of at least a minute; the site is refusing cloud traffic and hammering it makes that worse. For site endpoints, consider accepting a stored copy with a larger max_age instead.
- What does URL_BLOCKED mean?
- The URL points at something the API will not fetch: private networks, localhost, link-local addresses, non-http schemes. It is a safety rule, not a temporary state.
- Is a 4xx from the target site an error?
- On /v1/scrape, no: if the target answers 404 or 403, it returns 200 with the target's statusCode in the body (and blocked for a wall), because the fetch itself succeeded. On site endpoints, yes: a missing item is NOT_FOUND, a 403 or 429 wall is SITE_BLOCKED and any other error status is FETCH_FAILED, all charged because the target caused them.