# Errors

**You are never charged for an error.** Only `200` responses use credits — every status below costs nothing, so retrying is safe.

## Format

```json
{
  "error": {
    "status": 402,
    "code": "insufficient_credits",
    "message": "Your query balance is empty. Top up or choose a plan at https://serpwiz.com/dashboard/billing",
    "request_id": "9f2c1a7b3e4d5f60",
    "docs": "https://serpwiz.com/docs/errors"
  }
}
```

Match on `code`, not on `message` — messages may be reworded.

## Codes

| Status | `code` | What happened | What to do |
|---|---|---|---|
| `400` | `invalid_request` | A parameter is missing or invalid. The message names it. | Fix the request. Don't retry unchanged. |
| `401` | `unauthorized` | API key missing, malformed, invalid or revoked. | Check the `X-API-Key` header. |
| `402` | `insufficient_credits` | Free allowance and balance are both used up. | [Top up or upgrade](https://serpwiz.com/dashboard/billing). |
| `403` | `email_unverified` | The account's email isn't verified yet. | Click the link in your verification email. |
| `403` | `account_suspended` | The account is suspended. | Contact [support@serpwiz.com](mailto:support@serpwiz.com). |
| `404` | `not_found` | Unknown endpoint. | Check the path against the [API reference](/docs/api). |
| `405` | `method_not_allowed` | Not a `GET`. | Use `GET`. |
| `429` | `rate_limited` | Over your requests-per-second limit, or the service is busy. | Wait `Retry-After` seconds, then retry. |
| `500` | `internal_error` | Something broke on our side. | Retry with backoff. |
| `502` | `upstream_error` | The search couldn't be completed. | Retry with backoff. |
| `503` | `capacity` | Temporarily over capacity. | Wait `Retry-After` seconds, then retry. |
| `504` | `upstream_timeout` | The search took too long. | Retry. |

## Retrying

Retry `429`, `5xx` and network errors with exponential backoff and jitter, honouring `Retry-After` when present. Don't retry `400`–`404`.

```python
import random, time, requests

def search(params, key, attempts=5):
    for i in range(attempts):
        r = requests.get("https://api.serpwiz.com/v1/google/search", params=params,
                         headers={"X-API-Key": key}, timeout=60)
        if r.status_code == 200:
            return r.json()
        if r.status_code in (429, 500, 502, 503, 504):
            wait = float(r.headers.get("Retry-After", 0)) or min(30, 2 ** i)
            time.sleep(wait + random.random())
            continue
        r.raise_for_status()
    raise RuntimeError("gave up after retries")
```

Quote the `request_id` when you contact support.
