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

# Errors

> Status codes and error handling.

Errors return a non-2xx HTTP status and an envelope with `success: false`:

```json theme={null}
{
  "success": false,
  "message": "Insufficient permissions. Required scope: pay",
  "request_id": "b2f1c3a0-...",
  "required_scopes": ["pay"]
}
```

Always branch on the HTTP status code and surface `message` for debugging. Log
`request_id` — quote it when contacting support.

## Status codes

| Code  | Meaning           | Common causes                                                           |
| ----- | ----------------- | ----------------------------------------------------------------------- |
| `200` | OK                | Successful read or synchronous write                                    |
| `201` | Created           | Resource created (payout, beneficiary, webhook, …)                      |
| `202` | Accepted          | Async work started — **not yet settled** (`/pay`, batch `/process`)     |
| `400` | Bad Request       | Missing/invalid fields, provider rejection, insufficient balance        |
| `401` | Unauthorized      | Missing, invalid, or expired token / API key                            |
| `403` | Forbidden         | Token lacks the required scope, inactive account, or IP not whitelisted |
| `404` | Not Found         | Resource id doesn't exist or isn't yours                                |
| `409` | Conflict          | Duplicate `reference`, or slug already taken                            |
| `429` | Too Many Requests | Rate limit hit (e.g. `/auth`: 20 per 15 min)                            |
| `500` | Server Error      | Unexpected error — retry with backoff, then contact support             |

## Idempotency and duplicates

`/pay` and `/deposit` accept a `reference`. If you resend a request with a `reference`
that already exists, you get `409` with the existing transaction:

```json theme={null}
{
  "success": false,
  "message": "Transaction with this reference already exists",
  "existing_transaction": { "id": 88213, "status": "pending" }
}
```

Always set a stable, unique `reference` per logical transaction so safe retries don't
create duplicate charges.

## Handling pending payments

A `202` from `/pay` is **not** a completed payment. Poll
`GET /merchant/api/transactions/{id}` or wait for the
[`payment_received` webhook](/webhooks). Transaction `status` values you may see:

| Status       | Meaning                                     |
| ------------ | ------------------------------------------- |
| `pending`    | Prompt sent; awaiting customer confirmation |
| `processing` | Being settled                               |
| `success`    | Completed and credited                      |
| `failed`     | Rejected or could not complete              |
| `cancelled`  | Cancelled before completion                 |
| `reversed`   | Completed then reversed                     |
| `expired`    | Customer did not confirm in time            |
