# Errors & rate limits
Source: https://proxuno.com/docs/api-errors
Updated: 2026-09-16

REST API

The API error format, every error code with its HTTP status, rate-limit headers, the rotation cooldown, and a retry strategy that respects both.

Updated 16 Sept 2026 3 min read API v1

### On this page

- [Error format](#error-format)
- [Codes](#codes)
- [Rate-limit headers](#rate-limit-headers)
- [Retry strategy](#retry-strategy)
- [Proxy-side errors](#proxy-side-errors)

## Error format[#](#error-format)

Every error — from the REST API and from rotation links — has the same shape:

JSONCopy

```json
{
"error": {
"code": "validation_error",
"message": "Some fields need your attention.",
"fields": { "ttl": "Sticky sessions last between 1 and 30 minutes." }
}
}
```

- `code` is stable and machine-readable. **Branch on `code`, never on `message`.**
- `message` is a human-readable English sentence for logs. Its wording may improve over time.
- `fields` appears on validation errors only and maps each invalid parameter to a message.
- `retry_after` (seconds) appears on `rotation_cooldown` errors.

## Codes[#](#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[#](#rate-limit-headers)

Every API response carries:

HTTPCopy

```http
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:

HTTPCopy

```http
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-strategy)

Retry only what can succeed later: `429` and `5xx`, plus network timeouts. Never retry `400`, `401`, `403` or `404` unchanged.

PythonNode.js

Copy

```python
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")
```

```javascript
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[#](#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](/contact?topic=support&subject=Documentation%20issue%3A%20Errors%20%26%20rate%20limits)
