Errors
SpeyBooks uses conventional HTTP status codes and returns structured, machine-readable error information in JSON.
Error responses are stable and designed to be handled programmatically.
HTTP status codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
204 | No content (successful deletion) |
400 | Bad request — invalid parameters |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — valid key, insufficient permissions |
404 | Not found — resource does not exist |
409 | Conflict — resource already exists |
422 | Unprocessable entity — validation failed |
429 | Too many requests — rate limit exceeded |
500 | Server error — internal failure |
HTTP status codes indicate request outcome, not business meaning. Use the error payload for precise handling.
Error response format
All errors follow the same structure.
{
"success": false,
"error": {
"code": "validation_error",
"message": "The 'email' field is required",
"field": "email"
}
}
Error fields
| Field | Description |
|---|---|
code | Stable, machine-readable error code |
message | Human-readable description |
field | Field that caused the error (when applicable) |
The field property is included only for validation errors.
Error codes
Authentication errors
| Code | Description |
|---|---|
unauthorized | Missing or invalid API key |
forbidden | API key valid but lacks permission |
Validation errors
| Code | Description |
|---|---|
validation_error | Request data failed validation |
required_field | Required field is missing |
invalid_format | Field has an invalid format |
invalid_value | Field value is not permitted |
invalid_id | Malformed resource ID |
Validation errors always return HTTP 422.
Resource errors
| Code | Description |
|---|---|
not_found | Resource does not exist |
already_exists | Resource with the same identifier exists |
conflict | Resource state prevents the operation |
unprocessable_entity | Business rule prevents the operation |
Rate limiting
| Code | Description |
|---|---|
rate_limit_exceeded | Too many requests |
When rate limited, the response includes a Retry-After header.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"success": false,
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 30 seconds."
}
}
Clients should respect the Retry-After value before retrying.
Server errors
| Code | Description |
|---|---|
internal_error | Internal server error |
service_unavailable | Temporary outage |
Server errors indicate failures on the SpeyBooks side. They are safe to retry with appropriate backoff.
Handling errors
Clients should handle errors by inspecting both the HTTP status code and the error payload.
Example (JavaScript)
try {
const response = await fetch('https://api.speybooks.com/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(invoiceData)
});
const result = await response.json();
if (!result.success) {
const { error } = result;
switch (error.code) {
case 'validation_error':
console.error(
`Validation failed: ${error.message}` +
(error.field ? ` (${error.field})` : '')
);
break;
case 'unauthorized':
console.error('Invalid or missing API key');
break;
case 'rate_limit_exceeded':
const retryAfter = response.headers.get('Retry-After');
console.error(`Rate limited. Retry after ${retryAfter}s`);
break;
default:
console.error(`API error: ${error.message}`);
}
}
} catch (err) {
console.error('Network error:', err);
}
Key principles
- Errors are explicit and structured
- Error codes are stable and machine-readable
- Validation failures are precise and field-level
- Rate limits communicate retry timing
- Server errors do not leak internal details
Error handling in SpeyBooks is designed to be predictable, composable, and safe to automate.
What to read next
- Pagination — page-based iteration
- Transactions API — validation and invariant enforcement
- API Overview — request and response conventions