# Error Envelope

Every error returned uses a canonical envelope. Parse it once and reuse across all endpoints.

## Shape

```json
{
  "error": {
    "type": "invalid_request",
    "message": "Both `first_name` and `last_name` are required together — an orphan first or last name has no usable shape.",
    "doc_url": "",
    "request_id": "24a78305-e6a0-45e6-8645-59e3507b70f3",
    "retryable": false,
    "suggested_action": "Fix the highlighted field and retry.",
    "param": "non_field_errors"
  }
}
```

## Field reference

| Field | Type | Notes |
|---|---|---|
| `type` | string | Machine identifier. Stable across releases. |
| `message` | string | Human-readable. Safe to display to end users. |
| `doc_url` | string | Anchor link into these docs. May be empty. |
| `request_id` | string | UUID per request. Also returned as `X-Request-Id` response header. |
| `retryable` | bool | `true` if the same request would have a chance of succeeding on retry. |
| `suggested_action` | string | Concrete next step. |
| `param` | string | Optional. Set on validation errors to identify the failing field (e.g. `"domain"`, `"non_field_errors"`). |

## Codes

| HTTP | `error.type` | When | Retryable |
|---|---|---|---|
| `400` | `invalid_request` | Request body or query params failed validation | no |
| `402` | `insufficient_credits` | Account balance insufficient for the call | no |
| `403` | `permission_denied` | Key valid but account not eligible for the public API | no |
| `404` | `not_found` | Resource does not exist | no |
| `409` | `conflict` | Request conflicts with existing state | no |
| `429` | `rate_limited` | Agentic Search concurrency limit exceeded (gateway rate limits use the response below) | yes |
| `500` | `internal_error` | Server-side error. | no |
| `502` | `upstream_error` |  Upstream error. | yes |
| `503` | `service_unavailable` | Service temporarily unavailable. | yes |

## Two error responses do NOT use this envelope

**`403 Forbidden`** — `x-api-key` missing, revoked, or not recognized:

```json
{"message": "Forbidden"}
```

**`429`** — rate limit exceeded:

```
HTTP/1.1 429 Too Many Requests
{"message":"Too Many Requests"}
```

## See also

- [Authentication](https://docs.clodo.ai/api-reference/authentication) for `403 Forbidden` semantics.
- Per-endpoint pages for endpoint-specific rate limits.
