Every BlooBank error response uses the same JSON envelope, regardless of which endpoint produced it. This page is the reference contract — the developer-facing guide on how to react to errors lives in Errors → Handling.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.
Branch on
error.status and error.details[].reason. Never string-match error.message — its wording changes between releases.The envelope
Every error response is a JSON object with exactly one top-level key:error.
erroris always present.error.code,error.status,error.message, anderror.detailsare always present.error.detailsis always an array. It may be empty ([]), but it is nevernulland never missing.- The response
Content-Typeis alwaysapplication/json; charset=utf-8.
Field reference
| Field | Type | Stability | Description |
|---|---|---|---|
error.code | integer | Stable per status | HTTP status code mirrored from the response status line. |
error.status | string | Stable | Canonical reason identifier in UPPER_SNAKE_CASE. Safe to branch on programmatically. |
error.message | string | Unstable | Human-readable summary aimed at developers. Do not string-match. |
error.details[] | array | Stable per reason | Structured diagnostics. May be empty. |
error.details[].reason | string | Stable | Machine-readable identifier in UPPER_SNAKE_CASE. Safe to branch on. |
error.details[].description | string | Unstable | Human-readable explanation of this specific detail. |
error.details[].metadata | object | Stable per reason | Key/value context. Schema depends on the reason. |
error.status vs error.details[].reason
Both carry stable UPPER_SNAKE_CASE identifiers. The practical rule:
- For simple failures with a single cause (most cases),
error.statusanderror.details[0].reasoncarry the same value (e.g., bothWALLET_NOT_FOUND). - For validation failures with multiple field issues,
error.statusis the cross-cutting class (INVALID_ARGUMENT) anderror.details[]carries one entry per invalid field.
error.status is the coarse classifier; details[].reason is the specific cause.
Status to HTTP mapping
The mapping is fixed.error.code | Typical error.status values | Meaning |
|---|---|---|
400 | INVALID_ARGUMENT, MISSING_HEADER, TIMESTAMP_INVALID | Request contract violation. |
401 | SIGNATURE_INVALID, TIMESTAMP_SKEW_EXCEEDED, REPLAY_DETECTED, CREDENTIAL_DISABLED, CREDENTIAL_REVOKED, CREDENTIAL_EXPIRED | Authentication failure. |
403 | RBAC_DENY, WORKSPACE_DISABLED, SERVICE_ACCOUNT_DISABLED | Authenticated, but not authorized. |
404 | WALLET_NOT_FOUND, PAYMENT_ORDER_NOT_FOUND | Resource does not exist. |
409 | WALLET_ALREADY_EXISTS | Conflict with current state. |
422 | IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS, LABEL_IMMUTABLE, PAYMENT_ORDER_NOT_AWAITING_APPROVAL, PAYMENT_ORDER_INVALID_STATE | Well-formed request, but a business precondition failed. |
500 | INTERNAL | Server-side failure. Sanitized — see §Sanitized 500s. |
503 | PROVIDER_UNAVAILABLE | Downstream dependency temporarily unavailable. Retry with backoff. |
Validation errors (INVALID_ARGUMENT)
The most common error during integration is request validation. The platform formats these consistently across every endpoint.
error.statusis"INVALID_ARGUMENT".error.codeis400.error.messageis a generic summary, e.g."One or more fields have invalid values.".error.details[]contains one entry per failed constraint.
field vs param
The platform distinguishes body fields from query/path parameters.
| Metadata key | Source | Example |
|---|---|---|
metadata.field | A field inside the JSON request body. | { "field": "name" } |
metadata.param | A query-string or path parameter. | { "param": "page_size" } |
Example — multiple invalid fields
Example — invalid query parameter
reason than INVALID_FIELD (e.g., INVALID_PAGE_SIZE, INVALID_FILTER, INVALID_ORDER_BY, INVALID_PAGE_TOKEN).
Sanitized 500s
Responses witherror.status: "INTERNAL" receive special treatment.
What sanitization means
When a service produces an internal error, the platform mutates the response before sending it:- The original
error.messageis replaced with a generic phrase such as"An internal error has occurred.". - Any original
details[]entries are dropped. - A single
details[]entry withreason: "ERROR_RECORDED"is appended (when the audit record was persisted successfully).
Anatomy of a recorded internal error
metadata.id (format exc_…) is the single most valuable piece of information you can forward to support. With it, a BlooBank engineer can locate the full audit trail in seconds.
What to do on INTERNAL
- Locate the detail with
reason === "ERROR_RECORDED". - Extract
metadata.id. - Log it with the URL, method, approximate UTC time, and your
X-Access-Request-Id. - Forward it when escalating.
ERROR_RECORDED is missing (rare — the audit-record persistence itself failed), send the approximate UTC time and your request id; engineers can still trace via access logs.
What is not in the envelope
Do not depend on these — they may appear or disappear between releases:- Top-level fields outside
error(timestamp,path,traceIdat the root). Treat as informational. - Nested fields inside
errorother than the four documented above. - Stack traces. Never returned, regardless of failure mode.
Next
Error catalog
Every reason code returned by the API, with remediation.
Handling errors
Branching patterns and retry strategy in code.