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

StatusMeaningWhat it means in practice
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid parameters or body — check the message
401UnauthorizedMissing or invalid X-API-Key header
403ForbiddenValid key, but no access to that organisation
404Not FoundResource doesn't exist (or never did)
429Too Many RequestsRate limit exceeded — back off and retry
500Internal Server ErrorSomething 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"]

On this page