❌
Error Reference
The CaseHug API uses standard HTTP status codes and returns a consistent error format for all failures. This page documents the error structure, status codes, and common errors.
Error Response Format
All errors return a JSON body with an error object containing a machine-readable code, a human-readable message, and optional details.
Error Response
{
"error": {
"code": "matter_not_found",
"message": "No matter found with id mat_01HXYZ",
"details": {
"id": "mat_01HXYZ",
"resource": "matter"
}
}
}Error Object Fields
| Field | Type | Description |
|---|---|---|
| code | string | Machine-readable error identifier (snake_case) |
| message | string | Human-readable description of the error |
| details | object | Additional context — structure depends on error type |
Validation Error Format
For 400 validation errors, the details object includes a fields array describing exactly what failed.
Validation Error
{
"error": {
"code": "validation_error",
"message": "Request body contains invalid fields",
"details": {
"fields": [
{
"field": "title",
"message": "title is required"
},
{
"field": "practice_area",
"message": "practice_area must be one of: personal_injury, estate_planning, family_law, immigration, criminal, business, real_estate, other"
}
]
}
}
}HTTP Status Codes
| Status | Meaning | When It Occurs |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource was successfully created |
| 204 | No Content | Request succeeded, no body (e.g. DELETE) |
| 400 | Bad Request | Invalid request body, missing required fields, or validation errors |
| 401 | Unauthorized | Missing, invalid, or expired API key or token |
| 403 | Forbidden | Valid credentials but insufficient permissions or scope |
| 404 | Not Found | Resource does not exist or belongs to another firm |
| 409 | Conflict | Duplicate resource (e.g. email already exists) |
| 422 | Unprocessable Entity | Semantically invalid request (e.g. invalid state transition) |
| 429 | Too Many Requests | Rate limit exceeded — see Retry-After header |
| 500 | Internal Server Error | Unexpected server error — contact support |
| 502 | Bad Gateway | Upstream service unavailable (temporary) |
| 503 | Service Unavailable | API is down for maintenance |
Common Error Codes
401
| Error Code | How to Fix |
|---|---|
| missing_api_key | Add X-Api-Key header to your request |
| invalid_api_key | Check for typos. Regenerate the key in Settings → API Keys if needed |
| expired_token | Refresh your OAuth access token using the refresh_token grant |
| revoked_api_key | The key was manually revoked. Create a new API key |
403
| Error Code | How to Fix |
|---|---|
| insufficient_scope | Regenerate the API key with the required scope enabled |
| firm_suspended | Contact support@calmintake.com to restore your account |
| forbidden | The resource belongs to a different firm than the API key |
404
| Error Code | How to Fix |
|---|---|
| matter_not_found | Check the matter ID. Use GET /api/v1/matters to list valid IDs |
| client_not_found | Check the client ID or create the client first |
| webhook_not_found | Verify the webhook ID via GET /api/v1/webhooks |
| upload_not_found | The upload ID was not returned by POST /uploads/signed-url |
409
| Error Code | How to Fix |
|---|---|
| duplicate_email | Use GET /api/v1/clients?search=email to find the existing client |
| user_already_exists | The email is already a member of this firm |
429
| Error Code | How to Fix |
|---|---|
| rate_limit_exceeded | Wait for the Retry-After duration before retrying. Implement exponential backoff |
422
| Error Code | How to Fix |
|---|---|
| invalid_status_transition | Check valid status transitions in the endpoint docs |
| upload_url_expired | Request a new signed URL — they expire in 15 minutes |
| user_limit_reached | Upgrade your plan to add more users |
| send_failed | Verify the email/phone is valid. Check your firm notification settings |
Handling Errors in Code
JavaScript / TypeScript
typescript
async function apiRequest(url, options = {}) {
const response = await fetch(`https://api.calmintake.com${url}`, {
...options,
headers: {
'X-Api-Key': process.env.CASEHUG_API_KEY,
'Content-Type': 'application/json',
...options.headers,
},
});
const body = await response.json();
if (!response.ok) {
const { error } = body;
switch (response.status) {
case 400:
throw new ValidationError(error.message, error.details?.fields);
case 401:
throw new AuthError('Authentication failed: ' + error.code);
case 403:
throw new PermissionError(error.message);
case 404:
throw new NotFoundError(error.message);
case 429:
const retryAfter = response.headers.get('Retry-After');
throw new RateLimitError(`Rate limited. Retry after ${retryAfter}s`);
default:
throw new ApiError(error.message, response.status);
}
}
return body.data;
}Python
python
import requests
import time
def api_request(method: str, path: str, **kwargs) -> dict:
response = requests.request(
method,
f"https://api.calmintake.com{path}",
headers={
"X-Api-Key": os.environ["CASEHUG_API_KEY"],
"Content-Type": "application/json",
},
**kwargs
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
return api_request(method, path, **kwargs) # retry once
if not response.ok:
error = response.json().get("error", {})
code = error.get("code", "unknown")
message = error.get("message", "API request failed")
raise CaseHugApiError(
f"[{response.status_code}] {code}: {message}",
status_code=response.status_code,
code=code,
)
return response.json()["data"]Server Errors (5xx)
If you receive a 500, 502, or 503 response, it is a server-side issue. We recommend:
- • Implement retry with exponential backoff (start at 1s, max 3 attempts)
- • Log the full response body — it may contain a trace ID to share with support
- • Check status.calmintake.com for ongoing incidents
- • Contact support@calmintake.com with the trace ID if the issue persists