# Errors

> HTTP status codes, error codes and response shapes returned by the Kustomer API, what causes each, and which to retry.

Source: https://developer.kustomer.com/docs/concepts/errors

The Kustomer API uses standard HTTP status codes. `2xx` means success, `4xx` means something about the request needs to change, and `5xx` means something went wrong on Kustomer's side.

## Error shape

Most errors return an `errors` array. Each entry has a machine-readable `code`, a human-readable `message`, and optional `meta`:

```json title="400 Bad Request"
{
  "errors": [
    {
      "code": "bad_request",
      "message": "Invalid request body or parameters.",
      "meta": {}
    }
  ]
}
```

Some endpoints return the JSON:API error object instead, with `status`, `code`, `title`, `detail`, `source` (pointing at the bad field) and `meta`. Write your error handling to accept both:

```ts title="Node.js"
interface KustomerError {
  code?: string
  message?: string
  title?: string
  detail?: string
  status?: string
  source?: { pointer?: string; parameter?: string }
}

async function errorMessage(res: Response): Promise<string> {
  const body = await res.json().catch(() => null)
  const err: KustomerError | undefined = body?.errors?.[0]
  if (!err) return `HTTP ${res.status}`
  const where = err.source?.pointer ?? err.source?.parameter
  return [err.code, err.message ?? err.detail ?? err.title, where && `at ${where}`].filter(Boolean).join(': ')
}
```

## Status codes

| Status | Code            | Common causes                                                                                                             | Retry?                                                           |
| ------ | --------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `400`  | `bad_request`   | Invalid JSON, missing required fields, wrong types, unknown enum values, bad custom attribute suffix                      | No: fix the request                                              |
| `401`  | `unauthorized`  | Missing `Authorization` header, missing `Bearer ` prefix, expired or deleted key, request from outside the key's IP range | No: fix the credentials                                          |
| `403`  | `forbidden`     | The key is valid but lacks a role the operation needs                                                                     | No: use a key with the right role                                |
| `404`  | `not_found`     | Wrong ID, a lookup such as `externalId=` found nothing, or the record was deleted                                         | No. For lookups, `404` is a normal "not found" answer.           |
| `409`  | `conflict`      | Duplicate unique value, or a concurrent write to the same resource, such as a company hierarchy edge                      | Sometimes: retry if the error says another write was in progress |
| `414`  | `uri_too_long`  | Too many IDs in a query string                                                                                            | No: batch the IDs                                                |
| `422`  | `unprocessable` | The request is valid but breaks a business rule                                                                           | No                                                               |
| `429`  | none            | Over the org-wide or an object [rate limit](https://developer.kustomer.com/docs/concepts/rate-limits)                     | Yes, after `x-ratelimit-reset`                                   |
| `5xx`  | none            | A temporary problem on Kustomer's side                                                                                    | Yes, with exponential backoff                                    |

> **Region mismatch:** If you see `Auth token associated with pod prod2 but request is being handled by prod1`, the token belongs to an org in another region. Use your org host, `https://<org>.api.kustomerapp.com`. See [Regions and base URLs](https://developer.kustomer.com/docs/get-started/regions-and-base-urls).

## Retry policy

```mermaid
flowchart TD
  R[Response] --> S{Status}
  S -- "2xx" --> OK[Done]
  S -- "429" --> W["Wait until x-ratelimit-reset<br/>+ jitter, then retry"]
  S -- "5xx or network error" --> B["Exponential backoff<br/>0.5s, 1s, 2s, 4s… (max 5 tries)"]
  S -- "409 'in progress'" --> B
  S -- "other 4xx" --> F[Fail: log code and message, fix the request]
```

Retrying a `POST` that timed out can create duplicates. Make creates safe to repeat by setting `externalId` and looking it up before you retry. See [Making requests](https://developer.kustomer.com/docs/get-started/making-requests#idempotency-and-retries).

## Bulk job errors

Bulk jobs report errors per item instead of failing the whole request. When you send `_clientRef`, each result has a `status` of `created`, `updated` or `error`, and failed items include an `error` reason. See [Bulk operations](https://developer.kustomer.com/docs/guides/bulk-operations#3-read-the-results).

## Debugging checklist

1. Log the status, the `errors` array, the method and path. Never log the `Authorization` header.
2. Reproduce the request with the **Try it** panel on the operation's reference page, or with `kusty api ... --dry-run` to see exactly what would be sent.
3. Check the roles on the reference page against your key's roles from `GET /v1/auth/tokens/current`.
4. For `400` errors, compare your body with the request schema on the reference page. Custom attribute names must end in a valid suffix.
