Skip to main content
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:
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 codeMeaning
422Unprocessable Entity
429Too Many Requests
500Internal Server Error
502Bad Gateway
503Service Unavailable
504Gateway 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.
You aren’t charged for retried attempts that fail. Only successful responses count toward your usage. See the Retry Failed Requests guide for more on how this works.

Handling failures

The SDK treats HTTP error status codes and network-level problems differently. Pick the tab that matches what you’re dealing with:
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:
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:
response = client.get(url)
response.raise_for_status()

Troubleshooting

  • Response still fails after retries are exhausted: Check response.status_code and look it up in 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.

Pricing

Retried attempts that fail are not billed. Only the final successful response counts. This makes retries effectively free insurance against transient failures:
ConfigurationCost multiplier vs. basic
Basic request1x
js_render=True5x
premium_proxy=True10x
js_render=True + premium_proxy=True25x
404 and 410 responses count as successful and are billed, since the request itself completed correctly.

FAQ

The default number of retries is 0. Retries are disabled unless you explicitly pass retries=N to the constructor.
No. Only successful requests consume your ZenRows balance.
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.

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