> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Resolve common response codes

> Fix each common Zenrows Fetch response code: 422, 499, 429, 402, 413, 400, 401, 404 and 410, with one function that handles them all.

Every Zenrows error response tells you what to change: an HTTP status, a `code`, and a `detail` sentence in a JSON body. This page maps each common code to the change that gets you the page, then puts the rules into one function you can drop into your scraper.

**Uses:** [Fetch](/fetch/api-reference) with [Adaptive Stealth Mode](/fetch/features/adaptive-stealth-mode) (`mode=auto`), [`allowed_status_codes`](/fetch/features/other#return-content-on-error), the [`Concurrency-Limit` header](/fetch/features/concurrency) and the [API error codes](/api-error-codes)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use Zenrows to read https://www.scrapingcourse.com/ecommerce/product/no-such-product/.
If the request returns an error, tell me the status, the error code, what it means,
and the exact change that gets the page. Then check my Zenrows credits.
```

## Read the error body

Every error is an <a href="https://www.rfc-editor.org/rfc/rfc7807" rel="nofollow">RFC 7807</a> JSON object. `code` names the cause and `type` links to its entry in the [error code reference](/api-error-codes). This request combines `js_render` with `mode=auto` on purpose, to show an error body:

```bash cURL theme={"dark"}
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fhttpbin.io%2Fanything&mode=auto&js_render=true"
```

```json theme={"dark"}
{
  "code": "REQS004",
  "detail": "Adaptive Stealth Mode ('mode=auto') picks rendering and proxies for you. Remove 'js_render' and 'premium_proxy' to keep it, or remove 'mode=auto' to set them yourself.",
  "instance": "/v1",
  "status": 400,
  "title": "Js_render and premium_proxy cannot be combined with mode=auto (REQS004)",
  "type": "https://docs.zenrows.com/api-error-codes#REQS004"
}
```

Log `code` with the URL on every failure, so you can group failures by cause and apply one fix per group.

## Quick reference

| Status | Codes | Retry? | Fix |
| - | - | - | - |
| [`422`](/api-error-codes#422) | [RESP001](/api-error-codes#RESP001) | Yes | Use `mode=auto`, or `js_render` + `premium_proxy` with `wait_for` |
| [`499`](/api-error-codes#499) | [CTX0001](/api-error-codes#CTX0001) | Yes | Set the client timeout to 180 seconds or more |
| [`429`](/api-error-codes#429) | [AUTH006](/api-error-codes#AUTH006), [AUTH008](/api-error-codes#AUTH008) | Yes, with backoff | Cap concurrent requests at `Concurrency-Limit` |
| [`402`](/api-error-codes#402) | [AUTH004](/api-error-codes#AUTH004), [AUTH014](/api-error-codes#AUTH014) | After the fix | Check usage, then top up, upgrade or raise the key's cap |
| [`413`](/api-error-codes#413) | [RESP005](/api-error-codes#RESP005) | After the fix | Ask for less: `css_extractor`, `outputs` or `response_type=markdown` |
| [`400`](/api-error-codes#400) | [REQS002](/api-error-codes#REQS002), [REQS004](/api-error-codes#REQS004), [RESP004](/api-error-codes#RESP004) | After the fix | Correct the parameters the `detail` names |
| [`401`](/api-error-codes#401) | [AUTH001](/api-error-codes#AUTH001), [AUTH002](/api-error-codes#AUTH002), [AUTH003](/api-error-codes#AUTH003) | After the fix | Send a valid API key |
| [`404`](/api-error-codes#404), `410` | [RESP002](/api-error-codes#RESP002), [RESP007](/api-error-codes#RESP007) | No | The target page or domain is gone: fix or drop the URL, or keep the page with `allowed_status_codes` |

## 422: get the page with a stronger configuration

`RESP001` means that attempt returned no usable content. Let Zenrows choose the configuration:

* **Send `mode=auto`.** Adaptive Stealth Mode adds JavaScript rendering and premium proxies only when a page needs them, and bills only the configuration that succeeds.
* **For full control,** send `js_render=true` and `premium_proxy=true`, add `wait_for` with a selector of the content you need, and `proxy_country` when the site serves one region.
* **Retry with backoff.** A 422 is retryable, and requests that don't succeed aren't charged.

For Extract requests, `no_data_extracted` (also 422) means the page had no values for Extract's fields. Make sure it rendered fully (`js_render=true` with `wait_for`, instead of `mode=auto`), or omit `extract_fields`. See [no\_data\_extracted](/api-error-codes#no_data_extracted).

## 499: give the request time to finish

`CTX0001` (HTTP 499) means your client closed the connection before Zenrows returned the page, usually because the client's timeout fired. Set the timeout to 180 seconds or more on every Fetch call, for example `requests.get(..., timeout=180)` in Python or `AbortSignal.timeout(180_000)` in Node.js. Requests that end in 499 aren't charged.

## 429: match your plan's concurrency

`AUTH006` means more requests were in flight than your plan runs at once. Every response carries `Concurrency-Limit` and `Concurrency-Remaining` headers. Size your worker pool to the limit:

```python Python (Requests) theme={"dark"}
from concurrent.futures import ThreadPoolExecutor

import requests

API_KEY = "YOUR_ZENROWS_API_KEY"
urls = [f"https://www.scrapingcourse.com/ecommerce/page/{n}/" for n in range(1, 4)]

def fetch(url):
    params = {"apikey": API_KEY, "url": url, "mode": "auto"}
    response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
    return url, response.status_code, response.headers.get("Concurrency-Limit")

# Read your plan's limit from any response, then never run more requests at once
_, _, limit = fetch(urls[0])
workers = int(limit or 5)
print("Concurrency-Limit:", workers)

with ThreadPoolExecutor(max_workers=workers) as pool:
    for url, status, _ in pool.map(fetch, urls):
        print(status, url)
```

```text theme={"dark"}
Concurrency-Limit: 5
200 https://www.scrapingcourse.com/ecommerce/page/1/
200 https://www.scrapingcourse.com/ecommerce/page/2/
200 https://www.scrapingcourse.com/ecommerce/page/3/
```

Keep client timeouts at 180 seconds here too: a request your client abandons keeps its slot until Zenrows finishes it. For thousands of URLs, a [Batch](/academy/scrape-from-a-list-of-urls) job manages concurrency for you.

## 402: check credits and caps

`AUTH004` means the plan's credits for this billing period are used. `AUTH014` means this API key reached a [credit cap](/first-steps/api-key-credit-caps) you set, while your other keys keep working. Check where you stand, free and without using a concurrency slot:

```bash cURL theme={"dark"}
curl -H "X-API-Key: YOUR_ZENROWS_API_KEY" https://api.zenrows.com/v1/subscriptions/self/details
```

The response includes `usage_credits`, `credit_limit`, `usage_percent`, `period_ends_at` and, under `api_key.caps`, any caps on this key. To continue right away, top up on the [Billing page](https://app.zenrows.com/billing), upgrade on the [Plans page](https://app.zenrows.com/plans), or raise the key's cap on the [API keys page](https://app.zenrows.com/settings/api-keys). See [Track usage and control spend](/academy/production/control-costs).

## 413: ask for the part of the page you need

`RESP005` means the page is larger than one response carries. Request less of it:

| Need | Parameter |
| - | - |
| A few fields | `css_extractor={"title":"h1","price":".price"}`, see [CSS extractor](/fetch/features/css-extractor) |
| Links, tables or other common data | `outputs=links,tables`, see [Output filters](/fetch/features/output-filters) |
| The text, for an LLM | `response_type=markdown`, see [Markdown](/fetch/features/markdown) |
| Structured fields on prepared sites | `extract=auto`, see [Extract](/extract/introduction) |

If you use `json_response=true`, its captured network calls count toward the size too: remove it when you don't need them. More strategies: [Response too large](/fetch/troubleshooting/response-too-large).

## 400 and 401: correct the request

These return the same answer until the request changes, so fix and resend instead of retrying.

* **`REQS004`**: a parameter value or the URL is invalid. The `detail` names it. Two common ones: `js_render` or `premium_proxy` sent with `mode=auto` (keep one approach), and a target URL that isn't URL-encoded.
* **`REQS002`**: the domain needs JavaScript rendering or premium proxies. Send `mode=auto` and Zenrows applies them.
* **`RESP004`**: `css_extractor` isn't valid JSON or has a malformed selector. Validate it with `json.dumps` on a dict.
* **`AUTH001`, `AUTH002`, `AUTH003`**: the key is missing or not recognized. Copy it again from the [API keys page](https://app.zenrows.com/settings/api-keys) and send it as `apikey`.

## 404 and 410: handle pages the target removed

`RESP002` means the target site itself answered 404: the page is gone, so drop or fix the URL. To receive the target's own error page as content instead of an error, add `allowed_status_codes=404,410`. The response is then `200` and the target's status is in the `Zr-Status-Code` header. Like every 404 and 410, these responses are billed as successful requests (see [Pricing](/first-steps/pricing)):

```bash cURL theme={"dark"}
curl -i "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2Fproduct%2Fno-such-product%2F&allowed_status_codes=404,410"
```

```text theme={"dark"}
HTTP/1.1 200 OK
X-Request-Credits: 1
Zr-Status-Code: 404
```

`RESP007` (also 404) means the domain doesn't resolve: check the hostname for typos.

## Handle every code in one function

This function sends `mode=auto`, retries the retryable statuses with backoff, treats pages the target removed as "gone", and raises with the code, detail and docs link for anything that needs a change:

```python Python (Requests) theme={"dark"}
import random
import time

import requests

API_KEY = "YOUR_ZENROWS_API_KEY"
RETRYABLE = {422, 429, 500, 502, 503, 504}

class FixNeeded(Exception):
    """A response that returns the same answer until you change something."""

def fetch(url, **extra):
    params = {
        "apikey": API_KEY,
        "url": url,
        "mode": "auto",                   # escalates rendering and proxies when a page needs it
        "allowed_status_codes": "404,410",  # return the target's own error pages as content
        **extra,
    }
    for attempt in range(4):
        try:
            response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
        except requests.Timeout:
            continue  # retry; keep the timeout at 180 s or more

        if response.ok:
            target_status = int(response.headers.get("Zr-Status-Code", 200))
            if target_status in (404, 410):
                return None  # the page is gone on the target site: drop it from your list
            return response.text

        if response.status_code in RETRYABLE:
            time.sleep(2 ** attempt + random.random())  # 1, 2, 4 s plus jitter
            continue
        error = response.json()
        # 400, 401, 402, 403, 404, 413: change the request, the key or the plan
        raise FixNeeded(f"{response.status_code} {error['code']}: {error['detail']} ({error['type']})")

    raise RuntimeError(f"Still failing after 4 attempts: {url}")

for url in [
    "https://www.scrapingcourse.com/ecommerce/",
    "https://www.scrapingcourse.com/ecommerce/product/no-such-product/",
]:
    body = fetch(url)
    print(url, "->", "gone" if body is None else f"{len(body)} bytes")

try:
    fetch("https://www.scrapingcourse.com/ecommerce/", js_render="true")
except FixNeeded as error:
    print(error)
```

## Example output

```text theme={"dark"}
https://www.scrapingcourse.com/ecommerce/ -> 82485 bytes
https://www.scrapingcourse.com/ecommerce/product/no-such-product/ -> gone
400 REQS004: Adaptive Stealth Mode ('mode=auto') picks rendering and proxies for you. Remove 'js_render' and 'premium_proxy' to keep it, or remove 'mode=auto' to set them yourself. (https://docs.zenrows.com/api-error-codes#REQS004)
```

The last call adds `js_render` on top of `mode=auto` on purpose, to show how a request that needs a fix surfaces.

## Customize it

| Change | How |
| - | - |
| More retries or longer waits | Change `range(4)` and the backoff base, see [Retry failed requests](/academy/retry-failed-requests) |
| Built-in retries instead of your own loop | Use the SDKs' retry option, see [Python retries](/fetch/sdk/python/error-handling-and-retries) |
| Keep other target statuses as content | Add them to `allowed_status_codes`, see [Return content on error](/fetch/features/other#return-content-on-error) |
| Retries for thousands of URLs | Run them as a Batch job and rerun only the failed tasks, see [Scrape a list of URLs](/academy/scrape-from-a-list-of-urls) |

## Related recipes

<CardGroup cols={2}>
  <Card title="Retry failed requests" icon="refresh" href="/academy/retry-failed-requests">
    Backoff, jitter and SDK retries in Python and Node.js.
  </Card>

  <Card title="Track usage and control spend" icon="coin" href="/academy/production/control-costs">
    Read per-request cost and set credit caps.
  </Card>

  <Card title="Scrape pages protected by anti-bot systems" icon="shield-check" href="/academy/fetch/protected-sites">
    Let Adaptive Stealth Mode pick the right configuration per page.
  </Card>

  <Card title="Run many requests concurrently" icon="arrows-split" href="/academy/fetch/concurrent-requests">
    Use your plan's concurrency to its full extent.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.