Error Handling
All API errors follow a consistent structure for programmatic handling. Parse type and code to determine what happened and how to recover.
This page covers the v3 structured error model. For the v1/v2 error and rate-limit model, see Errors and rate limits on the legacy portal.
Error Response Structure
Error Types
Each type maps to an HTTP status code:
Common Error Codes
These tables cover every code on the flows these guides walk through. Codes for Apple Pay sessions are listed on those endpoints in the API Reference.
Authentication & Authorization
Headers & Request Validation
Carts (POST /carts and adding items)
Cart Vouchers (POST /carts/{cartId}/vouchers)
Currency
Returned when a requested currency can’t be used. Which value gets which code is in Currency.
Cart state
Every operation on an existing cart — reads included — answers CART_EXPIRED (410) for a cart
that is gone (timed out, deleted, or no longer holding inventory) and CART_ALREADY_ORDERED
(409) for a cart that became an order, including one since canceled; look that order up with
GET /orders?cartId={cartId} (see After Checkout). details.orderId
names it when it can be found. Deleting a cart is the exception: see Create Cart.
Checkout (POST /orders)
A KLARNA or AFTERPAY order completes on a second call, made after the shopper approved the
payment. If that call is refused because the payment can no longer become this order —
CART_EXPIRED, EXPECTED_TOTAL_MISMATCH, PAYMENT_METHOD_NOT_AVAILABLE, or the cart’s currency no
longer being available — the
approved payment is canceled before the answer arrives, so the shopper approves again from the
first call. A refusal you can correct, such as a missing phone number or the wrong
expectedTotal.currency, leaves the approval open: fix the request and send it again with the same
approval result under a new Idempotency-Key.
Idempotency (POST /orders)
General
Rate Limits
Each API key may make a set number of requests per retailer in a window, across every endpoint. The limit sits far above what a storefront needs, and exists to stop a key that is leaked or stuck in a retry loop. Once a key reaches it, every endpoint answers 429 RATE_LIMITED for that retailer until the window ends.
A response counted against the limit carries two headers. Read them when they are there, but don’t depend on them: handle 429 whether or not you saw them.
A response served from our CDN cache never reaches the API, so it is not counted and repeats the headers of the response that was cached. Read X-RateLimit-Remaining on a cached response as a guide, not a live count.
A 429 from this limit also carries Retry-After, the most seconds to wait before sending the request again. A 429 for any other reason may not; see Retry Strategy. A key serving several retailers has a separate limit for each retailer it sends in X-TT-Retailer.
Retry Strategy
Safe to Retry
- 5xx errors — server-side issues are usually transient, including
503 CURRENCY_TEMPORARILY_UNAVAILABLE(a sold currency with no current exchange rate). Retry with exponential backoff (1s, 2s, 4s, max 3 attempts). OnPOST /orders, a 5xx uses up theIdempotency-Key, so retry with a new key; if the first attempt did place the order, you get409 CART_ALREADY_ORDEREDinstead of a second order. - 429 Rate Limit — wait at most the number of seconds in the
Retry-Afterheader before retrying. When the header is absent the delay is not known; back off as for a 5xx. - Network timeouts on
POST /orders— you don’t know whether the order went through, so send the same request with the sameIdempotency-Key. You get one of these answers:- The order, with
200rather than201— the first attempt succeeded, and this is the order it placed. 409 IDEMPOTENCY_KEY_ALREADY_PROCESSING— the first attempt is still running. Wait, then send the same request again.409 IDEMPOTENCY_KEY_FAILED— the first attempt failed. Send the order with a new key.409 CART_ALREADY_ORDERED— the key is over 24 hours old, so it no longer remembers the attempt, and the cart already became an order. Look it up withGET /orders?cartId=.
- The order, with
Not Safe to Retry (Without Changes)
- 400 errors — fix the request before retrying (wrong field value, missing field, invalid quantity).
- 401/403 errors — fix authentication or permissions (
RISK_CHECK_FAILEDis the exception — see below). - 403
RISK_CHECK_FAILED— the order was declined by risk checks. Retrying will not succeed, with the same or a different payment method. Showmessageand direct the customer to contact customer support. - 402
PAYMENT_AUTHENTICATION_REQUIRED— run the challenge fromdetails.actionfirst, then place the order again with the result. Use a newIdempotency-Key: the first attempt retired the old one, so reusing it returns 409IDEMPOTENCY_KEY_FAILED. Nothing was charged, and the cart is still held. - 402
PAYMENT_DECLINED— the card was refused, and retrying the same card will be refused again. Collect a different payment method, then place the order again with a newIdempotency-Key. - 409 Conflict — state has changed; re-fetch the resource and adapt.
- 410 Gone — resource is permanently unavailable; start over (e.g., create a new cart).
Idempotency Keys
The Idempotency-Key header on POST /orders is optional and strongly recommended. Without it, a cart still becomes at most one order — a second attempt gets 409 CART_ALREADY_ORDERED — but you can’t tell a lost response from a failure. With it:
- Generate a UUID v4 for each purchase attempt. A value that isn’t a valid UUID v4 is rejected with
INVALID_IDEMPOTENCY_KEY. - Reuse the key only to resend the same request after a timeout. See Network timeouts for the answers you can get.
- After an error response that lets you try again, place the order again with a new key. Most of these, including a
400for a field you then fix, use up the key, and resending it returns409 IDEMPOTENCY_KEY_FAILED. Do not retry403 RISK_CHECK_FAILED. After409 CART_ALREADY_ORDERED, look the order up withGET /orders?cartId={cartId}instead of buying again. A 5xx does not prove nothing was placed, so a new key is safe only because the cart can become just one order. Reuse a key only when no response came back, or after409 IDEMPOTENCY_KEY_ALREADY_PROCESSING. - Same key + different
cartId→409 IDEMPOTENCY_KEY_CART_MISMATCH. - Keys belong to your retailer and expire after 24 hours.
Forward Compatibility
The API may introduce new error codes and types at any time. To avoid breaking your integration:
- Switch on
typefor broad error handling (authentication vs validation vs server error) - Switch on
codefor specific recovery logic (e.g.,CART_EXPIRED→ create new cart) - Use a default/fallback case for unknown
typeandcodevalues - Display
messageto users but never parse it programmatically — it may change