# 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.
> Source: https://developers.coinbax.com/docs/api/errors
> Last updated: 2026-07-16

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

```json
{
  "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`:

```json
{
  "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:

```json
{
  "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](/docs/api/scopes) |
| 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](/docs/api/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:

```javascript
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

- [Rate limits](/docs/api/rate-limits): handling 429 with backoff
- [API scopes](/docs/api/scopes): resolving 403 responses
- [Payments API reference](/reference/coinbax-api/): per-endpoint error documentation