Errors & Rate Limits
The CrimeLayer API uses standard HTTP status codes. All error responses follow a consistent JSON shape so you can handle them uniformly in your code.
Error Response Shape
Every error returns a JSON object with a error field containing a machine-readable code and a human-readable message:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing X-API-Key header."
}
} HTTP Status Codes
| Status | Meaning | When It Occurs |
|---|---|---|
400 | Bad Request | Invalid JSON body, missing required fields (e.g. no cities array in batch), or malformed request. |
401 | Unauthorized | The X-API-Key header is missing from the request. |
403 | Forbidden | The API key is invalid, expired, or has been revoked. Regenerate from the Dashboard. |
404 | Not Found | The requested city is not in the CrimeLayer database. Use found: false responses for graceful fallback. |
429 | Too Many Requests | You've exceeded your monthly request quota or the per-minute burst limit. See Rate Limits below. |
500 | Internal Server Error | Unexpected server error. If persistent, contact support with the request ID from the response. |
Error Examples
400 Bad Request
{
"error": {
"code": "BAD_REQUEST",
"message": "Request body must include a 'cities' array."
}
} 401 Unauthorized
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing X-API-Key header."
}
} 403 Forbidden
{
"error": {
"code": "FORBIDDEN",
"message": "Invalid or revoked API key."
}
} 404 Not Found
{
"found": false,
"data": null
} Note: city lookups return 200 with found: false when the city simply isn't in the dataset — this is not an error, just a "not found" state. A true 404 only occurs when hitting an invalid API path.
429 Too Many Requests
{
"error": {
"code": "RATE_LIMITED",
"message": "Monthly request limit reached. Upgrade your plan or wait until the next billing cycle.",
"retry_after": null,
"resets_at": "2026-05-01T00:00:00Z"
}
} Rate Limits
Two rate limits apply to every API key:
| Limit Type | Value | Applies To |
|---|---|---|
| Monthly quota | Depends on plan (see Pricing) | Total requests per billing cycle |
| Burst limit | 60 requests / minute | All plans |
Retry-After Header
When you hit the burst limit (60 req/min), the response includes a Retry-After header with the number of seconds to wait before retrying:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
Content-Type: application/json
{
"error": {
"code": "RATE_LIMITED",
"message": "Burst limit exceeded. Retry after 23 seconds.",
"retry_after": 23
}
} Handling Rate Limits in Code
Check the HTTP status before reading the response body. On 429, read Retry-After and back off accordingly:
async function safetyLookup(city, apiKey) {
const res = await fetch(`https://api.crimelayer.com/v1/safety/${encodeURIComponent(city)}`, {
headers: { 'X-API-Key': apiKey }
});
if (res.status === 429) {
const retryAfter = res.headers.get('Retry-After');
throw new Error(`Rate limited. Retry in ${retryAfter ?? '60'}s.`);
}
if (!res.ok) {
const err = await res.json();
throw new Error(err.error?.message ?? 'API error');
}
return res.json();
} Need Help?
If you're seeing unexpected errors or believe you've hit a bug, email [email protected] with your request details and the request_id field from the error response.