API Documentation

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.