# Errors

The single error object every Kustom API returns on a 4xx or 5xx — error_code, error_messages and correlation_id — the rule that you branch on the code and never parse the message, and the full catalogue of codes with what to do about each one.

Every Kustom API signals failure the same way — an HTTP 4xx or 5xx status together with a
JSON error object. The object carries a machine-readable `error_code`, one or more
human-readable `error_messages` and a `correlation_id` that identifies the request in
Kustom's logs. This page defines that object, how to act on it, and
carries the full catalogue of codes.

## The error object every Kustom API returns

```json
{
  "error_code": "BAD_VALUE",
  "error_messages": ["Invalid postal code for purchase_country SE"],
  "correlation_id": "8fd05d5b-3f36-4c94-9f11-a2f0f6d1c8a1"
}
```

| Field | Type | What it is for |
| --- | --- | --- |
| `error_code` | string | `ALL_CAPS_SNAKE_CASE`, enumerable per service. The only part of the body you may branch on. |
| `error_messages` | array of strings | English, for humans, for your logs. Never displayed to a customer, never parsed. |
| `correlation_id` | string | Identifies this exact request. Quote it to Kustom support. |

Some services still return the field as `error_message`, singular, holding a single string
rather than an array. Read both names and normalize to a list. Do not assume either shape.
The Hosted Payment Page distribution endpoint goes further and returns `error_code` alone, with
no messages and no `correlation_id`.

## Branch on error_code, never parse error_messages

`error_code` is the contract. `error_messages` is not: it is English prose, it is not
internationalized, its wording can change without a version bump and it is not part of what
[API stability](/basics/conventions/stability/) promises. Code that regex-matches an error
message is code that breaks on a copy edit.

- **Branch on** `error_code`, plus the HTTP status when the same code appears under two
  statuses.
- **Log** `error_messages` and `correlation_id`, in full, on every failure. This is the pair
  support will ask for.
- **Never** show `error_messages` to a customer. If a customer needs to be told something, map
  `error_code` to your own copy, in your own languages, on your side.
- **Never** parse `error_messages` to work out which field was wrong. Where a service needs
  to point at a field, it does so in the code table for that product. Treat the message as a
  debugging hint that narrows a search, not as data.

## What the HTTP status class tells you

| Status | Meaning | What to do |
| --- | --- | --- |
| `2xx` | Success. | Accept any 2xx. Do not code against one specific success code. |
| `4xx` | Your request was rejected — bad data, wrong credential or a state that forbids the operation. | Fix and resend, or escalate. Retrying an unchanged 4xx will fail identically. |
| `429` | Rate limited. | Back off and retry. See [Limits](/api/limits/). |
| `5xx` | Kustom-side failure, or an unknown outcome. | On Order Management, retry with the same idempotency key on the schedule in [Idempotency and retries](/order-management/idempotency-and-retries/). Checkout calls take no key, so read the order back before retrying one. |

After a `5xx` or a broken connection, the state of the resource is *unknown*, not failed.
Read it back before you act on the assumption that nothing happened.

## Every error code

Codes are enumerated per service, and the vocabularies genuinely differ — the same HTTP
status carries different codes depending on which API you called. A missing object shows it
most clearly: Order Management names one as `NO_SUCH_ORDER` or `NO_SUCH_CAPTURE`, and the other
APIs do not name it at all.

Not every `404` carries a code at all. A missing or expired Checkout order, a missing Hosted
Payment Page session and an unknown customer token all answer with an empty body, so there is
nothing to branch on but the status. That includes Hosted Payment Page's `NOT_FOUND`: it is in
that API's list of codes, but the `404` that means it arrives without a body.

They are grouped below by the API that returns them. Two kinds read differently:

- A **state** error means the object was not where the call required it to be. Nothing about
  the request was wrong, so the fix is never to change the payload — it is to read the object
  and look at where it actually is. These carry the lifecycle, with the status the call needs
  marked.
- A **request** error means the request itself was wrong, or the thing it named does not
  exist.

The retry line on each is the one to read first: some failures can never succeed on a second
attempt, and retrying them only burns the budget and delays the message someone actually
needs.

Two consequences worth designing for. First, one code can arrive under several statuses —
Checkout's `HTTP_ERROR` covers `401`, `404`, `405`, `406` and `415` alike — so a handler keyed on
the code alone is ambiguous. Key on the pair. Second, a code you have never seen is not a bug: new enum values are a non-breaking
change, so your handler needs a default branch that logs and escalates rather than throwing.

## Related

- [Retrying safely after a 5xx](/order-management/idempotency-and-retries/) — the idempotency key and the retry schedule
- [Backing off after a 429](/api/limits/) — the rate limit headers and what they mean
- [Checking a credential when you get a 401](/api/authentication/) — which key belongs to which environment
- [Reading an order back after an unknown outcome](/order-management/update-and-cancel/) — the amounts that tell you what actually happened
