---
id: api-errors
title: Errors by HTTP status
summary: Every API error code, ordered by the status you got back, with what to do about each.
faces: ["public", "agent"]
section: back-end
group: API reference
slug: errors
order: 45
next: ["api-stripe-compatibility"]
---
# Errors by HTTP status

You arrived here holding a status code from a log. This page is ordered for that. If you are holding an SDK
error code from a browser instead, [Errors](/js/errors) is the same information ordered for you.

Every refusal answers `{"error": "<code>", "message": …, "request_id": …}` and carries `Rip-Request-Id`.
Quote the request id to support. The three anonymous reads answer a shorter body with no request id.

Both pages are generated from one catalogue, which is checked against the API's own code on every run, so
they cannot disagree with each other or with the API.

<!-- generated from docs/catalogue/errors.json — edit the catalogue, not this -->

### 400

Bad request — the request is malformed, and sending it again unchanged will not help

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `card_data_rejected` | A value that looks like a card number was sent in a free-text field. It is never echoed back. | Remove it from metadata, the customer name or email, the statement descriptor, the order reference, and item sku, name or type. Order references need fewer than 13 digits, or a letter. | no — Never with the same payload. |
| `currency_required` | No currency was given and none could be resolved. | Pass a currency, or set the integration's default. The order is: integration default, then the one active live acquiring profile, then the registered country. A server create with no currency gets invalid_request instead. | no — Never as sent. |
| `customer_email_required` | Every payment needs the shopper's email address. | Pass the customer email to collect() or to the session create, or call setEmail(). Direct API callers send it on confirm. | The same request, with the customer email. |
| `hold_duration_out_of_range` | The hold duration must be between one and five days. | Set a duration of one to five days. The default is five. | no — nothing changes the answer |
| `idempotency_key_missing` | This write needs an idempotency key header of one to 255 printable ASCII characters. | Send a unique key for each logical create. The Stripe-compatible create also accepts Stripe's own header, but not both with different values. | no — Never as sent. |
| `invalid_request` | A field is malformed. The message names the field, never the value. | Read the message. Amounts are positive minor units; a server create needs a three-letter currency; the confirmation URL must be https on an approved origin and at most 512 characters; the order reference is at most 64 characters with no control characters; at most 100 items; the body must be JSON. | no — Never as sent. |
| `invalid_url` | A webhook URL must be absolute, public and https. | Do not use localhost, .local, .internal, a loopback address or a private IP literal. | no — Never as sent. |
| `metadata_too_large` | More than 50 metadata entries, more than 8 KB of metadata, or a statement descriptor suffix longer than 22 characters. | Trim the metadata or the suffix. | no — nothing changes the answer |
| `origin_invalid` | An allowed origin must be exactly a scheme, host and optional port: https, or http only for localhost and 127.0.0.1. No path and no wildcard. | Enter an origin such as https://shop.example. | no — nothing changes the answer |
| `unknown_event_type` | An event filter named an event the registry does not know. The refusal lists the allowed values. | Use a value from the allowed list the refusal returns. | no — nothing changes the answer |

### 401

Unauthorized — the credential is missing, wrong or revoked

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `key_revoked` | The key was revoked. | Issue a new key and deploy it. | no — Never. A new key is needed. |
| `public_key_missing` | The merchant has no publishable key, so no browser can load the checkout. | Create or activate the publishable key. | no — Never until a publishable key exists. |
| `unauthorized` | The credential is missing, invalid or expired, or the key's IP or origin allow-list blocked the call. The reason is withheld from browsers. | From a server, send the ripper API key header. The Stripe-compatible routes take a bearer token instead. In a browser, check the key on the script tag and that the client secret matches the session id. | no — Never with this credential. |

### 403

Forbidden — the credential is real but not allowed to do this

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `key_live_only` | A live-only key was used while the effective mode is test. | Turn live payments on, set the integration mode to auto, or use a key that allows test mode. | no — Never until the mode is fixed. |
| `merchant_not_active` | The account cannot take payments in its current state. | Finish onboarding, or resolve the account status with ripper. | no — Never until the account is active. |
| `origin_not_allowed` | The page's origin, or a missing origin, is not in the integration's allowed origins. | Add the exact origin, such as https://shop.example, to that integration's allowed origins. Hosted-page origins are managed by ripper. | no — Never until the origin is added. |
| `permission_denied` | The secret key lacks the permission this route needs. | Use a key that can create payments, for creates, or read transactions, for reads. | no — nothing changes the answer |

