Errors & rate limits
The API error format, every error code with its HTTP status, rate-limit headers, the rotation cooldown, and a retry strategy that respects both.
On this page
Error format#
Every error — from the REST API and from rotation links — has the same shape:
{
"error": {
"code": "validation_error",
"message": "Some fields need your attention.",
"fields": { "ttl": "Sticky sessions last between 1 and 30 minutes." }
}
}
codeis stable and machine-readable. Branch oncode, never onmessage.messageis a human-readable English sentence for logs. Its wording may improve over time.fieldsappears on validation errors only and maps each invalid parameter to a message.retry_after(seconds) appears onrotation_cooldownerrors.
Codes#
| HTTP | code |
Meaning | What to do |
|---|---|---|---|
400 |
validation_error |
A parameter or body field is missing, malformed or out of range; also malformed JSON, and rotation requested on a static proxy. | Fix the request using error.fields. Do not retry unchanged. |
401 |
unauthorized |
No API key, or the key is malformed or revoked. | Check the header; create a new key if it was revoked. |
403 |
forbidden |
The account is suspended, or the proxy is expired or suspended. | Renew the proxy, or contact support for an account issue. |
404 |
not_found |
Unknown endpoint, or a resource that does not exist in your account. | Check the id. Other accounts' resources always answer 404. |
429 |
rate_limited |
More than 60 requests in the current minute for this key. | Wait until X-RateLimit-Reset / Retry-After. |
429 |
rotation_cooldown |
Less than 10 s since the previous manual rotation of this proxy. | Wait Retry-After seconds, then rotate once. |
500 |
internal_error |
Unexpected failure on our side. It is logged with the request. | Retry with backoff; contact support with the time of the request if it persists. |
New codes may be added in v1 (additively). Treat any unknown 4xx code like validation_error and any 5xx like internal_error.
Rate-limit headers#
Every API response carries:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1790777160
X-RateLimit-Reset is a Unix timestamp in seconds. The window is one minute and is counted per API key, whatever endpoint you call. Requests that fail authentication are counted per source IP.
At the limit:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
Content-Type: application/json
{"error":{"code":"rate_limited","message":"Rate limit of 60 requests per minute reached. Retry after 23 s."}}
Need more than 60 requests per minute? Usually you do not: list endpoints return all proxies in one call, and residential endpoints can be generated 1,000 at a time. If your use case still needs more, contact support with the expected volume.
Retry strategy#
Retry only what can succeed later: 429 and 5xx, plus network timeouts. Never retry 400, 401, 403 or 404 unchanged.
import random, time, httpx
RETRYABLE = {429, 500, 502, 503, 504}
def call(client: httpx.Client, method: str, path: str, **kw) -> dict:
for attempt in range(5):
try:
r = client.request(method, path, **kw)
except httpx.TransportError:
r = None
if r is not None and r.status_code not in RETRYABLE:
body = r.json()
if r.is_error:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
return body
wait = float(r.headers.get("Retry-After", 0)) if r is not None else 0
time.sleep(max(wait, min(30, 2 ** attempt)) + random.random())
raise RuntimeError("gave up after 5 attempts")
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function call(path, init = {}) {
for (let attempt = 0; attempt < 5; attempt++) {
let res = null;
try {
res = await fetch(`https://proxuno.com/api/v1${path}`, {
...init,
headers: { Authorization: `Bearer ${process.env.PROXUNO_API_KEY}`, ...init.headers },
});
} catch {}
if (res && !RETRYABLE.has(res.status)) {
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error.message), { code: body.error.code });
return body;
}
const wait = Number(res?.headers.get("retry-after") || 0) * 1000;
await sleep(Math.max(wait, Math.min(30000, 2 ** attempt * 1000)) + Math.random() * 1000);
}
throw new Error("gave up after 5 attempts");
}
Proxy-side errors#
Errors from the proxy gateways are protocol errors, not JSON:
| Situation | HTTP proxy | SOCKS5 |
|---|---|---|
| Wrong or missing credentials, IP not whitelisted | 407 Proxy Authentication Required |
auth failure, connection closed |
| Proxy expired | 407 |
auth failure |
| Unknown residential country/city flag | 407 |
auth failure |
| No residential traffic left | 402 Payment Required |
connection closed |
| Target unreachable or refused | 502 Bad Gateway |
reply 0x05 (connection refused) |
| Target too slow (no response in 60 s) | 504 Gateway Timeout |
connection closed |
| Mobile modem reconnecting after a rotation | 502 for 5–10 s |
connection closed |
During a rotation the modem is offline for a few seconds; schedule rotations between jobs, or retry 502 after a short pause.
Something unclear, outdated or wrong on this page? Tell us — documentation issues are fixed in the next release at the latest.
Report an issue with this page