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

# Payout Errors

> Stable error codes returned by the Payouts API

The Payouts API uses the same error envelope as the rest of the Clevis API:

```json theme={null}
{
  "error": {
    "code": "PAYOUT_NOT_FOUND",
    "message": "No payout 'po_01J...' for this client.",
    "details": {},
    "request_id": "01HX9B2KM3..."
  }
}
```

<Tip>
  Use the `code` field for programmatic branching — never the HTTP status,
  which can be shared by several codes (e.g. `409` covers two distinct error
  codes below).
</Tip>

## Error code reference

| Code                           | HTTP  | When it fires                                                                                                                   | What to do                                                                                                                          |
| ------------------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `PAYOUTS_DISABLED`             | `403` | The server has `PAYOUTS_ENABLED=false`. All `/v1/payouts*` routes return this.                                                  | Check your environment configuration; payouts are gated and may be off in some regions.                                             |
| `PAYOUT_NOT_FOUND`             | `404` | No payout with that id is visible to your client.                                                                               | The id may be wrong, or it belongs to another client. Don't retry with the same id.                                                 |
| `PAYOUT_COUNTRY_NOT_SUPPORTED` | `404` | `country` is not one of MX, CO, AR, BR, CL, PE.                                                                                 | Adding a country is a schema-only change on the server — open a support request.                                                    |
| `PAYOUT_CURRENCY_MISMATCH`     | `400` | `currency` does not match the country's payout currency (e.g. `USD` for `country: "CO"`).                                       | Send the country's native currency. See [Country requirements](/payouts/country-requirements).                                      |
| `EXTERNAL_ID_CONFLICT`         | `409` | The `external_id` is already used by this client, and no idempotency replay match was found.                                    | Use a unique `external_id`, or include the original `Idempotency-Key` header to replay.                                             |
| `IDEMPOTENCY_KEY_REUSED`       | `409` | Same `Idempotency-Key`, but the request body is different from the original.                                                    | Either fix the caller to send the original body, or pick a new key. Surface this as a bug — silent retries are *not* safe here.     |
| `PAYOUT_NOT_CANCELLABLE`       | `409` | Cancel was called on a payout that's already terminal (`PAID`, `REJECTED`, or `CANCELLED`).                                     | Read the resource and act on its current `status`.                                                                                  |
| `PAYOUT_STORE_FULL`            | `503` | The mock store hit `PAYOUTS_MAX_OPEN` (default 10,000). Mock-only failure.                                                      | Restart the API to clear the in-memory store, or raise `PAYOUTS_MAX_OPEN`. Not an issue with the real provider.                     |
| `VALIDATION_ERROR`             | `422` | Request body failed schema validation, including per-country beneficiary checks and bad CLABE/CPF/CBU/RUT/CCI **check digits**. | Read `details` for the specific field. A bad check digit is a bad **request**, not a `REJECTED` payout — fix the data, don't retry. |

## Don't confuse these

A few distinctions worth calling out:

* **`REJECTED` status vs. `422 VALIDATION_ERROR`.** A `REJECTED` payout was
  accepted by the API (the request is well-formed) and then turned away by
  the provider/bank. Bad input (e.g. malformed CLABE, missing beneficiary
  field) is rejected with `422` *before* a payout is created.
* **`EXTERNAL_ID_CONFLICT` vs. `IDEMPOTENCY_KEY_REUSED`.** Both are `409`s.
  The first means you reused an `external_id` for a *different* payout. The
  second means you reused an `Idempotency-Key` for the same logical create,
  but the body changed — usually a caller bug.
* **`PAYOUT_NOT_FOUND` vs. `PAYOUT_COUNTRY_NOT_SUPPORTED`.** Both are `404`s.
  The first means "this id doesn't exist for you"; the second means "this
  country isn't a payout destination yet".

## Handling errors in code

<CodeGroup>
  ```python Python theme={null}
  import httpx

  resp = httpx.post(
      "https://api.clevis.dev/v1/payouts",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Idempotency-Key": idempotency_key,
      },
      json=payload,
  )

  if resp.status_code in (200, 201):
      payout = resp.json()
  else:
      err = resp.json()["error"]
      match err["code"]:
          case "VALIDATION_ERROR":
              print(f"Bad request: {err['details']}")
          case "PAYOUT_CURRENCY_MISMATCH":
              print("Wrong currency for country — check requirements/{country}")
          case "PAYOUT_COUNTRY_NOT_SUPPORTED":
              print("Country not in the supported set")
          case "EXTERNAL_ID_CONFLICT":
              print("external_id already used — fetch the existing payout")
          case "IDEMPOTENCY_KEY_REUSED":
              raise RuntimeError("Idempotency-Key reused with a different body")
          case "PAYOUT_STORE_FULL":
              # mock-only — retry after the operator restarts the API
              ...
          case _:
              print(f"{err['code']}: {err['message']} (request_id={err['request_id']})")
  ```

  ```javascript Node.js theme={null}
  const resp = await fetch("https://api.clevis.dev/v1/payouts", {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Idempotency-Key": idempotencyKey,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (resp.ok) {
    const payout = await resp.json();
    // ...
  } else {
    const { error } = await resp.json();
    switch (error.code) {
      case "VALIDATION_ERROR":
        console.log("Bad request:", error.details);
        break;
      case "PAYOUT_CURRENCY_MISMATCH":
        console.log("Wrong currency for country");
        break;
      case "EXTERNAL_ID_CONFLICT":
        console.log("Already used external_id — fetch existing");
        break;
      case "IDEMPOTENCY_KEY_REUSED":
        throw new Error("Idempotency-Key reused with a different body");
      default:
        console.log(`${error.code}: ${error.message} (${error.request_id})`);
    }
  }
  ```
</CodeGroup>

## See also

* [Idempotency](/payouts/idempotency) — how `Idempotency-Key` and `external_id` deduplicate
* [Country requirements](/payouts/country-requirements) — what `VALIDATION_ERROR` catches
* [Error handling guide](/guides/error-handling) — the broader Clevis error envelope
