---
title: "API Documentation"
description: "Complete API reference for WebScraping.AI. Learn how to scrape websites, extract data with AI, and integrate with your applications."
url: https://webscraping.ai/docs#errors
markdown_index: https://webscraping.ai/llms.txt
---
## Error Codes

Understanding API error responses and how to handle them.

| Code | Description | Solution |
| --- | --- | --- |
| 400 | Invalid parameters | Check parameter values and format |
| 402 | Insufficient credits | Upgrade your plan, top up pay-as-you-go credits, or wait for credit reset |
| 403 | Invalid API key | Verify your API key is correct |
| 429 | Too many concurrent requests | Reduce request rate or upgrade for higher concurrency |
| 500 | Target page could not be scraped (blocked, anti-bot challenge, non-2xx status, DNS failure), or an unexpected error (`error_code` `internal_error`) | Read `error_code` and follow `next_step` (see below); retry an `internal_error` and contact support if it persists |
| 503 | Temporarily unavailable: all scraping workers are busy, or [`/data`](https://webscraping.ai/docs/data.md) is still learning a new kind of page (`error_code` `structure_processing`). Not charged | Retry after the seconds in the `Retry-After` header |
| 504 | Request timeout | Increase timeout parameter value |

**Good news:** Failed requests don't count against your credit quota. You only pay for successful responses.

### Target errors are structured

When the target website blocks the request or returns an error, the `500` response body tells you what happened and what to try next, with the real credit cost of that retry. Example for a site that blocks datacenter IPs:

```
{
  "message": "The target website returned HTTP 403 (Forbidden) through a datacenter proxy: it is blocking this IP pool (anti-bot protection). Retry with proxy=residential (10 credits per request). Failed requests are not billed.",
  "error_code": "target_blocked",
  "status_code": 403,
  "status_message": "Forbidden",
  "body": "<html><body>Access denied</body></html>",
  "request_parameters": { "proxy": "datacenter", "js": false },
  "next_step": {
    "params": { "proxy": "residential" },
    "cost": 10,
    "message": "Retry with proxy=residential (10 credits per request)."
  }
}
```

The escalation ladder is **datacenter → residential → stealth**. Anti-bot challenge pages go straight to `proxy=stealth`, because they need the hardened browser, not just a better IP. When the request already ran on the stealth tier (or a dedicated route), `next_step` is omitted and the message says the target is currently not reachable; Google search URLs are pointed at the [`/serp` endpoint](https://webscraping.ai/docs/serp.md) instead. Automating the retry is safe: `next_step.params` are ordinary request parameters to set, `next_step.remove` (present only when you used `custom_proxy`, which overrides any proxy setting) lists parameters to drop, and `next_step.cost` is the price per request on current plans if the retry succeeds. If you would rather not automate it yourself, `proxy=auto` walks the same ladder inside one request (see the [proxy parameter](https://webscraping.ai/docs#param-proxy)); its error bodies carry `request_parameters.auto: true` and `request_parameters.tiers_tried`, and `request_parameters.proxy` is the tier the walk ended on.

| `error_code` | Meaning | Next step |
| --- | --- | --- |
| `target_blocked` | Target returned 403, 429, 503 or 999: an IP-reputation block or rate limit | Better proxy tier from `next_step` |
| `target_challenge` | Target served an anti-bot challenge page (Cloudflare, Akamai, PerimeterX, Datadome) instead of content, or closed the browser tab | `proxy=stealth`; a Cloudflare JavaScript challenge hit with `js=false` suggests `js=true` instead (with `proxy=residential` on datacenter) |
| `target_error` | Other non-2xx status from the target (its own 4xx or 5xx) | Check the URL; for 5xx a proxy upgrade is suggested as a hedge |
| `target_redirect` | Redirect returned because `error_on_redirect=true` | Billed as a success; the redirect target is in `message` |
| `target_unreachable` | Hostname does not resolve, or the target dropped or never answered the connection without an HTTP response (range-level blocking) | Check the URL, or the proxy tier from `next_step` |
| `wait_for_timeout` | `wait_for` selector never appeared | Check the selector or increase `timeout` |
| `invalid_url`, `forbidden_target`, `custom_proxy_error` | Malformed URL, a domain/body the API does not serve, or your `custom_proxy` rejected our credentials (407) | Fix the request |
| `response_too_large` | Response over the size limit | Use `/selected` or `/selected-multiple` |
| `ai_unavailable` | `/ai/question` and `/ai/fields` only: the AI model is busy. Not charged | Retry |
| `timeout` (504), `internal_error` | Page did not load in time, or an error on our side | Increase `timeout`; for internal errors retry or contact support |
