Skip to content

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.

{
"error_code": "BAD_VALUE",
"error_messages": ["Invalid postal code for purchase_country SE"],
"correlation_id": "8fd05d5b-3f36-4c94-9f11-a2f0f6d1c8a1"
}
FieldTypeWhat it is for
error_codestringALL_CAPS_SNAKE_CASE, enumerable per service. The only part of the body you may branch on.
error_messagesarray of stringsEnglish, for humans, for your logs. Never displayed to a customer, never parsed.
correlation_idstringIdentifies 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

Section titled “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 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.
StatusMeaningWhat to do
2xxSuccess.Accept any 2xx. Do not code against one specific success code.
4xxYour 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.
429Rate limited.Back off and retry. See Limits.
5xxKustom-side failure, or an unknown outcome.On Order Management, retry with the same idempotency key on the schedule in 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.

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.

Checkout API

BAD_REQUEST400requestNever retry

The request itself cannot be processed, with no single field to name.

  • A body Kustom cannot parse as an order
  • A post-purchase update sent to an order that is not in a post-purchase state

Fix: Check the request shape against the Checkout API reference before looking at field values.

BAD_VALUE400requestNever retry

A field on the order is missing, malformed, or inconsistent with another field.

  • Order line totals that do not add up to order_amount
  • A tax amount that does not match the rate and the line total
  • A merchant_urls value that is not HTTPS
  • Two fields that are each valid but cannot be combined -- reported as "Invalid combination of fields"

Fix: The error_messages name the field, in the form "Bad value: <field>". See /checkout/tax-and-order-lines/ for the amounts that must reconcile.

FORBIDDEN403requestNever retry

The request tried to set a field that only Kustom may write.

  • An order read back and sent again in full, including its read-only fields

Fix: The message names the field. Send only the fields you set.

HTTP_ERROR401404405406415requestNever retry

The request never reached an order -- it was unauthenticated, addressed nothing, or used a method, Accept header or Content-Type the endpoint does not serve.

  • Missing or wrong credentials
  • A path that does not resolve to a resource
  • A verb the endpoint does not serve
  • A missing or wrong Content-Type on a POST

Fix: Read the HTTP status -- the same code covers five of them. The single message is an empty string, so the status is the whole signal.

INTERNAL_SERVER_ERROR500transientRetry with backoff

Something failed inside Kustom.

  • An unhandled failure in the Checkout service

Fix: Create a new order. If it repeats, send the correlation_id to merchant support.

READ_ONLY_ORDER403stateNever retry

The order is completed or aborted, so it accepts no further updates.

  1. checkout_incomplete
  2. checkout_complete
  3. AUTHORIZED
  4. PART_CAPTURED → CAPTURED
  5. CLOSED

The call needs checkout_incomplete. This error means it was something else.

  • The shopper completed the purchase before your update arrived
  • The order was aborted

Fix: Message is always "Resource is read-only". Read the order and handle it as finished; do not retry.

Customer Tokens API

BAD_VALUE400requestRetry after fixing the request

A field in the request is invalid, or the token belongs to a different merchant ID.

  • A field failed validation -- error_messages names it, as "Bad value: <field>"
  • The token was created under another MID -- error_messages reads "Token do not belong to merchant"
  • A status update named a value other than CANCELLED

Fix: Correct the named field. A token belonging to another MID can only be used by that MID.

PAYMENT_METHOD_FAILED403transientRetry with backoff

The charge failed. The method behind the token did not work this time.

  • Insufficient funds
  • An issuer blip

Fix: Back off and retry rather than cancelling the subscription on the first miss. The body carries a correlation_id for support. When retries keep failing, ask the customer to update their payment method -- which means a new first purchase and a new token.

Hosted Payment Page API

BLACKLIST_REFUSED400requestNever retry

The recipient is blocked.

  • The address or number is on a suppression list

Fix: Use a different recipient, or contact Kustom support.

CONSTRAINT_VIOLATION400requestNever retry

The request is malformed.

  • A field is missing or fails validation

Fix: Correct the request body and resend.

EMAIL_LIMIT_EXCEEDED429transientRetry with backoff

The Playground daily e-mail cap was hit.

  • Repeated distribution testing on Playground

Fix: Wait for the cap to reset. This limit is a Playground constraint, not a production one.

MISSING_CONTACT_DATA400requestNever retry

The contact block is incomplete.

  • Neither an e-mail address nor a phone number was supplied

Fix: Supply the contact detail for the distribution method you asked for.

NOT_FOUND404requestNever retry

The session no longer exists. The 404 carries no body, so there is no error_code to branch on -- the status is the whole signal.

  • The session expired
  • The session id is wrong

Fix: Create a new session.

UNSUPPORTED_COUNTRY400requestNever retry

The phone number's country is not valid in this region.

  • An SMS distribution to a country the account's region does not cover

