Error response format
Every error response from the Casework API returns a JSON body with a consistent shape:
{
"error": "A human-readable error message",
"details": {}
}
The error field is always present. The details field is optional and included when additional context is available, such as validation errors on specific fields.
HTTP status codes
| Status | Meaning | When you will see it |
|---|---|---|
400 | Bad Request | Missing required fields, malformed JSON, or invalid query parameters. |
401 | Unauthorized | No API key provided, or the key is invalid or expired. |
403 | Forbidden | Valid key but insufficient role, missing scope, or plan does not include the feature. |
404 | Not Found | The requested resource does not exist, or it belongs to a different tenant. |
409 | Conflict | The resource has been modified since you last read it (optimistic concurrency). |
422 | Unprocessable Entity | The request body is syntactically valid JSON but fails domain validation (e.g. invalid enum value, date in the past). |
429 | Too Many Requests | You have exceeded the rate limit. Check the Retry-After header. |
500 | Internal Server Error | Something unexpected went wrong on our side. If this persists, contact support. |
Authentication errors
The API authenticates requests using an API key passed via the Authorization header (Bearer sk_...) or the X-API-Key header.
When authentication fails, the response is always 401 or 403:
Invalid vs. expired vs. revoked
The API intentionally returns the same error message for invalid, expired, and revoked keys. This prevents attackers from discovering whether a given key string was ever valid.
Rate limiting
The API enforces rate limits using a sliding window algorithm backed by the database. Limits differ depending on the type of caller.
| Caller type | Limit | Window |
|---|---|---|
| API key | 100 requests | 1 minute (sliding) |
| Public endpoint (unauthenticated, per IP) | 5 requests | 1 hour (sliding) |
Public endpoint limits
Public rate limits apply to unauthenticated endpoints such as the contact form and public ward lookups. These are keyed by client IP address. Authenticated API key requests are always subject to the 100/minute limit regardless of endpoint.
Rate limit headers
Every API-key-authenticated response includes rate limit headers. Public endpoints only return these headers when the limit is exceeded.
429 response example
HTTP/1.1 429 Too Many Requests
Retry-After: 34
X-RateLimit-Remaining: 0
{
"error": "Rate limit exceeded"
}
Handling errors in code
Best practices
Exponential backoff
When you receive a 429 response, do not retry immediately. Use the Retry-After header as a baseline and apply exponential backoff for successive retries:
wait = Retry-After * (2 ^ attempt_number)
Cap your retries at 3 attempts. If you are still rate-limited after 3 retries, back off for a longer period or queue the request for later.
Do not retry 401 or 403 errors
Authentication and authorization errors will not resolve on their own. Retrying them wastes your rate limit budget. Only retry 429 and 5xx errors.
Idempotency
POST requests that create resources (cases, events, news articles) are not inherently idempotent. If your request times out or you receive a 5xx error, check whether the resource was created before retrying, to avoid duplicates. Use a GET with a known reference or external ID to verify.
Handling 429s proactively
Monitor the X-RateLimit-Remaining header on every response. When the value drops below 10, slow down your request rate. This is cheaper than hitting the limit and waiting for Retry-After.
Error logging
Log the full error body (including details) along with the HTTP status code and request path. This makes debugging significantly easier, especially for 422 validation errors where details contains per-field information.