# API error codes (/en/integrate/errors/api-error-codes)

Every code returned in `error.code` on a failed API call, grouped by domain. Each entry's URL is the value of `error.doc_url`.

This page is the destination of every `error.doc_url` you receive on a failed API call. Anchors here match the `code` value, so `https://docs.xpay.app/integrate/errors/api-error-codes#parameter_missing` lands directly on the row below.

Each code is paired with the `error.type` it ships with. Branch on `type` first; use `code` for narrow special-cases. For the envelope and the handler shape, see [API errors](/integrate/errors/api-errors).

## Generic request [#generic-request]

`error.type`: `invalid_request_error`. The request was rejected because of something on your side. Don't retry; fix the request.

### `invalid_request` [#invalid_request]

The request was rejected and no more specific code applied. Read `error.message` for detail.

### `parameter_missing` [#parameter_missing]

A required parameter was missing from the request body. `error.param` names the field.

### `parameter_invalid` [#parameter_invalid]

A parameter has the wrong type or format. `error.param` names the field.

### `parameter_out_of_range` [#parameter_out_of_range]

A parameter value is outside the allowed range (for example a negative `quantity` or an `amount` exceeding the maximum).

### `parameter_unknown` [#parameter_unknown]

Your request included a parameter XPay doesn't recognize. Usually a typo or a stale field. Remove the unknown parameter.

### `parameters_exclusive` [#parameters_exclusive]

You sent two parameters that can't be used together (for example `customerId` and `customerDetails` on a Checkout Session). Pick one.

### `parameter_requires_another` [#parameter_requires_another]

You sent a parameter that requires another to also be present (for example `customerUpdate` requires `customerId`).

### `validation_error` [#validation_error]

A generic validation failure that didn't match any of the more specific codes above. Read `error.message`.

### `resource_missing` [#resource_missing]

The resource you referenced doesn't exist. Returned with HTTP 404. Check the ID and that you're using the right key (test vs live).

### `resource_invalid_state` [#resource_invalid_state]

The resource exists but isn't in a state that allows this operation (for example trying to expire an already-completed Checkout Session, or refund a charge that was never captured).

## Authentication [#authentication]

`error.type`: `authentication_error`. HTTP 401. Don't retry; fix the credential or the header.

### `authentication_required` [#authentication_required]

No API key was provided. The `Authorization` header is missing.

### `invalid_api_key` [#invalid_api_key]

The API key didn't match any known key. Common causes: typo, leading/trailing whitespace, using a key from a different account.

### `api_key_inactive` [#api_key_inactive]

The API key was found but is currently disabled. Re-enable it in **Developers → API keys** in the dashboard, or use a different key.

### `invalid_signature` [#invalid_signature]

For endpoints that require an HMAC signature, the signature in the request didn't match the expected value. Check the signing secret and that you're signing the canonical body.

## Authorization [#authorization]

`error.type`: `invalid_request_error`. HTTP 403.

### `merchant_not_activated` [#merchant_not_activated]

Your account hasn't completed live-mode activation. Test-mode calls work as expected; live-mode calls return this until you finish onboarding.

### `permission_denied` [#permission_denied]

The API key or session you're using lacks permission for this action. Check the key's scoped permissions in **Developers → API keys**.

### `two_factor_required` [#two_factor_required]

A live-mode action that needs a recent two-factor check, on a session that hasn't done one in the last 4 hours. Dashboard sessions only; API keys never see this. The dashboard handles it for you by asking for your authenticator code and retrying.

## Sessions [#sessions]

`error.type`: `invalid_request_error`.

### `checkout_session_expired` [#checkout_session_expired]

The Checkout Session is past its `expiresAt`. Create a new session if the customer still wants to pay.

### `invalid_client_secret` [#invalid_client_secret]

The `clientSecret` doesn't match the session it was paired with. Most often this means you pasted a stale secret or paired it with the wrong session ID.

### `creation_failed` [#creation_failed]

Session creation failed for an unexpected reason. Retry the request; if it persists, quote the `request_id` to support.

## Payment links [#payment-links]

`error.type`: `invalid_request_error`.

### `payment_link_inactive` [#payment_link_inactive]

The Payment Link is deactivated and won't accept new payments. Reactivate it in the dashboard or create a new link.

### `payment_link_expired` [#payment_link_expired]

The Payment Link is past its expiration date.

## Amount and currency [#amount-and-currency]

`error.type`: `invalid_request_error`.

### `amount_invalid` [#amount_invalid]

