Skip to content
Error Handling

Error Handling & Retries

Build resilient proxy pipelines with proper error handling, exponential backoff, and failover strategies. This guide covers every error code, retry patterns, and production-grade patterns for reliable proxy usage.

Error Codes Reference

400

Bad Request

Cause: Invalid parameters in an API request (the response lists them in details) or a malformed proxy username

Fix: Fix the fields named in details. For proxies, check the credential format: USERNAME:PASSWORD_country-us_session-ID (Light / Basic) or USERNAME-country-us-session-ID:PASSWORD (Advanced)

401

Unauthorized

Cause: Missing or invalid API key, or the account is suspended

Fix: Copy your current key from Dashboard → Settings (regenerating replaces the old key immediately)

403

Forbidden

Cause: No bandwidth purchased for the plan you asked to generate

Fix: Top up that plan in the dashboard, then generate again

407

Proxy Auth Required

Cause: Missing or wrong proxy username/password

Fix: Use the username and password from a generated line in the proxy URL. Your API key is not a proxy credential

429

Rate Limited

Cause: More than 3,000 API requests in a minute from one API key

Fix: Wait the number of seconds in the Retry-After header, then retry with backoff

502

Bad Gateway

Cause: Target site unreachable via proxy, or mobile/datacenter generation briefly unavailable (API)

Fix: Retry with a different session/IP or check target availability

503

Pool Exhausted

Cause: No available proxies in the requested geo/type

Fix: Broaden targeting (country instead of city) or try different proxy type

504

Gateway Timeout

Cause: Target site too slow to respond

Fix: Increase timeout or retry. Target may be rate-limiting.

Error Response Format

json
// 429 from the API
{
"success": false,
"error": "API rate limit exceeded."
}
// 400 validation errors also list the fields
{
"success": false,
"error": "Validation failed",
"details": [{ "field": "count", "message": "Number must be less than or equal to 10000" }]
}

API errors return success: false with a readable error message; some add a machine-readable code. API responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers, and a 429 adds Retry-After (seconds).

Retry Strategies

Exponential Backoff with Jitter (Python)

python
import requests
import time
import random
def fetch_with_retry(url: str, proxies: dict, max_retries: int = 5) -> requests.Response:
"""Fetch URL through proxy with exponential backoff and jitter."""
for attempt in range(max_retries):
try:
response = requests.get(url, proxies=proxies, timeout=30)
if response.status_code == 200:
return response
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
time.sleep(retry_after)
continue
if response.status_code >= 500:
# Server error - retry with backoff
wait = min(2 ** attempt + random.uniform(0, 1), 60)
time.sleep(wait)
continue
# 4xx - don't retry (client error)
response.raise_for_status()
except requests.exceptions.Timeout:
wait = min(2 ** attempt + random.uniform(0, 1), 60)
time.sleep(wait)
except requests.exceptions.ConnectionError:
wait = min(2 ** attempt + random.uniform(0, 1), 60)
time.sleep(wait)
raise Exception(f"Failed after {max_retries} retries: {url}")
# Usage
proxies = {
"http": "http://USERNAME:PASSWORD_country-us@basic.resi.zentislabs.com:12321",
"https": "http://USERNAME:PASSWORD_country-us@basic.resi.zentislabs.com:12321",
}
response = fetch_with_retry("https://example.com/api/data", proxies)
print(response.json())

Retry with Session Rotation (Python)

python
import requests
import time
import random
def fetch_with_rotation(url: str, max_retries: int = 5) -> requests.Response:
"""Retry with a fresh proxy IP on each attempt."""
for attempt in range(max_retries):
session_id = f"retry{attempt}n{random.randint(1000, 9999)}" # letters and digits
proxy = f"http://USERNAME:PASSWORD_country-us_session-{session_id}@basic.resi.zentislabs.com:12321"
proxies = {"http": proxy, "https": proxy}
try:
response = requests.get(url, proxies=proxies, timeout=30)
if response.status_code == 200:
return response
print(f"Attempt {attempt + 1}: Status {response.status_code}, rotating IP...")
except requests.RequestException as e:
print(f"Attempt {attempt + 1}: {e}, rotating IP...")
wait = min(2 ** attempt + random.uniform(0, 1), 30)
time.sleep(wait)
raise Exception(f"Failed after {max_retries} retries with rotation")

