> ## Documentation Index
> Fetch the complete documentation index at: https://developers.soax.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Every status code the SOAX API returns, the error response shapes, common error messages, and how to handle each one.

The SOAX API uses conventional HTTP status codes. Anything in the `2xx` range is success, `4xx` means something is wrong with the request, and `5xx` means something went wrong on the SOAX side.

## Error response shapes

Errors return a JSON object with the message under one of two keys, depending on which validation layer rejected the request. Endpoint-level checks (auth, scopes, not-found, pagination and count limits) use `detail`:

```json theme={null}
{ "detail": "Package not found" }
```

Deeper business-rule validation — connection-string rule combinations, analytics date ranges and filters, API key creation rules — and all 429s use `error`:

```json theme={null}
{ "error": "Cannot specify both 'rotate-time' and 'rotate-requests' simultaneously." }
```

The split is an implementation detail you shouldn't depend on — in your error handling, read `detail ?? error` and treat them the same. The tables below show which key each message actually uses.

**422 validation errors** (a parameter or body field failed type/format validation) are the third shape: a `detail` **array**, one entry per problem, with the location of the offending field:

```json theme={null}
{
  "detail": [
    {
      "loc": ["query", "limit"],
      "msg": "Input should be less than or equal to 100",
      "type": "less_than_equal"
    }
  ]
}
```

## Status codes

| Code  | Meaning                                                    | How to handle                                                                                                                                                                            |
| ----- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | Success                                                    | —                                                                                                                                                                                        |
| `201` | Created (new API key)                                      | Store the returned `secret` immediately — it is never shown again.                                                                                                                       |
| `204` | Success, no body (key revoked)                             | —                                                                                                                                                                                        |
| `400` | Invalid parameter value                                    | Don't retry as-is. The `detail` message names the parameter and, where applicable, lists valid values. Fix the request.                                                                  |
| `401` | Missing, invalid, or revoked API key                       | Don't retry. Check the `Authorization: Bearer <secret>` header and the key's status. If the key was revoked, create a new one.                                                           |
| `403` | Key lacks the required scope, or your plan lacks a feature | Don't retry. Either use a key with the right scope (the `detail` names it), or — for ASN/ZIP location endpoints — upgrade to a plan with that targeting feature.                         |
| `404` | Resource not found                                         | Don't retry. The resource doesn't exist, belongs to another organization, or is outside the key's package restriction (restricted keys get 404, never 403, for packages they can't see). |
| `422` | Request failed schema validation                           | Don't retry as-is. Fix the field named in `loc`.                                                                                                                                         |
| `429` | Rate limit exceeded                                        | Wait the number of seconds in the `Retry-After` header, then retry. See [Rate limits](/api/rate-limits).                                                                                 |
| `5xx` | Server error                                               | Retry with exponential backoff. If it persists, contact support with the `x-request-id` response header value.                                                                           |

<Tip>
  Every response includes an `x-request-id` header. Log it — support can trace exactly what happened to a request from that ID.
</Tip>

## Common error messages

Messages under the `detail` key:

| Status | `detail`                                                                                | What it means                                                                                                                                                                                |
| ------ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | `Invalid API key`                                                                       | No key sent, the secret is wrong, or the key was revoked.                                                                                                                                    |
| 403    | `Missing required scope: <scope>`                                                       | The key doesn't carry the scope this endpoint requires. See [scopes](/api/authentication#scopes).                                                                                            |
| 403    | `Feature asn_targeting is not enabled` / `Feature zip_code_targeting is not enabled`    | Your plan doesn't include ASN or ZIP targeting ([ASNs](/api/locations/package-asns), [ZIP codes](/api/locations/package-zip-codes)).                                                         |
| 404    | `Package not found`                                                                     | Wrong package ID, another organization's package, or outside the key's package restriction.                                                                                                  |
| 404    | `API key not found`                                                                     | The `key_id` being revoked doesn't exist in your organization.                                                                                                                               |
| 404    | `No active subscription found`                                                          | The account has no active subscription.                                                                                                                                                      |
| 400    | `Invalid status value: … Valid values are: active, deleted, limited, paused, suspended` | Bad `status` filter on [list packages](/api/packages/list). One value only — comma-separated lists are rejected.                                                                             |
| 400    | `Count cannot exceed 1000.`                                                             | `count` on [connection strings](/api/packages/connection-string) is above the maximum.                                                                                                       |
| 400    | `Country code '…' is not available for this package.`                                   | A [package location endpoint](/api/locations/package-regions) was called with a country the package can't target. List valid ones via [package countries](/api/locations/package-countries). |
| 400    | `Network '…' is not available for this package. Available networks: …`                  | Bad `network` filter on a package location endpoint; the message lists what the package supports.                                                                                            |