### 404

Not found — no such thing for this merchant, in this mode

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `compat_provider_unknown` | ripper only emulates Stripe. | Use the Stripe-compatible path, or the native API. | no — nothing changes the answer |
| `handle_not_found` | There is no checkout for this handle or key. The config read also answers it for an unknown integration name, a revoked or inactive key, and a live-only key on a test integration. | Check the key on the script tag, the handle, the integration name, and the key and merchant status. | no — Never. The 404 is not cached. |
| `resource_not_found` | There is no such session, payment, integration or hold for this merchant. Another merchant's id looks the same as an unknown one. | Check the id, and that you are using the same merchant's key and the same mode. | no — nothing changes the answer |

### 409

Conflict — the thing exists but is not in a state that allows this

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `attempts_exhausted` | The session used all three of its attempts. | Create a new session. | no — Never on this session. Create a new one. |
| `authentication_incomplete` | 3-D Secure was not finished: either awaiting the challenge, or never started. | Nothing, if you use the SDK. If you call the API directly, finish the challenge or authenticate first, then post the confirm again. | The same request, once the challenge is finished. |
| `capture_window_ended` | The window for charging this hold has ended, or is unknown, so it can only be released. | Release the hold, and take a new payment if you still need one. | no — Never. The hold can only be released. |
| `hold_already_decided` | The held payment has already been captured or voided. | Read the payment to see how it was decided. | no — nothing changes the answer |
| `idempotency_key_reused` | The key was used for a different request or endpoint, or its first request is still running. | A different request needs its own key. This one code covers both a key already used for a different request and a key whose first request is still running, so do not tell them apart by the code. After a refusal a new key is safe, because the refused request created nothing; after a timeout reuse the same key, because that request may have created a session. | Only when the first request with this key is still running, which is a retry of an attempt that returned nothing — never a retry after a refusal. A different body or endpoint is refused for as long as this key's record lives. |
| `integration_builtin` | The built-in integrations cannot be created, renamed or removed, and their origins are set by ripper. | Use a custom integration instead. | no — nothing changes the answer |
| `integration_name_taken` | An active integration already has that name. | Pick another name. | no — nothing changes the answer |
| `operation_in_progress` | Another capture or void on this payment is still open. | Wait, then read the payment. | After a wait. The same decision id replays. |
| `session_completed` | This session has already been paid. | Treat it as paid. Read the session or payment from your server, and do not charge again. | no — nothing changes the answer |
| `session_declined` | The session ended on a hard or fraud decline, or on its last soft decline. | Create a new session. | no — Never on this session. |
| `session_expired` | The session's 30 minutes ran out before the payment was made. | Create a new session, or call collect() again. | no — Never on this session. |
| `session_processing` | Another tab is charging this session right now. | Do not retry the charge. Read the outcome instead. | After a wait, by reading the state rather than charging again. |

### 422

Unprocessable — the request is well formed but cannot be carried out as configured

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `acquiring_profile_incomplete` | Your live acquiring details are incomplete, so the charge cannot be sent. | Complete the live acquiring profile: active, BIN, currency and merchant category code, plus the merchant ID for 3-D Secure. Contact ripper support if it already looks complete. | After the profile is completed. The same session can then confirm. |
| `amount_unverified` | The integration requires a verified amount, and this one could not be verified. | Send items that match the price list, rely on the integration default, create the session from your server, or set the amount trust policy to allow unverified amounts. | no — Never as sent. The request has to change. |

### 500

Server error — something failed on our side

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `internal_error` | An unplanned fault. The message is always empty. | Retry idempotently. If it persists, quote the request id to support. | After a wait. On a create, reuse the same idempotency key. |

### 503

Unavailable — a dependency was unreachable; nothing was charged twice

| code | what it means | what to do | can a retry succeed? |
|---|---|---|---|
| `routing_unavailable` | No single acquirer could be chosen for this live payment, because there were none or several. | Contact ripper to fix live routing for this currency. | no — Not by waiting. This is configuration, not a transient fault. |
| `store_unavailable` | A ripper store was unreachable. Nothing was created and nothing was charged twice. | Retry with the same idempotency key. The wait in seconds appears only in the message. | After about five seconds, with the same request and the same idempotency key. |
| `three_ds_provider_unavailable` | The 3-D Secure provider is down. This is not a decline. | Let the shopper try again. | After a wait. |

<!-- end generated -->
