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

# Errors

> Consistent error envelope and codes returned by the Clarky API

Every Clarky API error — whether it's an authentication failure, a validation problem, or an internal error — comes back in the same envelope. This makes it easy to write a single error handler that works across every endpoint.

## Error envelope

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "first_name must be a string",
    "details": {
      "field": "first_name"
    }
  }
}
```

<ResponseField name="error.code" type="string" required>
  A short machine-readable identifier for the failure. Use this for branching logic — never the message.
</ResponseField>

<ResponseField name="error.message" type="string" required>
  A human-readable description of what went wrong. Safe to surface to developers, but not generally suitable for end users.
</ResponseField>

<ResponseField name="error.details" type="object">
  Optional structured context. The shape depends on the error code — for `validation_error` this often includes the offending field name, expected type, etc.
</ResponseField>

## Error codes

| Code               | HTTP | Meaning                                                            |
| ------------------ | ---- | ------------------------------------------------------------------ |
| `unauthorized`     | 401  | Missing, malformed, or revoked API key                             |
| `forbidden`        | 403  | Authenticated, but the key doesn't have permission for this action |
| `not_found`        | 404  | The resource doesn't exist or isn't visible to your workspace      |
| `bad_request`      | 400  | The request was malformed (bad JSON, unsupported parameter, etc.)  |
| `validation_error` | 422  | The request was well-formed but a field failed validation          |
| `conflict`         | 409  | The request conflicts with the current state of a resource         |
| `rate_limited`     | 429  | Too many requests — see [Rate Limits](/api-reference/rate-limits)  |
| `internal_error`   | 500  | Something went wrong on our end. Retry with backoff                |

## Examples

### `unauthorized` (401)

Returned when the `Authorization` header is missing, the key has been revoked, or the key format is invalid.

```json theme={null}
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key"
  }
}
```

### `forbidden` (403)

Returned when the key is valid but doesn't have permission for the action — for example, a `read`-only key trying to `POST`.

```json theme={null}
{
  "error": {
    "code": "forbidden",
    "message": "This API key does not have the 'write' scope"
  }
}
```

### `not_found` (404)

Returned when the resource doesn't exist, or exists in a different workspace than the one your key belongs to.

```json theme={null}
{
  "error": {
    "code": "not_found",
    "message": "Contact not found"
  }
}
```

### `bad_request` (400)

Returned for malformed requests — invalid JSON, unsupported query parameters, missing `Content-Type`, etc.

```json theme={null}
{
  "error": {
    "code": "bad_request",
    "message": "Request body must be valid JSON"
  }
}
```

### `validation_error` (422)

Returned when the request is structurally valid but at least one field failed validation. The `details` object identifies the offending field.

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "deal_value must be a non-negative number",
    "details": {
      "field": "deal_value"
    }
  }
}
```

### `conflict` (409)

Returned when the request would violate a uniqueness constraint or otherwise conflict with the current state.

```json theme={null}
{
  "error": {
    "code": "conflict",
    "message": "A contact with this primary email already exists",
    "details": {
      "field": "emails",
      "existing_contact_id": "c_01HXYZ..."
    }
  }
}
```

### `rate_limited` (429)

Returned when you exceed the per-key request budget. The response includes `Retry-After` and `X-RateLimit-*` headers — see [Rate Limits](/api-reference/rate-limits).

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Retry after 30 seconds."
  }
}
```

### `internal_error` (500)

Returned when something unexpected happened server-side. These should be rare. Retry with exponential backoff.

```json theme={null}
{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred"
  }
}
```

<Tip>
  Always branch on `error.code`, never on `error.message`. We may improve message wording over time, but codes are stable contract.
</Tip>