Messages under the `error` key:

| Status | `error`                                                                       | What it means                                                                                                                   |
| ------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `Country '…' is not available for this proxy package.`                        | [Connection strings](/api/packages/connection-string) requested for a country the package can't target.                         |
| 400    | `Invalid network '…'. Must be one of: residential, mobile, any.`              | Bad `network` value on connection strings.                                                                                      |
| 400    | `Country must be specified if region, city, ISP, ASN, or ZIP is provided.`    | Finer-grained targeting always needs `country`.                                                                                 |
| 400    | `Cannot specify both 'rotate-time' and 'rotate-requests' simultaneously.`     | Pick one rotation mode.                                                                                                         |
| 400    | `The 'retries' parameter is required when onerror='retry'.`                   | `on-error=retry` needs `retries`; `retries` is only valid with `on-error=retry`.                                                |
| 400    | `Session ID must be an alphanumeric lowercase string up to 16 characters.`    | `session` must match `^[a-z0-9]{1,16}$`.                                                                                        |
| 400    | `bind=node is incompatible with prefer=lookalike.` / `…with onerror=replace.` | A bound session can't be replaced, so replacement-based options don't apply.                                                    |
| 400    | `Date range cannot be more than 1 year`                                       | An [analytics](/api/analytics/summary) query spans more than 365 days.                                                          |
| 400    | `hour interval requires a date range less than 24 hours`                      | See interval constraints in [Rate limits](/api/rate-limits#other-usage-constraints); `day` and `month` have analogous messages. |
| 400    | `Invalid scopes: … Valid scopes are: …`                                       | [Creating a key](/api/keys/create) with an unknown scope.                                                                       |
| 400    | `Requested scopes exceed the calling key's own scopes`                        | [Creating a key](/api/keys/create) with more scopes than the caller has.                                                        |
| 429    | `Rate limit exceeded: …`                                                      | Too many requests. Wait `Retry-After` seconds.                                                                                  |

## Handling errors in code

A pattern that covers the cases worth automating — retry `429` and `5xx`, surface everything else:

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests

  def soax_request(method, url, *, api_key, max_attempts=5, **kwargs):
      headers = {"Authorization": f"Bearer {api_key}"}
      for attempt in range(max_attempts):
          resp = requests.request(method, url, headers=headers, **kwargs)
          if resp.status_code == 429:
              time.sleep(int(resp.headers.get("Retry-After", 1)))
              continue
          if resp.status_code >= 500:
              time.sleep(min(2 ** attempt, 30))  # exponential backoff, capped
              continue
          if resp.status_code >= 400:
              body = resp.json()
              message = body.get("detail") or body.get("error") or body
              raise RuntimeError(
                  f"SOAX API error {resp.status_code}: {message} "
                  f"(x-request-id: {resp.headers.get('x-request-id')})"
              )
          return resp.json() if resp.status_code != 204 else None
      raise RuntimeError(f"Gave up after {max_attempts} attempts: {url}")
  ```

  ```javascript Node.js theme={null}
  async function soaxRequest(url, { apiKey, maxAttempts = 5, ...init } = {}) {
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      const resp = await fetch(url, {
        ...init,
        headers: { Authorization: `Bearer ${apiKey}`, ...init.headers },
      });
      if (resp.status === 429) {
        const retryAfter = Number(resp.headers.get("Retry-After") ?? 1);
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        continue;
      }
      if (resp.status >= 500) {
        await new Promise((r) => setTimeout(r, Math.min(2 ** attempt * 1000, 30000)));
        continue;
      }
      if (!resp.ok) {
        const body = await resp.json().catch(() => ({}));
        const message = body.detail ?? body.error ?? JSON.stringify(body);
        throw new Error(
          `SOAX API error ${resp.status}: ${message} ` +
          `(x-request-id: ${resp.headers.get("x-request-id")})`
        );
      }
      return resp.status === 204 ? null : resp.json();
    }
    throw new Error(`Gave up after ${maxAttempts} attempts: ${url}`);
  }
  ```
</CodeGroup>

<Note>
  These are errors from the **API** (`api.platform.soax.com`). Errors from the **proxy gateway** (`proxy.soax.com:1337`, e.g. `407 AUTH_FAILED`) are a different system with different codes — see [Proxy error codes](/troubleshooting/error-codes).
</Note>