Retry with Failover (Node.js)

typescript
import axios, { AxiosError } from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
async function fetchWithRetry(
url: string,
maxRetries = 5,
): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const sessionId = `retry${attempt}n${Date.now()}`; // letters and digits
const agent = new HttpsProxyAgent(
`http://USERNAME:PASSWORD_country-us_session-${sessionId}@basic.resi.zentislabs.com:12321`
);
try {
const { data } = await axios.get(url, {
httpsAgent: agent,
timeout: 30000,
});
return data;
} catch (err) {
const status = (err as AxiosError).response?.status;
if (status && status >= 400 && status < 500 && status !== 429) {
throw err; // Don't retry client errors (except 429)
}
const wait = Math.min(2 ** attempt * 1000 + Math.random() * 1000, 60000);
console.log(`Attempt ${attempt + 1} failed (${status}), retrying in ${wait}ms...`);
await new Promise((r) => setTimeout(r, wait));
}
}
throw new Error(`Failed after ${maxRetries} retries: ${url}`);
}

Failover Patterns

Proxy Type Failover

Fall back from residential to datacenter if the residential pool is exhausted.

python
import requests
# Each proxy type has its own credentials - copy them from its dashboard page
PROXY_CHAIN = [
"http://USERNAME:PASSWORD_country-us@basic.resi.zentislabs.com:12321", # Residential (Basic)
"http://USERNAME:PASSWORD@dc.zentislabs.com:PORT", # Datacenter (generated line)
]
def fetch_with_failover(url: str) -> requests.Response:
"""Try each proxy type in order until one succeeds."""
for proxy_url in PROXY_CHAIN:
proxies = {"http": proxy_url, "https": proxy_url}
try:
response = requests.get(url, proxies=proxies, timeout=30)
if response.status_code == 200:
return response
except requests.RequestException:
continue
raise Exception("All proxy types failed")
response = fetch_with_failover("https://example.com/data")

Geo Failover

python
# If a specific city fails, fall back to broader targeting
# (Light / Basic: targeting goes in the password; a city always needs its state)
GEO_CHAIN = [
"PASSWORD_country-us_state-newyork-city-newyork", # Most specific
"PASSWORD_country-us_state-newyork", # State level
"PASSWORD_country-us", # Country level
]
def fetch_with_geo_failover(url: str) -> requests.Response:
for password in GEO_CHAIN:
proxy = f"http://USERNAME:{password}@basic.resi.zentislabs.com:12321"
proxies = {"http": proxy, "https": proxy}
try:
response = requests.get(url, proxies=proxies, timeout=30)
if response.status_code == 200:
return response
except requests.RequestException:
continue
raise Exception("All geo levels failed")

Timeout Configuration

Recommended timeout values by proxy type:

Proxy TypeConnect TimeoutRead TimeoutTotal Timeout
Datacenter5s15s20s
ISP5s20s25s
Residential10s30s40s
Mobile15s45s60s

Best Practices

Always implement retries

Transient errors are normal in proxy operations. 2–3 retries with backoff handles most cases.

Rotate IP on failure

Use a new session ID on retry to get a fresh IP. The previous IP may be blocked by the target.

Use appropriate timeouts

Set connect + read timeouts. Mobile proxies need longer timeouts than datacenter.

Log errors with context

Record the time, endpoint, status code and error message. Include them when you contact support for the fastest help.

Monitor error rates

Track error rates per proxy type and geo. Sudden spikes may indicate target-side changes.

Don't retry 4xx errors

Client errors (except 429) indicate a problem with your request - retrying won't help.