Skip to content

Response Envelope

Every response from the Chainabit API follows a consistent envelope format. This makes it straightforward to handle responses generically in your client code, regardless of which endpoint you are calling.

Envelope Structure

typescript
interface ApiResponseEnvelope<T> {
  data?: T;
  meta?: ApiMeta;
  error?: ApiError;
}

A successful response always includes data and typically includes meta. An error response includes error and omits data.

Meta Object

typescript
interface ApiMeta {
  requestId: string;    // Unique identifier for this request (useful for support)
  durationMs: number;   // Server-side processing time in milliseconds
  limit?: number;       // Page size (list endpoints only)
  offset?: number;      // Current offset (offset-paginated endpoints only)
  total?: number;       // Total record count (offset-paginated endpoints only)
  hasNextPage?: boolean; // Whether more records exist
  nextCursor?: string;  // Cursor for next page (cursor-paginated endpoints only)
}

Error Object

typescript
interface ApiError {
  code: string;     // Machine-readable error code (e.g., "VALIDATION_ERROR")
  message: string;  // Human-readable description
  details?: any;    // Additional context (field-level validation errors, etc.)
}

Examples

Single Record Success

GET /api/v1/ai/sessions/sess-abc-123 returns 200 OK:

json
{
  "data": {
    "id": "sess-abc-123",
    "title": "Research Assistant",
    "status": "active",
    "createdAt": "2026-01-15T08:00:00.000Z"
  },
  "meta": {
    "requestId": "req_7f3a9b2c",
    "durationMs": 12
  }
}

Paginated List Success (Offset-based)

GET /api/v1/ai/sessions?limit=2&offset=0 returns 200 OK:

json
{
  "data": [
    {
      "id": "sess-abc-123",
      "title": "Research Assistant",
      "status": "active"
    },
    {
      "id": "sess-def-456",
      "title": "Developer Pipeline",
      "status": "active"
    }
  ],
  "meta": {
    "requestId": "req_8e4b0c3d",
    "durationMs": 18,
    "limit": 2,
    "offset": 0,
    "total": 12,
    "hasNextPage": true
  }
}

Cursor-Paginated List Success

GET /api/v1/billing/invoices?limit=2 returns 200 OK:

json
{
  "data": [
    {
      "id": "inv_001",
      "amount": 1999,
      "currency": "usd",
      "status": "paid",
      "createdAt": "2026-03-01T00:00:00.000Z"
    },
    {
      "id": "inv_002",
      "amount": 1999,
      "currency": "usd",
      "status": "paid",
      "createdAt": "2026-02-01T00:00:00.000Z"
    }
  ],
  "meta": {
    "requestId": "req_9f5c1d4e",
    "durationMs": 22,
    "limit": 2,
    "hasNextPage": true,
    "nextCursor": "eyJpZCI6Imludl8wMDIifQ=="
  }
}

To fetch the next page, include the cursor: GET /api/v1/billing/invoices?limit=2&cursor=eyJpZCI6Imludl8wMDIifQ==

Validation Error (400)

POST /api/v1/ai/sessions with invalid body returns 400 Bad Request:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "name",
        "message": "name must be a string"
      },
      {
        "field": "cadence",
        "message": "cadence must be one of: daily, weekly, custom"
      }
    ]
  },
  "meta": {
    "requestId": "req_1a2b3c4d",
    "durationMs": 3
  }
}

Rate Limit Error (429)

Any endpoint when the rate limit is exceeded returns 429 Too Many Requests:

json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please retry after the indicated time.",
    "details": {
      "retryAfter": 45
    }
  },
  "meta": {
    "requestId": "req_5e6f7g8h",
    "durationMs": 1
  }
}

The retryAfter value is in seconds. You should also check the Retry-After HTTP header.

Built with purpose.