> For the complete documentation index, see [llms.txt](https://support.swisspay.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://support.swisspay.ai/api-reference/errors.md).

# Errors

SwissPay returns conventional HTTP status codes plus a JSON body carrying a broad `error.type`, a stable `error.code`, and a human-readable `error.message`.

```json
{
  "error": {
    "type": "validation_error",
    "code": "invalid_params",
    "message": "amount must be a positive integer"
  }
}
```

* `type` is the broad category. It is one of `invalid_request_error`, `authentication_error`, `validation_error`, `idempotency_error`, `rate_limit_error`, `provider_error`, or `api_error`.
* `code` is **stable** -- match against it in your code.
* `message` may evolve to be clearer over time and should be treated as human-readable only.

## Error catalogue

| HTTP      | `type`                  | `code`                                    | When you'll see it                                                                                                                                                                                                                                                             |
| --------- | ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400       | `invalid_request_error` | `missing_idempotency_key`                 | No `Idempotency-Key` header on `POST /api/v1/payments`.                                                                                                                                                                                                                        |
| 400       | `invalid_request_error` | `missing_field`                           | Malformed JSON or a required field is missing.                                                                                                                                                                                                                                 |
| 401       | `authentication_error`  | `missing_api_key`                         | No `Authorization` header.                                                                                                                                                                                                                                                     |
| 401       | `authentication_error`  | `invalid_api_key`                         | Unknown or revoked key.                                                                                                                                                                                                                                                        |
| 401       | `authentication_error`  | `key_mode_mismatch`                       | The key's mode no longer matches your workspace's environment -- e.g. the workspace was switched between sandbox and live after the key was created. Create a fresh key in the current environment. See [Authentication](/api-reference/authentication.md#test-and-live-keys). |
| 404       | `invalid_request_error` | `customer_not_found`                      | Payment referenced an unknown `cus_...`, or one belonging to a different merchant.                                                                                                                                                                                             |
| 404       | `invalid_request_error` | `not_found`                               | An unknown `pay_...`, `re_...`, `pm_...`, or `cus_...` id, including ids belonging to a different merchant.                                                                                                                                                                    |
| 409       | `idempotency_error`     | `key_reused`                              | Same `Idempotency-Key`, different request body.                                                                                                                                                                                                                                |
| 422       | `validation_error`      | `invalid_params`                          | Validation failure -- see *Common 422 causes* below.                                                                                                                                                                                                                           |
| 422       | `validation_error`      | `customer_email_taken`                    | Email already on file for this merchant (unique per merchant, case-insensitive).                                                                                                                                                                                               |
| 422       | `validation_error`      | `customer_external_id_taken`              | `external_id` already on file for this merchant.                                                                                                                                                                                                                               |
| 422       | `validation_error`      | `amount_invalid`                          | Refund amount is <= 0 or exceeds the remaining refundable amount.                                                                                                                                                                                                              |
| 422       | `validation_error`      | `payment_not_refundable`                  | The payment is not in `succeeded` status.                                                                                                                                                                                                                                      |
| 422       | `validation_error`      | `metadata_invalid`                        | More than the allowed number of metadata keys, or a value over the size limit.                                                                                                                                                                                                 |
| 422       | `validation_error`      | `provider_not_configured`                 | Your account has no active payment-provider connection.                                                                                                                                                                                                                        |
| 422       | `validation_error`      | `processing_not_enabled`                  | Live payments have not been enabled for your account yet -- activation is a manual step that completes after onboarding checks. Test-mode payments are exempt.                                                                                                                 |
| 422       | `validation_error`      | `processing_not_active`                   | Your activated account is currently suspended, restricted, or closed; the message names its current status. Processing resumes on its own once the account is restored.                                                                                                        |
| 422       | `invalid_request_error` | `no_provider_connection`                  | The merchant has no connected PayPal account (e.g. when minting a PayPal client token).                                                                                                                                                                                        |
| 422       | `validation_error`      | `processing_not_enabled`                  | A **live** payment (or `confirm`) before your account has been activated for live processing. The message reads `PayPal processing has not been enabled for this merchant`. Test-mode payments are exempt.                                                                     |
| 422       | `validation_error`      | `processing_not_active`                   | A **live** payment (or `confirm`) while an activated account is suspended, restricted, or closed. The message names the account's current status; processing resumes on its own once the account is restored.                                                                  |
| 429       | `rate_limit_error`      | `rate_limited`                            | Too many requests for this API key -- back off and retry.                                                                                                                                                                                                                      |
| 502       | `provider_error`        | `missing_approval_url`                    | A PayPal wallet order was accepted but PayPal returned no approval link. No money moved -- retry with a new `Idempotency-Key`.                                                                                                                                                 |
| 502 / 503 | `provider_error`        | `provider_error` / `provider_unavailable` | The upstream payment provider failed, refused, or timed out.                                                                                                                                                                                                                   |

## Decline handling

**Declined card payments return HTTP 200**, not a 4xx. We do this because the API call itself succeeded — the provider received the authorisation request and the issuer made a decision. The decision happened to be "no".

```json
{
  "id": "pay_01HABC...",
  "status": "failed",
  "failure": {
    "code": "refused",
    "message": "Refused by issuer"
  },
  "amount": 2999,
  "currency": "CHF"
}
```

If your test framework asserts only on HTTP status, declines will silently pass as successes. Always check `status` first:

```python
res = requests.post(url, json=body, headers=headers)
res.raise_for_status()              # 4xx / 5xx → exception
data = res.json()
if data["status"] != "succeeded":   # 200 OK with failure
    handle_decline(data["failure"]["code"], data["failure"]["message"])
```

## Failure codes

When `status: "failed"`, the `failure.code` will be one of:

| Code                                      | Meaning                                                                                                                                        |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `refused`                                 | Generic issuer refusal — most common.                                                                                                          |
| `do_not_honor`                            | The issuer declined the charge without a more specific reason. In the sandbox this is the outcome of the `CCREJECT-REFUSED` rejection trigger. |
| `expired_card`                            | The card on file is past its expiry.                                                                                                           |
| `insufficient_funds`                      | Self-explanatory.                                                                                                                              |
| `invalid_cvv`                             | The card security code (CVC) did not match.                                                                                                    |
| `invalid_card`                            | The card number is not a valid card.                                                                                                           |
| `restricted_card`                         | The card is not permitted for this transaction.                                                                                                |
| `declined`                                | The issuer or bank declined the charge (generic error).                                                                                        |
| `lost_card`, `stolen_card`, `pickup_card` | Card reported in the issuer's risk lists.                                                                                                      |
| `3ds_failed`                              | The cardholder failed the 3-D Secure challenge.                                                                                                |
| `3ds_abandoned`                           | The cardholder never completed the challenge.                                                                                                  |
| `3ds_token_expired`                       | The cardholder arrived at the challenge page after the 15-minute window.                                                                       |
| `3ds_not_available`                       | Your connection is in 3DS-required mode but the issuer can't perform 3-D Secure.                                                               |

## Common 422 causes

* `amount` ≤ 0 or non-integer.
* Bad `email` (failed RFC 5322 validation).
* `locale` not in `xx-XX` form.
* `metadata` exceeds 20 keys or a value exceeds 500 chars.
* `payment_method.holder_name` missing (required on every card payment).
* `success_url` / `failure_url` missing or non-HTTPS when 3-D Secure is enabled.

## What to log

For every API request, log the **request ID** we return in the `Swisspay-Request-Id` response header. If something goes wrong and you contact support, quoting that ID gets you to a resolution faster than any other piece of information.

## Retries

* `5xx` and connection timeouts: safe to retry with the **same** `Idempotency-Key`.
* `4xx`: don't retry — the request is malformed; fix it first.
* `200 status: failed` (decline): don't retry the same card. The customer can choose a different payment method.
