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

# Errors

> The error envelope, the codes you can get, and how to handle them.

Every failure returns the same envelope — never alongside `data`:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Domain is not a valid hostname",
    "details": [
      { "field": "domain", "code": "INVALID_FORMAT", "message": "Domain is not a valid hostname" }
    ]
  }
}
```

`code` and `message` are always present; `details` only appears on field-level
failures.

<Warning>
  Branch on `error.code`, never on `error.message`. Messages are written for
  humans and change without notice; codes are a contract.
</Warning>

## Codes

| Status | Code                  | When                                                                                                                                 |
| ------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `BAD_REQUEST`         | A parameter is malformed or a required one is missing — e.g. a date that won't parse.                                                |
| 401    | `UNAUTHORIZED`        | No `X-API-KEY` header, or the key is unknown, revoked, or expired.                                                                   |
| 403    | `FORBIDDEN`           | The key is valid but the path isn't part of the public API, or the brand belongs to another organization.                            |
| 404    | `NOT_FOUND`           | The brand, prompt, topic, competitor or ad doesn't exist — including a brand that's been soft-deleted.                               |
| 422    | `VALIDATION_ERROR`    | The payload parsed but failed validation. Look at `details`.                                                                         |
| 422    | `USAGE_LIMIT_REACHED` | A write would push your subscription past its prompt/region limits. **The whole batch is rolled back** — nothing partial is written. |
| 429    | `RATE_LIMITED`        | Too many requests. See [Rate limits](/guides/rate-limits).                                                                           |
| 500    | `INTERNAL_ERROR`      | Something broke on our side. Safe to retry with backoff; if it persists, get in touch.                                               |

### Field codes

Inside `details[].code` on a `VALIDATION_ERROR`:

| Code             | Meaning                                                             |
| ---------------- | ------------------------------------------------------------------- |
| `REQUIRED`       | The field is missing or empty.                                      |
| `INVALID_FORMAT` | Present but malformed (a domain that isn't a hostname, a bad UUID). |
| `OUT_OF_RANGE`   | A number or date outside the accepted range.                        |
| `DUPLICATE`      | Conflicts with something that already exists.                       |

## Partial success on bulk writes

Bulk endpoints are not all-or-nothing in the same way. `create_brand_prompts`
writes the valid rows and reports the rest in the response body — a `201` can
still contain failures:

```json theme={null}
{
  "data": {
    "total_prompts": 12,
    "prompts_added": 10,
    "prompts_failed": 2,
    "duplicates": ["best crm for startups"],
    "invalid_regions": ["xx"],
    "errors_and_warnings": [ ]
  }
}
```

Check `prompts_failed` and `errors_and_warnings` even on a success status.

The one exception is **usage limits**: if the batch as a whole would exceed
your subscription, the entire mutation is rolled back and you get a `422`
`USAGE_LIMIT_REACHED` instead — no rows are written.

## Handling errors

* **401** — stop retrying. The key is bad, revoked, or expired; retrying won't
  fix it.
* **403** — you're calling an endpoint outside the public surface, or a brand
  your org doesn't own. Also not retryable.
* **404** — treat as terminal. A soft-deleted brand starts returning it
  mid-integration, so handle it as "gone", not as a transient failure.
* **422** — a bug in your payload. Log `details` and fix the caller.
* **429** — back off exponentially and retry. Don't rotate keys to get around
  it; the per-IP backstop catches that too.
* **500** — retry with backoff, cap the attempts, and alert if it sticks.
