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
| Code | Name | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request body or parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Valid key but insufficient permissions |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Resource already exists or state conflict |
| 422 | Validation Error | Request data failed validation rules |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | Server-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.
| Scope | Methods | Default limit |
|---|---|---|
| Write operations | POST, PUT, PATCH, DELETE | 60 requests/min |
| Read operations | GET | 120 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 paginationoffset- 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.