2xx means the page was delivered, 4xx points at the request or at the target site, and 5xx means a problem on our side or a request that ran out of time. Only a few responses are billed - they are marked in the table; everything else is free.
Status codes
Dead URLs: X-Origin-Status
When the target site itself answers that the page does not exist (404 or 410), that answer is the result for your URL - we reached the site, the URL is just dead. ScrapeUnblocker returns the target’s own status (404 or 410) with the page exactly as the target served it (the body can be empty; with get_cookies=true it is the usual JSON, and with parsed_data=true the body is {"data": {"page_type": "not_found", "data": {}}} - there is nothing to extract from a not-found page) and adds:
These calls are billed like any delivered page, and retrying will not change the answer.
X-Origin-Status is what tells the target’s 404 apart from a 404 of our own (a wrong API path) - branch on it. On /getImage a dead image comes back the same way, with an empty body:
5xx error page (for example nginx 502 Bad Gateway), you get a 200 with that page, X-Origin-Status carrying the origin’s status and X-Upstream-Unavailable: true, also billed. It stays a 200 on purpose: most HTTP clients retry 5xx automatically, and every retry would be billed.
The target’s own 401 (the page needs a login), 451 (unavailable for legal reasons) and a 500 with no body at all come back the same way, once a second exit has seen the same answer: a 200 with the body exactly as the target served it (often empty) and X-Origin-Status: 401, 451 or 500, billed. They are never passed through as the status itself - our own 401 always means a problem with your API key, and our own 500 an error on our side.
Until 2026-09-30 a target’s
404 / 410 also came back as a 200 with X-Origin-Status. Code that checks r.status_code == 200 first should now handle 404 / 410 with the header as a dead URL, not as an error to retry.400 Hostname cannot be resolved instead, also billed.
Is it the target, or is ScrapeUnblocker down?
If you run failover to another provider, only these mean a problem on our side: connection errors, read timeouts,500, 502, 503, and 504 when it hits many different domains at the same time. All other codes above describe the target site or the request.
- A call never hangs indefinitely: if our gateway is unreachable you get a connection error, and if no scraping capacity is available you get an immediate
503. A healthy call takes at most its time cap plus up to 60 s of queueing when all capacity is busy. Use a connect timeout of about 10 s and a read timeout of about 125 s. - Decide on a rate, not on a single response - one failed call is often one difficult domain. For example, switch over when at least half of the calls in the last minute (at least 20 calls, across at least 3 domains) end with one of those signals, and switch back once probe calls succeed.
- Responses our gateway generates itself (
502,503,504) are never billed.
401 unauthorized
A401 means the request was rejected at our edge, before it ever reached the scraping engine. Because nothing was scraped, a 401 does not count against your quota and is never billed.
Unlike validation errors, 401 responses have a plain-text body (Content-Type: text/plain), not JSON. There are two distinct messages, and they mean different things:
Unauthorized
x-scrapeunblocker-key header does not match any known key. Common causes:
- A typo, or a truncated copy-paste. Keys are long; make sure the whole value was copied.
- Trailing whitespace or a newline in the header value - especially when the key is read from a file rather than an environment variable.
- An empty header value. Sending
x-scrapeunblocker-keywith nothing after it counts as an unknown key, not as a missing header. - A rotated or revoked key. After you generate a new key in the dashboard, the old one stops working once its short grace period ends.
- Environment mismatch. Production keys only work against
api.scrapeunblocker.com. A key issued for one environment sent to another is an unknown key there.
No valid subscription
A billing problem on an active subscription returns
402, not 401 - quota exceeded, credit limit exceeded, or a card that failed repeatedly. 401 is strictly about who you are, 402 about what you owe.Missing header returns 400, not 401
If you omit the x-scrapeunblocker-key header entirely, the response is 400 Bad Request with the body Missing x-scrapeunblocker-key. This is deliberate: it separates “you forgot to authenticate” from “you authenticated, and it was rejected”, so client code can tell a wiring bug from a credential problem.
402 payment required
A402 means your key and account are recognised and in good standing as credentials - the request was stopped for a billing reason. Like 401, it is refused at our edge before anything is scraped, so a 402 consumes no quota and is never billed.
The body is plain text (Content-Type: text/plain), not JSON. There are four messages:
If more than one applies, the most serious wins: payment failure outranks credit limit, which outranks quota, which outranks your own budget limit.
All four clear themselves. Our gateway refreshes key statuses about once a minute, so once you upgrade, the invoice is paid or you raise your budget limit, access comes back within roughly a minute - no key change, no support ticket, no redeploy.
Quota exceeded
402 you can see coming.
Credit limit exceeded
User set budget exceeded
0 for no limit.
The key works again at the start of your next billing period, or within about a minute after you raise or remove the limit. Usage is counted with a delay of a few minutes, so the period’s spend can end up slightly above the limit.
Payment failed - update payment method
402 versus its neighbours
422 validation error shape
When you send an invalid request body,/getPageSource, /serpApi, and /getImage all return a structured validation error:
loc is the path to the problem field. msg is human-readable. type is a stable machine-readable identifier.
Restricted targets
ScrapeUnblocker does not fetch sign-in or payment surfaces or government sites, and it does not fill credential or payment forms. Three refusals enforce this. All are returned at our edge (or before any action runs), are never billed, and carry anX-SU-Error-Code header so you can tell them apart from an
ordinary bot block.
A
restricted_target refusal is a plain-text 403:
restricted_action refusal is a JSON 403 naming the step:
/payment-policies or /how-to-login-safely is fetched normally - only the login
or payment surface itself is refused. Ordinary steps into search boxes, filters
and forms are unaffected. If you have a legitimate need for such a target, contact
support.
403 - blocked vs. invalid key
A403 from ScrapeUnblocker never means your API key is wrong. Invalid keys return 401. Otherwise a 403 is one of two things, which the X-SU-Error-Code header tells apart: a restricted target (the header is set), or the target site blocked us on every bypass route we tried (no such header).
When you see a 403 with no X-SU-Error-Code (a bot block):
- Try a different
proxy_country. Some sites geo-fence or geo-rotate their bot protection. A US site may be unreachable from EU IPs and vice versa. - Wait and retry. Rate-based blocks expire after a few minutes.
- Contact support if the same URL repeatedly fails - we may need to add a custom plugin for that domain.
403 with an X-SU-Error-Code is a policy refusal, not a bot block - retrying or changing proxy_country will not help. See restricted targets.
More detail in the handling failures guide.
Retries and idempotency
All three endpoints are safe to retry. Requests are idempotent in the sense that retrying with the same parameters does not double-charge or create duplicate state on your account. We recommend exponential backoff for transient5xx errors and 523 (failed calls are free, so a retry costs nothing extra):

