> ## 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.

# Error Handling and Retries with the Python SDK

> Configure automatic retries in the ZenRows Python SDK and handle failed requests reliably.

Web scraping requests occasionally fail for transient reasons, like a rate limit or a temporary server hiccup. The SDK can retry these automatically so you don't have to write that logic yourself.

## Basic usage

Retries are **off by default** (`retries=0`). Pass a `retries` count to the client constructor to turn them on:

```python theme={null} theme={null}
from zenrows import ZenRowsClient

client = ZenRowsClient("YOUR_ZENROWS_API_KEY", retries=2)

response = client.get("https://www.zenrows.com/")
```

With `retries=2`, a failed request is attempted up to 2 additional times (3 attempts total) before the SDK returns the last response it received.

## When to enable retries

Enable `retries` (2–3 is a reasonable starting point) whenever you're running requests at scale or in production. Anywhere a transient `429` or `5xx` shouldn't fail your whole job. Leave `retries=0` (the default) for quick one-off scripts or debugging, where you'd rather see the raw failure immediately than wait through automatic retry attempts.

## Which failures get retried

The SDK retries requests that come back with one of these status codes:

| Status code | Meaning               |
| ----------- | --------------------- |
| `422`       | Unprocessable Entity  |
| `429`       | Too Many Requests     |
| `500`       | Internal Server Error |
| `502`       | Bad Gateway           |
| `503`       | Service Unavailable   |
| `504`       | Gateway Timeout       |

Between attempts, it waits using exponential backoff (starting around 0.5 seconds and increasing with each retry), so it doesn't hammer the API or the target site immediately after a failure.

<Tip>
  You aren't charged for retried attempts that fail. Only successful responses count toward your usage. See the [Retry Failed Requests](/zenrows-academy/retry-failed-requests) guide for more on how this works.
</Tip>

## Handling failures

The SDK treats HTTP error status codes and network-level problems differently. Pick the tab that matches what you're dealing with:

<Tabs>
  <Tab title="HTTP error status">
    Even after all retry attempts are exhausted, the SDK returns the final `requests.Response` object. It does **not** raise an exception for a non-2xx status. Always check the response before trusting its content:

    ```python theme={null} theme={null}
    response = client.get(url, params={"js_render": True})

    if response.ok:
        print(response.text)
    else:
        print(f"Failed after retries: {response.status_code} - {response.text}")
    ```

    If you'd rather fail loudly, call `response.raise_for_status()`, which raises `requests.exceptions.HTTPError` for 4xx/5xx responses:

    ```python theme={null} theme={null}
    response = client.get(url)
    response.raise_for_status()
    ```
  </Tab>

  <Tab title="Network-level exceptions">
    Connection problems (DNS failures, connection timeouts, dropped connections) are raised as exceptions by the underlying `requests` library, not returned as a response. Wrap calls in a `try`/`except` to handle those:

    ```python theme={null} theme={null}
    import requests

    try:
        response = client.get(url, timeout=30)
    except requests.exceptions.RequestException as error:
        print(f"Request failed: {error}")
    ```

    <Note>
      `timeout` and other [requests](https://docs.python-requests.org/) keyword arguments can be passed directly to `get`, `post`, and `put`. They're forwarded to the underlying HTTP call.
    </Note>
  </Tab>
</Tabs>

## Troubleshooting

* **Response still fails after retries are exhausted**: Check `response.status_code` and look it up in [API Error Codes](/api-error-codes). Some failures (like an invalid `apikey`) won't be fixed by retrying and need a code change instead.
* **Retries seem to take a long time**: That's the exponential backoff by design. Lower `retries` if latency matters more than success rate for your use case.
* **Getting exceptions instead of error responses**: That only happens for network-level failures (timeouts, DNS errors), not HTTP error status codes. Wrap the call in `try`/`except requests.exceptions.RequestException` as shown above.
* **429 errors even with retries enabled**: You're likely exceeding your plan's concurrency limit, not hitting a one-off rate limit. See [Async Requests and Concurrency](/universal-scraper-api/sdk/python/async-concurrency).

## Pricing

Retried attempts that fail are **not** billed. Only the final successful response counts. This makes `retries` effectively free insurance against transient failures:

| Configuration                           | Cost multiplier vs. basic |
| --------------------------------------- | ------------------------- |
| Basic request                           | 1x                        |
| `js_render=True`                        | 5x                        |
| `premium_proxy=True`                    | 10x                       |
| `js_render=True` + `premium_proxy=True` | 25x                       |

<Note>`404` and `410` responses count as successful and are billed, since the request itself completed correctly.</Note>

## FAQ

<AccordionGroup>
  <Accordion title="What's the default number of retries?">
    The default number of retries is 0. Retries are disabled unless you explicitly pass `retries=N` to the constructor.
  </Accordion>

  <Accordion title="Am I charged for retried requests that eventually fail?">
    No. Only successful requests consume your ZenRows balance.
  </Accordion>

  <Accordion title="Does the SDK retry on connection timeouts automatically?">
    Connection-level failures raise a `requests` exception rather than returning a response, so they're not covered by the `retries` status-code list. Handle them with your own `try`/`except`, as shown above.
  </Accordion>
</AccordionGroup>

## Look up a specific error code

For a full breakdown of ZenRows error codes (like `AUTH002` or `RESP001`) and how to resolve each one, see the [API Error Codes](/api-error-codes) reference.
