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

# Error catalog

> Every reason code returned by the BlooBank API, grouped by category, with HTTP status and remediation.

This is the authoritative reference for `error.status` (and `error.details[].reason`) values returned by the BlooBank Transactions Engine API. Branch on these in your client code; they are **stable** and additive.

For the envelope structure, see [Errors overview](/get-started/errors/overview). For the full normative contract, see the [Errors reference](/api-reference/conventions/errors).

## Authentication (HTTP 400/401)

These fire before any business logic runs — fix the request signing.

| Reason                    | HTTP | What it means                                                      | What to do                                                          |
| ------------------------- | ---: | ------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `MISSING_HEADER`          |  400 | One of the four required headers is absent.                        | Send all four `X-Access-*` headers.                                 |
| `TIMESTAMP_INVALID`       |  400 | `X-Access-Timestamp` is not a decimal integer in milliseconds.     | Send Unix time in **milliseconds** (UTC).                           |
| `TIMESTAMP_SKEW_EXCEEDED` |  401 | Header timestamp is more than ±10 seconds from server time.        | Sync your host clock via NTP.                                       |
| `SIGNATURE_INVALID`       |  401 | Signature does not verify against the canonical request.           | See [Troubleshooting](/get-started/authentication/troubleshooting). |
| `REPLAY_DETECTED`         |  401 | This `(accessKey, requestId)` tuple was used within the last hour. | Generate a fresh `X-Access-Request-Id`.                             |
| `CREDENTIAL_DISABLED`     |  401 | Your Access Key is currently disabled.                             | Contact BlooBank account team.                                      |
| `CREDENTIAL_REVOKED`      |  401 | Your Access Key was permanently revoked.                           | Request a new credential.                                           |
| `CREDENTIAL_EXPIRED`      |  401 | Your Access Key passed its expiry date.                            | Rotate the credential.                                              |

## Authorization (HTTP 403)

The credential is valid but lacks permission for this action on this resource.

| Reason                     | HTTP | What it means                                           | What to do                                                                               |
| -------------------------- | ---: | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `RBAC_DENY`                |  403 | The credential lacks a required role binding.           | Request appropriate role from BlooBank. Include `decisionId` from `metadata.decisionId`. |
| `WORKSPACE_DISABLED`       |  403 | The workspace you are calling into is disabled.         | Contact BlooBank account team.                                                           |
| `SERVICE_ACCOUNT_DISABLED` |  403 | The service account behind your credential is disabled. | Contact BlooBank account team.                                                           |

## Validation (HTTP 400)

The request payload, query, or path violates input constraints.

| Reason                                  | HTTP | What it means                                                                     | What to do                                                                                   |
| --------------------------------------- | ---: | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `INVALID_ARGUMENT`                      |  400 | Generic class for request validation failures. Inspect `details[]` for specifics. | Fix the request.                                                                             |
| `INVALID_FIELD` (in `details[].reason`) |    — | A specific field in the JSON body failed validation. `metadata.field` names it.   | Fix the named field.                                                                         |
| `INVALID_ID`                            |  400 | An ID string is malformed and cannot be decoded.                                  | Use a well-formed `id` (e.g., `wal_…`, `ord_…`).                                             |
| `INVALID_FILTER`                        |  400 | The `filter` query parameter violates SFS-1 syntax or the endpoint schema.        | See [Filtering](/api-reference/conventions/filtering).                                       |
| `INVALID_ORDER_BY`                      |  400 | The `order_by` parameter references an unknown field or invalid direction.        | See [Pagination reference](/api-reference/conventions/pagination).                           |
| `INVALID_PAGE_SIZE`                     |  400 | The `page_size` is outside the accepted range.                                    | Use a value between `1` and `100`.                                                           |
| `INVALID_PAGE_TOKEN`                    |  400 | The `page_token` is malformed, expired, or context-mismatched.                    | Restart pagination from the beginning.                                                       |
| `UNSUPPORTED_FILTER_OPERATION`          |  400 | The filter uses an operator not allowed on the field's type.                      | Use `=` / `!=` for strings/booleans/enums; comparison operators only for numbers/timestamps. |

## Resource state (HTTP 404 / 409 / 422)

The request is well-formed but conflicts with the state of the target resource.

