The SpeyBooks API uses conventional HTTP status codes to indicate the outcome of a request. Codes in the 2xx range indicate success, codes in the 4xx range indicate a problem with the request, and codes in the 5xx range indicate an issue on our end.
Error response format
Every error response follows a consistent envelope. The code field is a machine-readable string you can match against in your code. The message field is a human-readable explanation suitable for logging — it should not be displayed directly to end users.
HTTP status codes
| Status | Meaning |
|---|---|
200 | Success — the request completed normally |
201 | Created — a new resource was created |
204 | No Content — the request succeeded with no response body |
400 | Bad Request — the request was malformed or contained invalid parameters |
401 | Unauthorized — no valid authentication credentials were provided |
403 | Forbidden — the credentials are valid but lack permission for this action |
404 | Not Found — the requested resource does not exist |
409 | Conflict — the request conflicts with the current state of a resource |
422 | Unprocessable Entity — the request was well-formed but failed business logic validation |
429 | Too Many Requests — you have exceeded the rate limit |
500 | Internal Server Error — something went wrong on our end |
Error codes
| Code | Status | Description |
|---|---|---|
validation_error | 400 | One or more fields failed validation. Check the message for details. |
invalid_id | 400 | The ID does not match the expected format. SpeyBooks uses prefixed IDs — for example, inv_42 for invoices, cont_3 for contacts. |
not_found | 404 | The resource does not exist, or it belongs to a different organisation. |
unauthorized | 401 | The request lacks valid authentication. Either the session has expired, the API key is missing, or the token is invalid. |
forbidden | 403 | Authentication succeeded but the credentials lack the required scope. |
insufficient_scope | 403 | The API key does not have the required scope for this endpoint. The message field indicates which scope is needed. |
conflict | 409 | The action conflicts with the current state of the resource — for example, deleting a finalised invoice. |
unprocessable_entity | 422 | The request is syntactically valid but violates a business rule — for example, declaring a dividend that exceeds retained profits. |
rate_limited | 429 | Too many requests. Back off and retry after the period indicated in the Retry-After header. |
internal_error | 500 | An unexpected error on our side. If this persists, contact support with the request timestamp and endpoint. |
Idempotency errors
When using the Idempotency-Key header, two additional scenarios can occur. If you reuse a key with different request parameters, the API returns a 409 Conflict. If the original request failed with a 5xx error, the key is released and can be retried. Keys expire after 24 hours.
Handling errors
For robust integration, your client should:
- Always check the
successfield before accessingdata. - Match on the
error.codestring for programmatic handling, not the HTTP status code alone — multiple error codes can share the same status. - Log the full error response including
codeandmessagefor debugging. - Implement exponential backoff for
429and5xxresponses. - Never display
error.messagedirectly to end users — it is intended for developers and may change between releases.