# Errors

> The one error shape every Kanorio API returns, how codes map to HTTP statuses, and how to handle retries.

> Markdown version of https://kanorio.com/en/developers/basics/errors. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

The Management API, the Content Delivery API and the Customer API return errors in the same shape. A code always has the same HTTP status on every API, so one error handler works for all of them.

```json
{
  "error": {
    "code": "SLUG_TAKEN",
    "message": "Another post already uses this slug.",
    "details": { "slug": "how-we-feed-our-starter" }
  }
}
```

| Field | Type | Notes |
|---|---|---|
| `error.code` | string | Stable, machine-readable. Branch on this. |
| `error.message` | string | One English sentence for logs and developers. It can change at any time, so never match on it. |
| `error.details` | object, optional | Structured context for the code, such as `retryAfter`, `availableAt`, `issues` or `requiredScope`. |

Successful responses never contain `error`, and no API wraps success in an `ok` flag.

## HTTP status by code

Every code maps to one status. Codes added later follow the same rules, so you can handle a code you have not seen yet by its status.

| Status | Meaning | Examples |
|---|---|---|
| `400` | The request is malformed or fails validation | `VALIDATION`, `INVALID_CURSOR`, codes starting with `INVALID_` |
| `401` | No credential, or the credential is invalid, expired or revoked | `UNAUTHORIZED`, `UNAUTHENTICATED` |
| `403` | The credential is valid but not allowed: permission, plan, role or mode | `FORBIDDEN`, `OWNER_REQUIRED`, `LIMIT_REACHED`, `LOCKED` |
| `404` | The resource does not exist on this site | `NOT_FOUND`, codes ending in `_NOT_FOUND` |
| `405` | The method is not supported on this path | `METHOD_NOT_ALLOWED` |
| `409` | Conflicts with the current state: stale writes, taken slugs, duplicates | `CONFLICT`, `SLUG_TAKEN`, `IDEMPOTENCY_CONFLICT` |
| `413` | The upload is too large | `FILE_TOO_LARGE` |
| `422` | Valid input, but a business rule does not allow it | `SITE_NOT_PUBLISHED`, `ASSET_IN_USE` |
| `429` | Too many requests, or a busy backend; retry later | `RATE_LIMITED`, `SEARCH_BUSY`, `ANALYTICS_BUSY` |
| `500` | Unexpected server error | `INTERNAL` |
| `502`, `503` | An upstream service failed or is temporarily unavailable | `UPLOAD_FAILED`, `SERVICE_UNAVAILABLE`, `SHARED_SENDER_PAUSED` |

Each operation in the API reference lists the codes it can return and what they mean for that operation. The OpenAPI documents carry the same list as `x-kanorio-errors`.

## Validation errors

`VALIDATION` lists every failing field in `details.issues`. `path` uses dots for nesting.

```json
{
  "error": {
    "code": "VALIDATION",
    "message": "Input does not match the capability schema.",
    "details": {
      "issues": [
        { "path": "title", "message": "Required", "code": "invalid_type" }
      ]
    }
  }
}
```

Form submissions on the Content Delivery API report invalid answers the same way, with the question id as the `path`.

## Retries

