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

# Errors

> Error envelope, HTTP status codes, and the error codes you'll actually encounter.

The Caratuva API returns errors with a consistent envelope:

```json theme={null}
{
  "statusCode": 400,
  "error": "KybNotApproved",
  "message": "Your organization has no virtual account. Complete KYB first.",
  "requestId": "b1f2c3d4-5e6a-4b7c-8d9e-0f1a2b3c4d5e"
}
```

Every error carries `statusCode`, `message`, and `requestId`. Every error code documented
below also carries a machine-readable `error`.

| Field        | Purpose                                                                                                                                                        |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `statusCode` | Mirrors the HTTP status.                                                                                                                                       |
| `error`      | Stable, machine-readable code. Switch on this. Present on every documented error code (some framework-level `404`s omit it).                                   |
| `message`    | Human-readable explanation. A string for most errors; an **array of `path: reason` strings** for `ValidationError`. Don't switch on this — wording may change. |
| `requestId`  | A UUID that correlates to our logs, also returned in the `x-request-id` response header. Include it in support requests.                                       |

## HTTP status mapping

| Status | When                                                                                                                                          |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Validation failed, business rule violated, or precondition not met.                                                                           |
| `401`  | Missing, malformed, expired, or revoked credential.                                                                                           |
| `403`  | Authenticated, but the credential lacks the required scope or role.                                                                           |
| `404`  | Resource doesn't exist or isn't visible to your organization.                                                                                 |
| `409`  | Conflict — most often `IdempotencyKeyConflict` or a race on a unique field.                                                                   |
| `429`  | Rate-limit exceeded.                                                                                                                          |
| `500`  | Unexpected server error. Safe to retry with the same `Idempotency-Key`.                                                                       |
| `503`  | Platform maintenance or an upstream rail outage. Check `status.caratuva.com`. May be returned by the edge without the standard JSON envelope. |

## Common error codes

The list below covers the codes you're most likely to encounter while integrating.

### Auth

| Code                     | Status | Cause                                                                                       |
| ------------------------ | ------ | ------------------------------------------------------------------------------------------- |
| `AuthenticationRequired` | 401    | No credential presented. Send `X-API-Key` (or, for the dashboard, `Authorization: Bearer`). |
| `InvalidApiKeyFormat`    | 401    | Header isn't `pk_(test\|live)_<id>.<secret>`.                                               |
| `InvalidApiKey`          | 401    | Key is unknown, revoked, or doesn't match the stored hash.                                  |
| `JwtExpired`             | 401    | Bearer JWT past its `exp`. Re-authenticate via the dashboard.                               |
| `InvalidJwt`             | 401    | Bearer JWT malformed, bad signature, or missing claims.                                     |
| `InsufficientScope`      | 403    | API key lacks the scope required for this endpoint.                                         |

### Validation

| Code                     | Status | Cause                                                                                   |
| ------------------------ | ------ | --------------------------------------------------------------------------------------- |
| `ValidationError`        | 400    | Request body failed schema validation. `message` is an array of `path: reason` strings. |
| `IdempotencyKeyConflict` | 409    | The `Idempotency-Key` was already used for a different resource. Use a fresh key.       |

### Lifecycle

| Code                          | Status | Cause                                                                                                                   |
| ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| `KybNotApproved`              | 400    | Your organization hasn't cleared KYB. Live operations are gated; test mode still works.                                 |
| `ProductionAccessNotApproved` | 400    | Live mode isn't enabled yet. Submit a Production Access Request and wait for Caratuva approval.                         |
| `AmountBelowMinimum`          | 400    | The USD amount is below the US\$10.00 bank-transfer funding minimum, so the buyer couldn't pay it. Increase the amount. |
| `PixPayoutKeyMissing`         | 400    | No PIX payout key registered. Configure payouts in the dashboard first.                                                 |
| `InvalidInvoiceState`         | 400    | Tried to edit or reassign an invoice past its editable state (`draft` / `awaiting_approval`).                           |
| `InvalidTransition`           | 400    | Illegal state transition — e.g. cancelling an invoice or payment intent that's already past the cancellable point.      |

A missing or invalid buyer email surfaces as `ValidationError`, not a dedicated code.
Re-POSTing the same `externalId` returns the existing intent rather than an error (see
[Payment intents](/api-reference/payment-intents)).

### Rate limiting

| Code                | Status | Cause                                                                                                           |
| ------------------- | ------ | --------------------------------------------------------------------------------------------------------------- |
| `RateLimitExceeded` | 429    | You exceeded the per-API-key limit. The JSON body includes `retryAfter` (seconds) — wait that long, then retry. |

### Server

| Code                  | Status | Cause                                                                        |
| --------------------- | ------ | ---------------------------------------------------------------------------- |
| `InternalServerError` | 500    | Unexpected server-side failure. Safe to retry with the same idempotency key. |

## Retry guidance

| Status                  | Retry? | How                                                                                                            |
| ----------------------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| 4xx (except 429)        | No     | The request will fail the same way again. Fix the input.                                                       |
| 429                     | Yes    | Wait `retryAfter` seconds (from the response body), then retry. Don't hammer.                                  |
| 500, 502, 503, 504      | Yes    | With exponential backoff, **reusing the same `Idempotency-Key`** so a retry can't create a duplicate resource. |
| Network error / timeout | Yes    | Same as 5xx. The original may or may not have committed; reusing the idempotency key keeps it safe.            |

## What to log

When an API call fails, log:

* `requestId` — lets us find your exact request in our logs (also on the `x-request-id` response header).
* `error` — the stable machine code.
* The endpoint and the request body **with secrets redacted** (never log the API key secret half or `Idempotency-Key` headers if they encode user data).

A good support escalation includes the `requestId` and the `error` code. With those two we can usually answer in minutes.
