Skip to main content
Every error returns the same JSON shape with a standard HTTP status. There’s one thing to parse, whatever went wrong.
number
The HTTP status, mirrored in the body for convenience.
string
A stable, machine-readable code. Branch on this — the wording of message may change; the code won’t.
string
A human-readable explanation of what went wrong.
Failed calls are free. Because credits are charged only on success, an error never costs you anything — see Credits.

Statuses

The request body didn’t pass validation — a missing url, a non-http scheme, an out-of-range maxAge, or requesting the json format without jsonOptions. Fix the request and retry.
The X-API-Key header is missing, malformed, revoked, or the key has expired. The check runs before any work, so a rejected request never costs credits. See Authentication.
Your workspace doesn’t have enough credits to cover the call. It’s rejected up front, before any work runs, so nothing is charged. Top up and retry.
The key is valid, but it is locked to IP addresses and the call came from another one. The message names the address we saw, so you can add it under the key’s allowed IPs in the dashboard. Nothing is charged. See API keys.
The resource doesn’t exist — an unknown job, crawl, or batch id — or a cacheOnly scrape found no fresh capture to serve. A cacheOnly miss is expected and never triggers a live fetch; see Caching.
The target site did not respond, or we could not read that page. This says something about that URL rather than about our service, and retrying it will not change the answer. Nothing was charged.When details.retryAfterSeconds is present, asking again before then returns this same answer straight away. After that, a new request makes a fresh attempt.
You’ve sent requests faster than your allowance. Back off and retry after a short delay, ideally with exponential backoff.
The page ran past the 90-second synchronous budget. The fetch keeps going in the background, so the capture usually lands in the cache and an immediate retry returns it quickly. Pages that hit this are almost always behind heavy bot protection. If you would rather not wait at all, send async: true and poll the job instead.
Something went wrong on our side, or the target page couldn’t be delivered within the time budget. These are safe to retry — the failed call wasn’t charged.

Handling errors

Read code to decide what to do, not the HTTP status alone:
  • VALIDATION_ERROR — fix the request; retrying as-is won’t help.
  • UNAUTHORIZED — check the key, and whether it has expired.
  • FORBIDDEN — add the address in the message to the key’s allowed IPs, or call from an address already on it.
  • INSUFFICIENT_CREDITS — top up, then retry.
  • NOT_FOUND — for cacheOnly, fall back to a live fetch if you want the page.
  • REQUEST_TIMEOUT — retry once; the background fetch usually makes the second call fast. Use async: true if you would rather not wait.
  • UPSTREAM_UNREACHABLE — the site is not answering or the page could not be read; do not retry it before retryAfterSeconds.
  • TOO_MANY_REQUESTS and 5xx — retry with exponential backoff.
Job-level failure is different from a request error. A crawl or batch returns 200 while individual pages may fail — check each page’s status and error in the job’s pages array, and the job’s own status for failed or cancelled. See Jobs & webhooks.