Errors
Failures are JSON with "ok": false and a message written for
a human. The status code tells you whether retrying can possibly help.
{"ok": false, "error": "Human-readable message", "code": "machine_readable"}
Status codes
| Code | Cause | Retry? |
|---|---|---|
| 400 | Malformed request: bad URL, too many URLs, a value out of range | No — fix the request |
| 401 | Missing, invalid, expired or revoked key | No |
| 403 | Valid key, missing scope | No |
| 404 | No such resource — or it is not yours | No |
| 409 | Wrong state, e.g. HTML requested for a task that is not DONE | After the state changes |
| 429 | Rate limit exceeded | Yes, after Retry-After |
| 503 | No capacity, or a circuit is open for that host | Yes, after Retry-After |
| 500 | A defect on our side | Once, then tell us |
Task failures are not HTTP failures
A job can return 201 and still contain tasks that end
FAILED. The HTTP code describes your request; the task status
describes the page. Always read failedTasks.
Common task errors
| Error | What happened | What to do |
|---|---|---|
signal:access denied | The site served a block page | Try another country; the site may simply refuse automation |
Execution context was destroyed | The page navigated while being read | Raise postLoadDelayMs to 3000–5000 |
Navigation timeout | The page never finished loading | Raise navigationTimeoutMs, or set blockAssets true |
expectedText not found | The page loaded without your marker | Working as intended — a wrong page was rejected |
No eligible Worker… | No worker can serve that host and country | Drop geo, or ask us to add capacity |
Blocked pages fail on purpose
A challenge page usually arrives as HTTP 200 with a body
that contains no content. Storing that as a success would hand you a file
that looks fine and is worthless, so we detect the block, quarantine the
exit IP and retry elsewhere. If every attempt is blocked the task ends
FAILED — which is the honest answer.
The strongest guard you control is expectedText: name a
string that only the real page contains, and anything else is rejected
before it reaches you.
A retry policy that works
429, 503 → wait Retry-After, then retry
500 → retry once after a few seconds
400,401,403,404,409 → do not retry; the next attempt fails identically
Per-task retries are already handled server-side by
maxAttempts, including rotating to a different exit IP. You
do not need to resubmit a job because some tasks failed — check whether
the attempts were exhausted first.