> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vestrapay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> How failed responses are shaped and which codes matter when collecting payments.

Failed JSON bodies look like this:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "status": "failed",
  "error": "invalid_credentials",
  "message": "A human-readable explanation"
}
```

Branch on `error`. Include `X-Request-Id` from the response headers when you contact support.

## Auth and session

| `error`                     | Typical HTTP | What to do                                                     |
| --------------------------- | ------------ | -------------------------------------------------------------- |
| `invalid_credentials`       | 401          | Wrong or missing `x-api-key` / Bearer.                         |
| `session.expired`           | 410          | Access code timed out (60 minutes). Initialize a new payment.  |
| `merchant.kyb_not_approved` | 403          | Live keys before KYB is approved. Stay on test, or finish KYB. |

## Payment

| `error`                                  | Typical HTTP | What to do                                                   |
| ---------------------------------------- | ------------ | ------------------------------------------------------------ |
| `transaction.not_found`                  | 404          | Wrong reference, or the key belongs to another environment.  |
| `transaction.already_processed`          | 400          | Already success or failed. Verify instead of charging again. |
| `transaction.reference_already_consumed` | 409          | This reference already completed. Create a new initialize.   |
| `payment.invalid_amount`                 | 400          | Amount must be a numeric string such as `"100.00"`.          |
| `payment.unsupported_currency`           | 400          | Use NGN.                                                     |
| `payment.max_attempts_exceeded`          | 422          | Too many charge attempts on this session. Initialize again.  |
| `processor.unavailable`                  | 503          | Retry with backoff.                                          |

## Channel-specific

| `error`                                                              | What to do                                 |
| -------------------------------------------------------------------- | ------------------------------------------ |
| Customer email required                                              | Transfer needs `email` on initialize.      |
| `bank_transfer.failed` / `bank_transfer.virtual_account_unavailable` | Show retry.                                |
| `bank_payment.bank_required`                                         | Send `bankCode` from the banks list.       |
| `ussd.phone_required`                                                | Send `phoneNumber`.                        |
| `card.declined`                                                      | Show `message`. Let them try another card. |
| `card.session_missing`                                               | Create a new card session, then charge.    |

## After pay

A charge can return `data.status` of `fingerprint_required`, `3ds_required`, or `awaiting_transfer`. See [card](/payments/card) and [transfer](/payments/transfer).
