Guides

Errors and the Response Envelope

The unified success/data/meta/error envelope, error codes by HTTP status, and how to build one parser for every API outcome.

Last updated 2026-07-16View as Markdown

Every Coinbax API response, success or failure, arrives in the same envelope. Write one parser and it handles every endpoint and every outcome.

The envelope

{
  "success": true,
  "data": { "id": "9f8b3c2a-...", "status": "escrowed" },
  "meta": {
    "timestamp": "2026-07-16T12:34:56.789Z",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  },
  "error": null
}
Field Type Meaning
success boolean true on success, false on error
data object or null The payload; null on error
meta object Timestamp, request ID, pagination, and (on errors) path and method
error object or null Error details; null on success

List endpoints add pagination to meta:

{
  "meta": {
    "timestamp": "...",
    "requestId": "...",
    "pagination": {
      "page": 2,
      "limit": 20,
      "total": 150,
      "totalPages": 8,
      "hasNextPage": true,
      "hasPreviousPage": true
    }
  }
}

Error responses

On failure, success is false, data is null, and error carries the detail:

{
  "success": false,
  "data": null,
  "meta": {
    "timestamp": "2026-07-16T12:34:56.789Z",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "path": "/api/v1/transactions",
    "method": "POST"
  },
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "statusCode": 400,
    "details": { "errors": ["amount: must be a positive number"] }
  }
}

The error object:

Field Type Meaning
code string Stable machine-readable code; branch on this, not on message
message string Human-readable summary
statusCode number The HTTP status, mirrored into the body
details object, optional Field-level validation errors or additional context

Error codes by status

HTTP status Code When you see it
400 VALIDATION_ERROR A request field failed validation; details names the field
400 INVALID_REQUEST Malformed request body or parameters
401 UNAUTHORIZED Missing, invalid, or expired credential
403 FORBIDDEN / INSUFFICIENT_SCOPES Valid credential, but it lacks a required scope
404 NOT_FOUND The resource does not exist or belongs to another workspace
409 CONFLICT Duplicate resource or an invalid state transition (for example refunding a completed transaction)
422 UNPROCESSABLE_ENTITY The request is well-formed but fails a business rule
429 RATE_LIMIT_EXCEEDED Too many requests; honor Retry-After (see rate limits)
500 INTERNAL_SERVER_ERROR Unexpected server error
503 SERVICE_UNAVAILABLE Temporary outage; retry with backoff

500-level responses are sanitized: they never leak stack traces or internal detail in production. Everything you need for support is in meta.requestId.

One parser for every outcome

Because the shape never varies, error handling collapses to a single function:

async function coinbax(path, init) {
  const res = await fetch(`${BASE}${path}`, init);
  const body = await res.json();
  if (!body.success) {
    const err = new Error(body.error.message);
    err.code = body.error.code;
    err.status = body.error.statusCode;
    err.requestId = body.meta.requestId;
    throw err;
  }
  return body.data;
}

Branch on error.code, never on message text. Messages are for humans and may change; codes are stable.

Request IDs and support

Every response carries a meta.requestId (also returned in the X-Request-ID response header). It traces the request through the whole platform.

  • Log it alongside your own request logs on every error.
  • Send it when opening an integration to make your requests traceable end to end: pass your own ID in the X-Request-ID request header and the API will echo it back instead of generating one.
  • Quote it when contacting support. A request ID plus a timestamp lets us find the exact request, its control executions, and its outcome.

Next steps