Error Handling
HTTP status codes, common errors, and a battle-tested retry pattern
The Servicebay API uses standard HTTP status codes and returns a consistent JSON envelope on every error. Your client only needs one parsing path.
Error envelope
{
"success": false,
"error": "Description of what went wrong"
}HTTP status codes
| Status | Meaning | What it means in practice |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
400 | Bad Request | Invalid parameters or body — check the message |
401 | Unauthorized | Missing or invalid X-API-Key header |
403 | Forbidden | Valid key, but no access to that organisation |
404 | Not Found | Resource doesn't exist (or never did) |
429 | Too Many Requests | Rate limit exceeded — back off and retry |
500 | Internal Server Error | Something went wrong on our end — please retry |
Common errors
401 Unauthorized
{
"success": false,
"error": "Missing API key. Include X-API-Key header in your request."
}Fix: Include a valid API key in the X-API-Key header. Double-check
the prefix (sk_live_) and that you copied the entire string.
403 Forbidden
{
"success": false,
"error": "You do not have access to this organisation"
}Fix: Your API key is scoped to a different organisation. Use the key that was created for the organisation you're trying to access.
404 Not Found
{
"success": false,
"error": "Customer not found"
}Fix: Check that the resource ID is correct. If you're using
/customers/lookup, a 404 means no customer matched the phone/email —
treat it as a normal "no result" case rather than an error.
429 Rate Limited
{
"success": false,
"error": "Rate limit exceeded. Please wait before making more requests."
}Fix: Wait until the rate-limit window resets. Read
X-RateLimit-Reset for the exact unix timestamp. See
Rate Limiting for an automatic retry pattern.
Handling errors in code
async function makeApiRequest(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
...options.headers,
},
});
const data = await response.json();
if (!data.success) {
switch (response.status) {
case 401:
throw new Error("Invalid API key");
case 403:
throw new Error("Access denied to this resource");
case 404:
throw new Error("Resource not found");
case 429: {
const resetTime = response.headers.get("X-RateLimit-Reset");
throw new Error(`Rate limited. Retry after ${resetTime}`);
}
default:
throw new Error(data.error || "Unknown error");
}
}
return data.data;
}import requests
def make_api_request(method, url, **kwargs):
headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
**kwargs.pop("headers", {}),
}
res = requests.request(method, url, headers=headers, **kwargs)
body = res.json()
if not body.get("success"):
if res.status_code == 401:
raise RuntimeError("Invalid API key")
if res.status_code == 403:
raise RuntimeError("Access denied to this resource")
if res.status_code == 404:
raise RuntimeError("Resource not found")
if res.status_code == 429:
reset = res.headers.get("X-RateLimit-Reset")
raise RuntimeError(f"Rate limited. Retry after {reset}")
raise RuntimeError(body.get("error", "Unknown error"))
return body["data"]