The `amount` is not a valid value (negative, zero where positive is required, or exceeds the per-call maximum).

### `currency_invalid` [#currency_invalid]

The currency code is not recognized. Use a 3-letter ISO 4217 code in lowercase (e.g. `egp`, `usd`).

## Prices and line items [#prices-and-line-items]

`error.type`: `invalid_request_error`. Returned when a line item references a Price that can't be used, at Checkout Session create or at Payment Link create or update.

### `product_archived` [#product_archived]

The Price's parent Product is archived. Archiving a Product cascades `active: false` to all of its Prices in one transaction, so an archived Product's Prices are unusable until the merchant un-archives the Product. Unarchive is one-way: re-activating the Product does NOT automatically re-activate its Prices. Each Price must be un-archived explicitly.

Fires from:

* `PATCH /prices/:id` with `{ "active": true }`: you tried to re-activate a Price whose Product is still archived. Un-archive the Product first, then the Price.
* `POST /payment-links` / `PATCH /payment-links/:id`: a referenced Price belongs to an archived Product. Use a Price under an active Product.

### `price_inactive` [#price_inactive]

The Price is archived (`active: false`). This happens either when the merchant archived the Price directly, or when the Price was cascade-archived as part of its parent Product being archived. Un-archive the Price (or its Product first, if the Product is also archived), or use a different Price.

### `price_not_yet_active` [#price_not_yet_active]

The Price has a future `startDate` and isn't yet active.

### `price_expired` [#price_expired]

The Price is past its `expirationDate`.

### `price_sold_out` [#price_sold_out]

The Price has finite stock and ran out before the customer paid. Remove the line, lower the requested quantity, or restock.

### `price_recurring_not_supported` [#price_recurring_not_supported]

The Price is `RECURRING` type, which isn't accepted in checkout today. You'll also get this when creating or updating a payment link that references one. Use a `ONE_TIME` price.

### `price_immutable_while_used` [#price_immutable_while_used]

You tried to change a field on a Price that's been used in at least one line item. Currency, amount, type, and `recurring` settings are locked once a price is in use.

### `price_date_range_invalid` [#price_date_range_invalid]

You tried to save a Price whose `startDate` is on or after its `expirationDate`.

### `price_stock_invalid` [#price_stock_invalid]

You tried to save a Price with a negative `stock` value.

### `line_item_missing_price` [#line_item_missing_price]

A line item on a Checkout Session was sent without a `price` reference or a `priceData` block. Provide one.

### `checkout_empty_cart` [#checkout_empty_cart]

Every line on the Checkout Session is at quantity 0, so there's nothing to pay for. This happens on an all-optional order when the customer tries to pay before adding an item. Add at least one item to the order, then submit payment again.

## Promotions and coupons [#promotions-and-coupons]

`error.type`: `invalid_request_error`.

### `promotion_codes_not_allowed` [#promotion_codes_not_allowed]

The customer entered a promotion code on a session where `allowPromotionCodes` is `false`.

### `promotion_code_not_found` [#promotion_code_not_found]

The promotion code string doesn't match any code on this account.

### `promotion_code_inactive` [#promotion_code_inactive]

The promotion code is deactivated.

### `promotion_code_expired` [#promotion_code_expired]

The promotion code is past its expiry date.

### `promotion_code_max_redemptions` [#promotion_code_max_redemptions]

The promotion code has reached its global redemption limit.

### `promotion_code_customer_mismatch` [#promotion_code_customer_mismatch]

The promotion code is restricted to a different customer than the one on this Checkout Session.

### `promotion_code_minimum_amount` [#promotion_code_minimum_amount]

The session amount doesn't meet the promotion's minimum.

### `promotion_code_first_time_only` [#promotion_code_first_time_only]

The promotion code is for first-time customers only, and this customer has paid before.

### `promotion_code_exists` [#promotion_code_exists]

You tried to create a promotion code with a code string that already exists. Use a different string.

### `coupon_invalid` [#coupon_invalid]

The coupon is invalid, deleted, or expired.

### `coupon_currency_mismatch` [#coupon_currency_mismatch]

The coupon's currency doesn't match the session's currency.

### `coupon_minimum_amount` [#coupon_minimum_amount]

The session subtotal doesn't meet the coupon's minimum.

### `coupon_customer_max_redemptions` [#coupon_customer_max_redemptions]

The customer has reached this coupon's per-customer redemption limit.

### `coupon_in_use` [#coupon_in_use]

