Opbox

Errors, Limits & Conventions

Opbox uses standard HTTP status codes and a consistent error response format. This page covers error handling, rate limits, and the conventions you can rely on when building against the API.

Error Response Format

Newly migrated routes use a standardised error envelope:

{
  "success": false,
  "error": "Human-readable error message",
  "code": "MACHINE_CODE",
  "details": {}
}

Validation errors include field-level details:

{
  "success": false,
  "error": "Invalid request body",
  "code": "VALIDATION_ERROR",
  "details": {
    "fieldErrors": { "title": ["Required"] }
  }
}

Some legacy routes still return { "error": "..." } while migration is in progress.

HTTP Status Codes

CodeNameDescription
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid request body or parameters
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient permissions
404Not FoundResource does not exist
409ConflictResource already exists or state conflict
422Validation ErrorRequest data failed validation rules
429Too Many RequestsRate limit exceeded
500Internal ErrorServer-side error (please report)

Common Error Scenarios

Missing Authorization Header

// Request without auth header
GET /api/forms

// Response: 401
{ "success": false, "error": "Unauthorized", "code": "UNAUTHORIZED" }

Ensure you include Authorization: Bearer sk_... in every request.

Resource Not Found

// Request with invalid or non-owned ID value
GET /api/forms/:id

// Response: 404
{ "success": false, "error": "Form not found", "code": "NOT_FOUND" }

Verify the resource ID is a valid CUID and belongs to your workspace.

Validation Failure

// Request with missing required field
POST /api/forms
{ "description": "No title provided" }

// Response: 400
{
  "success": false,
  "error": "Invalid request body",
  "code": "VALIDATION_ERROR",
  "details": {
    "fieldErrors": { "title": ["Required"] }
  }
}

Check the details.fieldErrors payload for field-level validation issues.

Rate Limits

The API enforces rate limits to protect service quality. Limits apply per IP address.

ScopeMethodsDefault limit
Write operationsPOST, PUT, PATCH, DELETE60 requests/min
Read operationsGET120 requests/min

On top of the global write cap, individual endpoints may enforce stricter per-endpoint limits (for example authentication, file uploads, and signing routes). When an endpoint is rate-limited, the response is:

HTTP 429 Too Many Requests

{
  "success": false,
  "error": "Too many requests",
  "code": "RATE_LIMITED"
}

When you see a 429, back off before retrying. Implement exponential backoff in your client for best results.

Pagination

List endpoints accept standard pagination query parameters:

  • limit - maximum number of items to return (typically capped at 100)
  • cursor - opaque cursor returned by a previous response, used for cursor-based pagination
  • offset - zero-based offset, used by older endpoints that still rely on offset pagination

Responses include a pagination block when more results are available:

{
  "success": true,
  "items": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6ImNtMDAwMSJ9",
    "hasMore": true
  }
}

If nextCursor (or hasMore: true) is present, request the next page by passing the cursor back in. When no cursor is returned, you have reached the end of the list.

Idempotency

POST endpoints that create resources accept an optional Idempotency-Key header. Reuse the same key to safely retry a request - if the original call succeeded, the server returns the original response instead of creating a duplicate.

curl -X POST https://your-domain.com/api/matters \
  -H "Authorization: Bearer sk_..." \
  -H "Idempotency-Key: 7f3c6b1e-4d8a-4f1a-9c3b-2e1f5a7b8c9d" \
  -H "Content-Type: application/json" \
  -d '{ "title": "New matter" }'

Keys are stored for 24 hours. After that window the key is forgotten and a replay will create a new resource. Use a fresh UUID per logical operation; reusing the same key across different payloads is an error.

Request IDs

Every response includes an X-Request-Id header. If you need to contact support, include this value in your ticket - it lets us trace your exact request in the server logs.

HTTP/1.1 200 OK
X-Request-Id: 01J9KXZ4T8R7A3VN0W1Q2B5YE6
Content-Type: application/json

{ "success": true, ... }

Client libraries should surface X-Request-Id in error messages and logs so it is available when debugging production issues.