# Amounts, idempotency and errors (/docs/conventions)

## Amounts [#amounts]

Amounts are **integer minor units sent as strings**: kobo for NGN. `"250000"` is ₦2,500.00. Strings avoid floating-point rounding; never send `2500.00`.

## Idempotency [#idempotency]

Every write (`POST`, `PUT`, `PATCH`, `DELETE`) needs an `x-idempotency-key` header: up to 128 letters, digits, `.`, `_`, `:` or `-`. Use one key per operation, for example `payout-881` or the order id.

- Retrying with the **same key and the same body** within 24 hours returns the original response; nothing is charged or paid twice.
- The **same key with a different body** returns `409`.
- After a timeout or a network error, **retry with the same key**. Never invent a new key for an operation that may already have happened.

Separately, your own `reference` values must be unique per environment. Reusing one returns `409`.

## Uncertain outcomes [#uncertain-outcomes]

Collection and payout responses include `requiresReconciliation`. When it is `true`, the provider has not confirmed the result yet. Do not retry: look the transaction up with `GET /transactions/{id}` or wait for the webhook.

## Errors [#errors]

Errors use standard HTTP status codes and a JSON body:

```json
{
  "statusCode": 409,
  "path": "/api/v1/payouts",
  "timestamp": "2026-11-02T10:15:00.000Z",
  "error": {
    "statusCode": 409,
    "error": "Conflict",
    "message": "Reference already exists. Query the original operation before retrying"
  }
}
```

Show or log `error.message`. For validation errors (`400`) it can be a list of messages, one per invalid field.

| Status | Meaning |
| --- | --- |
| `400` | The request is invalid; `error` says which field |
| `401` | Missing or invalid key, secret, signature, timestamp or idempotency key, or an IP not on your allow-list |
| `403` | The key lacks a permission, or live mode is not enabled for your business yet |
| `404` | Not found in this key's environment |
| `409` | Duplicate reference, or an idempotency key reused with a different body |
| `429` | Too many requests |
| `503` | A payment provider is unavailable; nothing was charged |