| Reason                                         | HTTP | What it means                                                        | What to do                                                             |
| ---------------------------------------------- | ---: | -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `WALLET_NOT_FOUND`                             |  404 | The named wallet does not exist or you cannot see it.                | Verify wallet name; check RBAC.                                        |
| `PAYMENT_ORDER_NOT_FOUND`                      |  404 | The named payment order does not exist within the wallet.            | Verify the `paymentOrder` id and wallet pairing.                       |
| `WALLET_ALREADY_EXISTS`                        |  409 | A wallet with the requested name already exists.                     | Pick a different name; names are immutable and globally unique.        |
| `LABEL_IMMUTABLE`                              |  422 | Attempt to change a name that is immutable after creation.           | Names cannot be changed — create a new resource if needed.             |
| `IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS` |  422 | The `(wallet, idempotencyKey)` tuple was used with a different body. | Use a new `idempotencyKey`; the first body bound to that key is final. |
| `PAYMENT_ORDER_NOT_AWAITING_APPROVAL`          |  422 | `PUT .../approve` on an order not in `AWAITING_APPROVAL`.            | Verify the order's current status before approving.                    |
| `PAYMENT_ORDER_INVALID_STATE`                  |  422 | `PUT .../cancel` on an order not in `AWAITING_APPROVAL`.             | Verify the order's current status before canceling.                    |

## Rate limiting (HTTP 429)

| Reason                                        | HTTP | What it means                                        | What to do                                                                            |
| --------------------------------------------- | ---: | ---------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `RESOURCE_EXHAUSTED`                          |  429 | Per-credential or per-endpoint rate limit exhausted. | Backoff with `Retry-After`; see [Rate limiting](/get-started/concepts/rate-limiting). |
| `RATE_LIMIT_EXCEEDED` (in `details[].reason`) |    — | Detail variant of `RESOURCE_EXHAUSTED`.              | Same — use `metadata.retry_after_seconds`.                                            |

## Server errors (HTTP 500 / 503)

| Reason                                   | HTTP | What it means                                                                          | What to do                                                       |
| ---------------------------------------- | ---: | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `INTERNAL`                               |  500 | Sanitized internal failure. `details[0]` is `ERROR_RECORDED` with `metadata.id`.       | Log `metadata.id`; forward to support. **Do not retry blindly.** |
| `ERROR_RECORDED` (in `details[].reason`) |    — | The server persisted an audit record. `metadata.id` (format `exc_…`) is the reference. | Forward `metadata.id` to support.                                |
| `PROVIDER_UNAVAILABLE`                   |  503 | Downstream payment provider is unavailable or returned a transient error.              | Retry with exponential backoff.                                  |
| `DATABASE_UNAVAILABLE`                   |  503 | Internal database unreachable.                                                         | Retry with exponential backoff.                                  |
| `UPSTREAM_UNAVAILABLE`                   |  503 | Some upstream dependency is unreachable.                                               | Retry with exponential backoff.                                  |
| `PERSISTENCE_FAILURE`                    |  500 | Unexpected persistence-layer failure. Sanitized.                                       | Forward `ERROR_RECORDED` id to support.                          |

## Authentication detail reasons

Some `details[].reason` codes are only meaningful inside the `details[]` array (not as a top-level `error.status`).

| Reason                    | Where      | What it means                                     |
| ------------------------- | ---------- | ------------------------------------------------- |
| `AUTHENTICATION_REQUIRED` | inside 401 | No credentials were provided.                     |
| `AUTHENTICATION_INVALID`  | inside 401 | Credentials were provided but did not verify.     |
| `TOKEN_EXPIRED`           | inside 401 | The session token has expired. Refresh and retry. |

## How this catalog evolves

* Reasons are **additive**: new ones may appear; existing ones do not change meaning.
* Domain reasons follow the `{AGGREGATE}_{CONDITION}` convention (e.g., `WALLET_NOT_FOUND`, `PAYMENT_ORDER_INVALID_STATE`).
* Cross-cutting reasons follow `{CONDITION}` (e.g., `INVALID_FIELD`, `INTERNAL`, `RESOURCE_EXHAUSTED`).
* Treat any reason **not** in this catalog as opaque — fall back to handling by `error.status` alone.

## Next

<CardGroup cols={2}>
  <Card title="Handling errors" icon="rotate" href="/get-started/errors/handling">
    Branching patterns in code.
  </Card>

  <Card title="Retry strategy" icon="rotate-right" href="/get-started/errors/retry-strategy">
    When to retry and how.
  </Card>
</CardGroup>
