Every Balansas Customer API response shares a predictable envelope, so you can write one parser and reuse it across every endpoint.

Success responses

A successful response always wraps the payload in a top-level data field. For a single resource, data is an object; for a collection it is an array.
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "currency": "EUR",
    "status": "active"
  }
}
data
object | array
The requested resource (object) or list of resources (array). Always present on a 2xx response.

Paginated lists

List endpoints that paginate add a meta.pagination object alongside data.
Paginated response
{
  "data": [
    { "id": "550e8400-...", "transaction_type": "payment", "status": "completed" }
  ],
  "meta": {
    "pagination": { "page": 1, "limit": 50, "total": 128 }
  }
}
meta.pagination.page
number
The page number that was returned (1-based).
meta.pagination.limit
number
The maximum number of items per page.
meta.pagination.total
number
The total number of items matching the query across all pages.
Not every list endpoint paginates. See Pagination for which endpoints return meta.pagination and which return the full array.

Error responses

When a request fails, the response replaces data with an error field — there is no data field on an error. The shape of error varies by endpoint while the contract is being migrated to a fully structured form:
{
  "error": "Account not found"
}
error
string | object
required
Either a human-readable string (most endpoints) or a structured { code, message, details? } object (webhooks, partner onboarding URLs, and newer endpoints). Always present on a non-2xx response.
error.code
string
Structured form only. Stable, machine-readable identifier (e.g. VALIDATION_ERROR, NOT_FOUND, INTERNAL_ERROR). Branch on this, not on the message text.
error.message
string
Structured form only. Human-readable description. Safe to log; do not parse.
error.details
object
Structured form only. Optional additional context (e.g. Zod-flattened field errors). May be absent.
details
array
Validation form only. Sibling of error (not nested under it). Each item is { field, message } for one validation failure.
We’re consolidating toward the structured { code, message, details? } form going forward — newer endpoints emit it today. The string and validation forms remain stable on existing endpoints until the next API version.

Handling all three shapes

Normalize at the boundary so the rest of your code branches on one shape:
Node
const res = await fetch(url, { headers });
const body = await res.json();

if (body.error !== undefined) {
  const normalized = typeof body.error === 'string'
    ? { code: undefined, message: body.error, details: body.details }
    : body.error; // already { code, message, details? }
  console.error(`[${normalized.code ?? res.status}] ${normalized.message}`);
} else {
  process(body.data, body.meta?.pagination);
}
Branch on the presence of body.error rather than HTTP status alone — both forms set a non-2xx status, but explicit body inspection is more robust to edge cases like 502s from upstream proxies.

Error handling

HTTP status mapping and a robust error-handling strategy.