Skip to main content

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.

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. For implementation patterns, see Handling errors.

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

{
  "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.
FieldStabilityWhat to do with it
error.codeStable per statusUse for coarse routing only.
error.statusStableBranch on this.
error.messageUnstableLog it; do not branch on it.
error.details[]Stable per reasonInspect for specific causes.
error.details[].reasonStableBranch on this for specific conditions.
error.details[].descriptionUnstableLog it; suitable for direct UI display.
error.details[].metadataStable per reasonRead for context (e.g., field, param, decisionId, excRecordId).

How to branch

The decision tree:
1

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).
2

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

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

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.

A worked example

A POST /wallets/production-main/paymentOrders with a missing field:
{
  "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.
{
  "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.
{
  "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 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

Error catalog

Every reason code returned by the API, with HTTP status and remediation.

Handling errors

Branching patterns in code, language by language.

Retry strategy

Exponential backoff with jitter — when to retry, when to give up.