- Retry `429`, `502` and `503` with backoff. When the response has a `Retry-After` header or `details.retryAfter`, wait at least that many seconds.
- Do not retry other `4xx` responses unchanged. Fix the request first.
- `409 CONFLICT` on a write means someone changed the resource after you read it. Read it again, merge, and send the write with the new timestamp.
- Make create requests safe to retry with an [idempotency key](https://kanorio.com/en/developers/basics/idempotency).

## Permission errors

`403` responses explain what is missing in `details`, for example the `requiredScope` the token lacks or `reason: "principal_not_allowed"` when a service account calls an account-level operation. Show the user what to change instead of retrying.

## Request IDs

Content Delivery API and Customer API responses carry an `X-Request-Id` header. Include it when you contact support about a specific request.

## Error codes

The tables below list every code each API can return today, generated from the OpenAPI documents.

### Management API codes

| Code | HTTP status |
| --- | --- |
| `EMPTY_SLUG` | 400 |
| `INVALID_BODY` | 400 |
| `INVALID_CURSOR` | 400 |
| `INVALID_EDITED_PATH` | 400 |
| `INVALID_FORMAT` | 400 |
| `INVALID_ORDER` | 400 |
| `INVALID_SUBDOMAIN` | 400 |
| `UNSUPPORTED_EDITED_PATH` | 400 |
| `VALIDATION` | 400 |
| `UNAUTHENTICATED` | 401 |
| `UNAUTHORIZED` | 401 |
| `CUSTOM_DOMAIN_CHANGE_FORBIDDEN` | 403 |
| `CUSTOM_DOMAIN_LIMIT_REACHED` | 403 |
| `FORBIDDEN` | 403 |
| `LIMIT_REACHED` | 403 |
| `MODERATION_LOCKED` | 403 |
| `OWNER_REQUIRED` | 403 |
| `PLAN_CANNOT_PUBLISH` | 403 |
| `PRO_REQUIRED` | 403 |
| `REDIRECT_LIMIT` | 403 |
| `SUBDOMAIN_CHANGE_FORBIDDEN` | 403 |
| `TOKEN_WEBSITE_SCOPED` | 403 |
| `HEADER_NOT_FOUND` | 404 |
| `HOME_PAGE_NOT_FOUND` | 404 |
| `NOT_FOUND` | 404 |
| `OWNER_NOT_FOUND` | 404 |
| `PAGE_NOT_FOUND` | 404 |
| `PARENT_NOT_FOUND` | 404 |
| `PRODUCT_NOT_FOUND` | 404 |
| `WEBSITE_NOT_FOUND` | 404 |
| `METHOD_NOT_ALLOWED` | 405 |
| `CONFLICT` | 409 |
| `CUSTOM_DOMAIN_TAKEN` | 409 |
| `HAS_CHILDREN` | 409 |
| `HOSTED_ONLY` | 409 |
| `HOSTNAME_CONFLICT` | 409 |
| `IDEMPOTENCY_CONFLICT` | 409 |
| `IDEMPOTENCY_IN_PROGRESS` | 409 |
| `KEY_TAKEN` | 409 |
| `PRIMARY_HEADER_PROTECTED` | 409 |
| `READD_COOLDOWN` | 409 |
| `REDIRECT_CONFLICT` | 409 |
| `SLUG_TAKEN` | 409 |
| `SUBDOMAIN_TAKEN` | 409 |
| `TAKEN` | 409 |
| `BLOCKED` | 422 |
| `BREADCRUMB_PARENT_INVALID` | 422 |
| `CONTENT_VALIDATION_FAILED` | 422 |
| `CORE_SECTION_VIOLATION` | 422 |
| `CUSTOM_DOMAIN_BLOCKED` | 422 |
| `CUSTOM_DOMAIN_HIGH_RISK_TLD` | 422 |
| `CUSTOM_DOMAIN_TOO_LONG` | 422 |
| `DEPTH_EXCEEDED` | 422 |
| `DNS_NOT_PROPAGATED` | 422 |
| `DOMAIN_CHANGE_NOT_ALLOWED` | 422 |
| `EMPTY_CONTENT` | 422 |
| `FOREIGN_PAGE` | 422 |
| `HIGH_RISK_TLD` | 422 |
| `HOME_HAS_HIERARCHY` | 422 |
| `HOME_NOT_CREATABLE` | 422 |
| `HOME_NOT_DELETABLE` | 422 |
| `HOME_NOT_SORTABLE` | 422 |
| `HOME_SLUG_IMMUTABLE` | 422 |
| `LINK_IN_BIO_DEDICATED_ENTRY` | 422 |
| `LINK_IN_BIO_NOT_DUPLICABLE` | 422 |
| `META_NOT_LIVE` | 422 |
| `NO_PUBLIC_URL` | 422 |
| `OWNERSHIP_REQUIRED` | 422 |
| `PAGE_LIMIT_REACHED` | 422 |
| `PARENT_NOT_ALLOWED` | 422 |
| `RESERVED_SEGMENT` | 422 |
| `RESERVED_SLUG` | 422 |
| `RESERVED_SUBDOMAIN` | 422 |
| `SANITIZE_FAILED` | 422 |
| `SEARCH_CONSOLE_AUTH_EXPIRED` | 422 |
| `SEARCH_CONSOLE_NOT_CONNECTED` | 422 |
| `SEARCH_CONSOLE_PERMISSION_DENIED` | 422 |
| `SEARCH_CONSOLE_PROPERTY_NOT_FOUND` | 422 |
| `SEGMENT_TOO_LONG` | 422 |
| `SITEMAP_NOT_REACHABLE` | 422 |
| `SUBDOMAIN_MISSING` | 422 |
| `SYSTEM_PAGE_LOCKED` | 422 |
| `TEMPLATE_PRODUCT_LOCKED` | 422 |
| `ANALYTICS_BUSY` | 429 |
| `DAILY_LIMIT_REACHED` | 429 |
| `RATE_LIMITED` | 429 |
| `SEARCH_BUSY` | 429 |
| `SEARCH_CONSOLE_QUOTA_EXCEEDED` | 429 |
| `STOCK_BUSY` | 429 |
| `INTERNAL` | 500 |
| `SEARCH_CONSOLE_ERROR` | 502 |
| `BUSY` | 503 |
| `SEARCH_CONSOLE_UNAVAILABLE` | 503 |
| `SERVICE_UNAVAILABLE` | 503 |
| `WRITE_FROZEN` | 503 |

### Content Delivery API codes

| Code | HTTP status |
| --- | --- |
| `BAD_HOST` | 400 |
| `FILE_TYPE_NOT_ALLOWED` | 400 |
| `INVALID_ANSWERS` | 400 |
| `INVALID_BODY` | 400 |
| `INVALID_CURSOR` | 400 |
| `INVALID_KIND` | 400 |
| `INVALID_LIMIT` | 400 |
| `INVALID_PATH` | 400 |
| `INVALID_QUERY` | 400 |
| `QUERY_REQUIRED` | 400 |
| `QUERY_TOO_LONG` | 400 |
| `QUERY_TOO_SHORT` | 400 |
| `TOO_MANY_PARAMS` | 400 |
| `UNKNOWN_BOT` | 400 |
| `UNSUPPORTED_LOCALE` | 400 |
| `VALIDATION` | 400 |
| `VERIFICATION_EXPIRED` | 400 |
| `NOT_PREVIEW` | 403 |
| `COLLECTION_NOT_FOUND` | 404 |
| `FORM_NOT_FOUND` | 404 |
| `HEADER_NOT_FOUND` | 404 |
| `ITEM_NOT_FOUND` | 404 |
| `PAGE_NOT_FOUND` | 404 |
| `PRODUCT_NOT_FOUND` | 404 |
| `SITE_NOT_FOUND` | 404 |
| `UNIT_NOT_FOUND` | 404 |
| `DUPLICATE_SUBMISSION` | 409 |
| `FILE_TOO_LARGE` | 413 |
| `RATE_LIMITED` | 429 |
| `SEARCH_BUSY` | 429 |
| `QUERY_TIMEOUT` | 503 |
| `UPLOAD_UNAVAILABLE` | 503 |
| `UPSTREAM_UNAVAILABLE` | 503 |

### Customer API codes

| Code | HTTP status |
| --- | --- |
| `BAD_HOST` | 400 |
| `INVALID_CODE` | 400 |
| `INVALID_CURSOR` | 400 |
| `INVALID_EMAIL` | 400 |
| `INVALID_LIMIT` | 400 |
| `VALIDATION` | 400 |
| `UNAUTHENTICATED` | 401 |
| `BOT_PROTECTION_FAILED` | 403 |
| `DOWNLOAD_NOT_ALLOWED` | 403 |
| `EXPIRED` | 403 |
| `FORBIDDEN` | 403 |
| `LIMIT_REACHED` | 403 |
| `LOCKED` | 403 |
| `NOT_RELEASED` | 403 |
| `NO_ATTEMPTS_LEFT` | 403 |
| `REVIEWS_DISABLED` | 403 |
| `CERTIFICATE_DISABLED` | 404 |
| `DELIVERABLE_NOT_FOUND` | 404 |
| `NOT_FOUND` | 404 |
| `NO_FILE` | 404 |
| `NO_RECEIPT` | 404 |
| `ORDER_NOT_FOUND` | 404 |
| `SITE_NOT_FOUND` | 404 |
| `UNIT_NOT_FOUND` | 404 |
| `WEBSITE_NOT_FOUND` | 404 |
| `METHOD_NOT_ALLOWED` | 405 |
| `ALREADY_REVIEWED` | 409 |
| `FILE_NOT_PROTECTED` | 409 |
| `NOT_A_QUIZ` | 409 |
| `QUIZ_NOT_READY` | 409 |
| `ACCOUNT_BLOCKED` | 422 |
| `ALREADY_REGISTERED` | 422 |
| `CHALLENGE_EXPIRED` | 422 |
| `EMAIL_NOT_VERIFIED` | 422 |
| `METHOD_DISABLED` | 422 |
| `VERIFY_FAILED` | 422 |
| `RATE_LIMITED` | 429 |
| `SHARED_SENDER_PAUSED` | 503 |
| `SIGN_FAILED` | 503 |
| `UPSTREAM_UNAVAILABLE` | 503 |
