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.
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.
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 onerror_code, plus the HTTP status when the same code appears under two
statuses.
Logerror_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.
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.
checkout_incomplete
checkout_complete
AUTHORIZED
PART_CAPTURED → CAPTURED
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.
checkout_incomplete
checkout_complete
AUTHORIZED
PART_CAPTURED → CAPTURED
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.
checkout_incomplete
checkout_complete
AUTHORIZED
PART_CAPTURED → CAPTURED
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.
checkout_incomplete
checkout_complete
AUTHORIZED
PART_CAPTURED → CAPTURED
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.
checkout_incomplete
checkout_complete
AUTHORIZED
PART_CAPTURED → CAPTURED
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.
checkout_incomplete
checkout_complete
AUTHORIZED
PART_CAPTURED → CAPTURED
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.