> ## 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.

# Errors overview

> How the BlooBank API signals failures — canonical envelope, branching rules, and what to log.

Every BlooBank error response uses the **same JSON envelope**, regardless of which endpoint produced it. Learn this shape once and you have a single uniform error-handling code path for the entire API.

For the full normative contract, see the [Errors reference](/api-reference/conventions/errors). For implementation patterns, see [Handling errors](/get-started/errors/handling).

## Core principles

* **Uniform envelope.** Every error response uses the structure described below. No endpoint deviates.
* **HTTP status is informational.** Use `error.code` for coarse routing (`< 500` vs `>= 500`). It is not granular enough for business logic.
* **`error.status` is the contract.** This is the stable, machine-readable identifier you branch on. It does not change between releases.
* **`error.message` is human-facing.** Wording may change. **Never string-match `error.message`** in code.
* **`error.details[]` carries machine context.** When you need to react programmatically to a specific condition, read `details[].reason` and `details[].metadata` — never parse `message`.

## The envelope

```json theme={"dark"}
{
  "error": {
    "code": 404,
    "status": "WALLET_NOT_FOUND",
    "message": "Wallet not found.",
    "details": [
      {
        "reason": "WALLET_NOT_FOUND",
        "description": "No wallet exists with name `production-main`.",
        "metadata": {}
      }
    ]
  }
}
```

The envelope shape is **invariant**:

* `error` is always present.
* `error.code`, `error.status`, `error.message`, and `error.details` are always present.
* `error.details` is always an array. It may be empty (`[]`), never `null`, never missing.
* The `Content-Type` is always `application/json; charset=utf-8`.

| Field                         | Stability           | What to do with it                                                      |
| ----------------------------- | ------------------- | ----------------------------------------------------------------------- |
| `error.code`                  | Stable per `status` | Use for coarse routing only.                                            |
| `error.status`                | **Stable**          | Branch on this.                                                         |
| `error.message`               | **Unstable**        | Log it; do not branch on it.                                            |
| `error.details[]`             | Stable per `reason` | Inspect for specific causes.                                            |
| `error.details[].reason`      | **Stable**          | Branch on this for specific conditions.                                 |
| `error.details[].description` | **Unstable**        | Log it; suitable for direct UI display.                                 |
| `error.details[].metadata`    | Stable per `reason` | Read for context (e.g., `field`, `param`, `decisionId`, `excRecordId`). |

## How to branch

The decision tree:

<Steps>
  <Step title="Coarse routing on error.code">
    `2xx` → success. `4xx` → caller error (do not retry without changing the request). `5xx` → server error (may be retryable with backoff).
  </Step>

  <Step title="Branch on error.status">
    The canonical reason — e.g., `SIGNATURE_INVALID`, `WALLET_NOT_FOUND`, `IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS`, `RESOURCE_EXHAUSTED`. The full catalog is in [Error catalog](/get-started/errors/error-catalog).
  </Step>

  <Step title="Inspect error.details[].reason for specifics">
    For validation failures (`INVALID_ARGUMENT`), `details[]` carries one entry per failed field. For sanitized 5xx (`INTERNAL`), `details[]` carries the `ERROR_RECORDED` reference id.
  </Step>

  <Step title="Capture metadata for support">
    On any failure you escalate, log `metadata.decisionId` (DENY decisions) and `metadata.id` from `ERROR_RECORDED` (5xx). These are the handles support uses to trace the failure server-side.
  </Step>
</Steps>

## A worked example

A `POST /wallets/production-main/paymentOrders` with a missing field:

```json theme={"dark"}
{
  "error": {
    "code": 400,
    "status": "INVALID_ARGUMENT",
    "message": "One or more fields have invalid values.",
    "details": [
      {
        "reason": "INVALID_FIELD",
        "description": "The field \"amount\" is required.",
        "metadata": { "field": "amount" }
      },
      {
        "reason": "INVALID_FIELD",
        "description": "The field \"network\" must be one of: br.gov.bcb.pix.",
        "metadata": { "field": "network" }
      }
    ]
  }
}
```

Your client should:

* Branch on `error.status === 'INVALID_ARGUMENT'`.
* Iterate `details[]` — there is one entry per invalid field.
* For each, read `metadata.field` to locate the form input; read `description` for the user-facing message.
* Do **not** retry — fix the request and let the user resubmit.

## Two patterns to remember

### Pattern 1 — simple failure (single cause)

`error.status` and `error.details[0].reason` carry the **same value**.

```json theme={"dark"}
{
  "error": {
    "code": 404,
    "status": "WALLET_NOT_FOUND",
    "details": [{ "reason": "WALLET_NOT_FOUND", "description": "...", "metadata": {} }]
  }
}
```

### Pattern 2 — multi-cause validation

`error.status` is the cross-cutting class (`INVALID_ARGUMENT`); `details[]` carries one entry per cause.

```json theme={"dark"}
{
  "error": {
    "code": 400,
    "status": "INVALID_ARGUMENT",
    "details": [
      { "reason": "INVALID_FIELD", "description": "...", "metadata": { "field": "amount" } },
      { "reason": "INVALID_FIELD", "description": "...", "metadata": { "field": "network" } }
    ]
  }
}
```

Always defensively check `details.length` before indexing — both patterns are valid; some failures legitimately return empty `details`.

## INTERNAL errors are sanitized

When the API returns `HTTP 500` with `error.status: "INTERNAL"`, the response is **deliberately stripped of detail** to avoid leaking implementation specifics. What you get:

* `error.message` is a generic phrase like `"An internal error has occurred."`
* `details[]` carries a single `ERROR_RECORDED` entry with `metadata.id` (format `exc_…`).

**The `metadata.id` is gold** — it lets BlooBank engineers locate the exact failure server-side. Always log it on `INTERNAL` and include it when contacting support.

See [Errors reference §Sanitized 500s](/api-reference/conventions/errors#sanitized-500s) for details.

## What to log

For every non-2xx response, log:

* HTTP method and URL.
* `error.status` and `error.code`.
* Every `details[].reason`.
* `metadata.decisionId` (DENY decisions) and `metadata.id` (`ERROR_RECORDED`).
* Approximate UTC time.
* Your `X-Access-Request-Id`.

Do **not** log credentials, the private key, raw signatures, or full request bodies containing user PII.

## Next

<CardGroup cols={3}>
  <Card title="Error catalog" icon="list" href="/get-started/errors/error-catalog">
    Every reason code returned by the API, with HTTP status and remediation.
  </Card>

  <Card title="Handling errors" icon="rotate" href="/get-started/errors/handling">
    Branching patterns in code, language by language.
  </Card>

  <Card title="Retry strategy" icon="rotate-right" href="/get-started/errors/retry-strategy">
    Exponential backoff with jitter — when to retry, when to give up.
  </Card>
</CardGroup>