Fix: Distribute by e-mail, or use a number in a supported country.

In-Person Payments API

CARD_VELOCITY_EXCEEDED200requestNever retry

A velocity limit was hit.

  • Too many transactions on the card in a short window

Fix: The clerk asks for another payment method. Your backend does nothing.

DECLINED_GENERIC200requestNever retry

Declined, with no specific reason given.

  • The issuer refused without saying why

Fix: The clerk retries on the device or asks for another card. Your backend does nothing.

EXPIRED_CARD200requestNever retry

The card has expired.

  • The expiry date has passed

Fix: The clerk asks for another payment method. Your backend does nothing.

INCORRECT_CVC200requestNever retry

The CVC was wrong.

  • The entered CVC does not match the card

Fix: The clerk retries on the device. Your backend does nothing.

INCORRECT_NUMBER200requestNever retry

The card number is invalid.

  • A mistyped or unreadable card number

Fix: The clerk retries on the device. Your backend does nothing.

INSUFFICIENT_FUNDS200requestNever retry

Declined for funds.

  • The card has insufficient balance for the amount

Fix: The clerk asks for another payment method. Your backend does nothing.

LOST_CARD200requestNever retry

The card is reported lost.

  • The issuer has flagged the card

Fix: The clerk asks for another payment method. Your backend does nothing.

PROCESSING_ERROR200transientNever retry

A terminal processing error.

  • The terminal could not complete the transaction

Fix: The clerk retries on the device. Your backend does nothing.

STOLEN_CARD200requestNever retry

The card is reported stolen.

  • The issuer has flagged the card

Fix: The clerk asks for another payment method. Your backend does nothing.

Order Management API

CANCEL_NOT_ALLOWED403stateNever retry

The order has captures, so it can only be refunded.

  1. checkout_incomplete
  2. checkout_complete
  3. AUTHORIZED
  4. PART_CAPTURED → CAPTURED
  5. CLOSED

The call needs AUTHORIZED. This error means it was something else.

  • Part or all of the order is already captured
  • The order is already cancelled

Fix: Cancel releases an authorization. Once money has moved, refund is the operation.

CAPTURE_NOT_ALLOWED403stateNever retry

The order is not in a state that permits capture.

  1. checkout_incomplete
  2. checkout_complete
  3. AUTHORIZED
  4. PART_CAPTURED → CAPTURED
  5. CLOSED

The call needs AUTHORIZED. This error means it was something else.

  • The order is already fully captured
  • The order was cancelled
  • The authorization expired -- by default after about 28 days

Fix: Read the order and check remaining_authorized_amount before capturing.

NO_SUCH_CAPTURE404requestNever retry

No capture exists with that id on this order.

  • The capture id belongs to a different order

Fix: List the order's captures and use an id from the response.

NO_SUCH_ORDER404requestNever retry

The order_id does not exist, or belongs to the other environment.

  • Playground credentials against a production order, or the reverse
  • The id belongs to another merchant

Fix: Check the environment matches the credential. See /api/authentication/.

NOT_ALLOWED403stateNever retry

The order is not in a state that permits this change.

  1. checkout_incomplete
  2. checkout_complete
  3. AUTHORIZED
  4. PART_CAPTURED → CAPTURED
  5. CLOSED

The call needs AUTHORIZED. This error means it was something else.

  • The order has moved past the point where the field can be amended

Fix: Read the order and look at its status.

PAYLOAD_TOO_LARGE413requestNever retry

The request body exceeds the accepted size.

  • An order with a very large number of order lines

Fix: Reduce the payload. See /api/limits/.

REFUND_NOT_ALLOWED403stateNever retry

The order has no captured amount to refund.

  1. checkout_incomplete
  2. checkout_complete
  3. AUTHORIZED
  4. PART_CAPTURED → CAPTURED
  5. CLOSED

The call needs CAPTURED. This error means it was something else.

  • Nothing has been captured yet
  • The full captured amount is already refunded

Fix: Capture before refunding, and check captured_amount against what you have already returned.

TEMPORARILY_UNAVAILABLE503transientRetry with backoff

The service is temporarily unavailable.

  • A Kustom-side failure

Fix: Retry with an idempotency key. See /order-management/idempotency-and-retries/.

TOO_MANY_REQUESTS429transientRetry with backoff

You are being rate limited.

  • Sustained request rate above the account's limit

Fix: Back off using the rate limit headers. See /api/limits/.

UPDATE_NOT_ALLOWED403stateNever retry

The order's state or risk assessment does not allow changing its amount or address.

  1. checkout_incomplete
  2. checkout_complete
  3. AUTHORIZED
  4. PART_CAPTURED → CAPTURED
  5. CLOSED

The call needs AUTHORIZED. This error means it was something else.

  • The order is captured, cancelled or expired
  • The change was refused on risk grounds

Fix: Read the order and look at its status.

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.

This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.