You tried to delete a coupon that's still attached to an active promotion code or session.

### `too_many_discounts` [#too_many_discounts]

A Checkout Session can carry at most one discount; you tried to add a second.

## Payment methods [#payment-methods]

`error.type`: `invalid_request_error`.

### `payment_method_corrupted` [#payment_method_corrupted]

The stored payment method data can't be read. Have the customer re-enter their details.

### `payment_method_customer_mismatch` [#payment_method_customer_mismatch]

The payment method belongs to a different customer than the one on the request.

### `payment_intent_customer_mismatch` [#payment_intent_customer_mismatch]

The Payment Intent's customer doesn't match the customer you're trying to act on.

### `payment_method_mismatch` [#payment_method_mismatch]

The payment method type the customer chose isn't included in the session's allowed `paymentMethodTypes`.

## Charges and refunds [#charges-and-refunds]

`error.type`: `invalid_request_error`. Returned by `POST /refunds` and related charge operations.

### `charge_not_captured` [#charge_not_captured]

You tried to refund a charge that hasn't been captured.

### `merchant_no_balance` [#merchant_no_balance]

The merchant doesn't have a balance record. Quote the `request_id` to support.

### `insufficient_balance` [#insufficient_balance]

The refund would push the available balance negative.

### `charge_missing_balance_transaction` [#charge_missing_balance_transaction]

The charge is missing its underlying balance transaction. Quote the `request_id` to support.

### `charge_missing_fee_data` [#charge_missing_fee_data]

The charge is missing the fee data needed to compute the refund. Quote the `request_id` to support.

### `charge_incomplete_fee_data` [#charge_incomplete_fee_data]

The charge has fee data but it's incomplete. Quote the `request_id` to support.

## Payment method configurations [#payment-method-configurations]

`error.type`: `invalid_request_error`.

### `cannot_rename_default` [#cannot_rename_default]

You tried to rename the default payment-method configuration. Names of the default cannot be changed.

### `must_have_enabled_method` [#must_have_enabled_method]

A configuration must have at least one enabled payment method.

### `cannot_delete_default` [#cannot_delete_default]

You tried to delete the default payment-method configuration. Make a different configuration the default first.

### `configuration_in_use` [#configuration_in_use]

You tried to delete a configuration that's referenced by one or more active Checkout Sessions.

## Currency [#currency]

`error.type`: `invalid_request_error`.

### `unsupported_currency` [#unsupported_currency]

The currency isn't supported for this merchant or this operation.

### `exchange_rate_not_found` [#exchange_rate_not_found]

No exchange rate is configured for this currency pair. Multi-currency presentment requires the rate to be available before session creation.

## Rate limiting [#rate-limiting]

`error.type`: `rate_limit_error`. HTTP 429.

### `rate_limit` [#rate_limit]

Your account exceeded the request rate limit. Back off and retry with exponential delay.

## Idempotency [#idempotency]

`error.type`: `idempotency_error`. HTTP 400 or 409. For the full retry contract, see [Idempotency](/integrate/idempotency).

### `idempotency_key_in_use` [#idempotency_key_in_use]

Two cases share this code, told apart by the HTTP status:

* **`400`**: you reused an `Idempotency-Key` with a different request (a different method, path, query, or body). A key is bound to the first request it was used with. Reuse the original request, or generate a fresh key.
* **`409`**: a request with this key is still in progress. Wait the number of seconds in the `Retry-After` header, then retry. You'll get the original request's result.

## System [#system]

`error.type`: `api_error`. HTTP 500.

### `internal_error` [#internal_error]

An unexpected server-side error. Retry with backoff. If it persists, quote the `request_id` to support.

## Where to next [#where-to-next]

<Cards>
  <Card icon="<TriangleAlert />" title="API errors" href="/integrate/errors/api-errors">
    The envelope, the three error types, and the handler pattern.
  </Card>

  <Card icon="<CreditCard />" title="Payment errors" href="/integrate/errors/payment-errors">
    The other surface: when a customer's card fails, the error sits on `lastPaymentError`, not in an
    HTTP response.
  </Card>

  <Card icon="<ListTree />" title="Payment error codes" href="/integrate/errors/payment-error-codes">
    The reference for `lastPaymentError.code`. Card validation, processing, fraud, and processor
    codes.
  </Card>

  <Card icon="<Layers />" title="Errors introduction" href="/integrate/errors/introduction">
    Back to the top. The two-surfaces map.
  </Card>
</Cards>