Error Handling
OBAPI uses conventional HTTP response codes and a structured JSON error body.
HTTP status codes
| Code |
Meaning |
When |
200 |
OK |
Request succeeded |
400 |
Bad Request |
Invalid parameters (e.g. malformed date, unknown filter value) |
401 |
Unauthorized |
Missing or invalid Bearer token |
403 |
Forbidden |
Token valid but insufficient permissions for this resource |
404 |
Not Found |
Object does not exist or endpoint not implemented |
405 |
Method Not Allowed |
Using POST/PUT/DELETE on a read-only API |
422 |
Unprocessable Entity |
Parameters are syntactically valid but semantically wrong |
429 |
Too Many Requests |
Rate limit exceeded (if provider implements rate limiting) |
500 |
Internal Server Error |
Unexpected server-side failure |
503 |
Service Unavailable |
Server temporarily down for maintenance |
Error response format
All errors return a JSON body with this structure:
{
"error": {
"type": "string -- error category",
"code": "string -- machine-readable error code",
"message": "string -- human-readable description"
}
}
Error types
| Type |
HTTP Code |
Description |
authentication_error |
401 |
Token missing, invalid, or expired |
authorization_error |
403 |
Token valid but access denied |
not_found |
404 |
Resource does not exist |
invalid_request |
400, 422 |
Request parameters are invalid |
method_not_allowed |
405 |
HTTP method not supported |
rate_limit |
429 |
Too many requests |
server_error |
500 |
Internal failure |
service_unavailable |
503 |
Temporary downtime |
Error codes reference
Authentication (401)
| Code |
Message |
Cause |
MISSING_TOKEN |
Authorization header is required |
No Authorization: Bearer header sent |
INVALID_TOKEN |
The provided Bearer token is invalid or expired |
Token does not match any active API key |
EXPIRED_TOKEN |
The provided Bearer token has expired |
Token was valid but is past its expiry |
Authorization (403)
| Code |
Message |
Cause |
INSUFFICIENT_PERMISSIONS |
Your token does not have access to this resource |
API key exists but lacks the required scope |
Not Found (404)
| Code |
Message |
Cause |
OBJECT_NOT_FOUND |
Requested object does not exist |
The {id} does not match any record |
ENDPOINT_NOT_FOUND |
This endpoint is not available |
Server does not implement this endpoint |
DOCUMENT_NOT_FOUND |
No document available for this object |
The object exists but has no downloadable file |
Invalid Request (400, 422)
| Code |
Message |
Cause |
INVALID_PARAMETER |
Parameter 'X' is invalid |
A query parameter has an unexpected value |
INVALID_DATE_FORMAT |
Date must be in YYYY-MM-DD format |
A date parameter is malformed |
INVALID_DATE_RANGE |
date_start must be before date_end |
Date range is inverted |
PAGE_OUT_OF_RANGE |
Page number exceeds available pages |
Requested page > total_pages |
PER_PAGE_TOO_LARGE |
per_page cannot exceed 200 |
Requested more than 200 items per page |
INVALID_STATUS_FILTER |
Unknown status value 'X' |
Status filter does not match allowed values |
Rate Limit (429)
| Code |
Message |
Cause |
RATE_LIMIT_EXCEEDED |
Too many requests, retry after X seconds |
Provider-defined rate limit hit |
When a 429 is returned, the response includes a Retry-After header (in seconds).
Server Errors (500, 503)
| Code |
Message |
Cause |
INTERNAL_ERROR |
An unexpected error occurred |
Bug or misconfiguration on the server |
SERVICE_UNAVAILABLE |
Service temporarily unavailable |
Planned maintenance or overload |
Examples
Missing authentication
GET /obapi/v1/invoices
(no Authorization header)
HTTP/1.1 401 Unauthorized
{
"error": {
"type": "authentication_error",
"code": "MISSING_TOKEN",
"message": "Authorization header is required"
}
}
Object not found
GET /obapi/v1/invoices/99999
Authorization: Bearer valid-token
HTTP/1.1 404 Not Found
{
"error": {
"type": "not_found",
"code": "OBJECT_NOT_FOUND",
"message": "Requested object does not exist"
}
}
Invalid date range
GET /obapi/v1/invoices?date_start=2024-12-31&date_end=2024-01-01
Authorization: Bearer valid-token
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"type": "invalid_request",
"code": "INVALID_DATE_RANGE",
"message": "date_start must be before date_end"
}
}
Empty results (not an error)
A query that returns no matching items is not an error. It returns 200 with an empty array:
{
"items": [],
"pagination": {
"page": 1,
"per_page": 50,
"total_items": 0,
"total_pages": 0
}
}
Best practices
- Always check the HTTP status code first before parsing the body.
- Log the full error response (
type, code, message) for debugging.
- Handle 429 gracefully by respecting the
Retry-After header.
- Do not retry on 4xx errors (except 429) -- the request is invalid and will fail again.
- Retry on 5xx errors with exponential backoff (1s, 2s, 4s, max 3 retries).
- Use Discovery to check which endpoints are available before calling them.