Errors & rate limits
Error format#
Every error response uses the same JSON shape and an appropriate HTTP status code:
{
"statusCode": 403,
"code": "FORBIDDEN",
"message": "Missing required scope: govern:checkpoints:write"
}
| Field | Description |
|---|---|
statusCode | The HTTP status code, repeated in the body. |
code | A stable, machine-readable error code. |
message | A human-readable description, safe to log. |
Status codes
| Status | When it happens |
|---|---|
400 Bad Request | A required field is missing or invalid — e.g. action omitted on a checkpoint, or tool_name omitted on a tool_call scan. |
401 Unauthorized | The API key is missing, malformed, revoked, or used from a disallowed IP. |
403 Forbidden | The key is valid but lacks the required scope. |
404 Not Found | The referenced resource (e.g. a checkpoint ID) does not exist in this workspace. |
429 Too Many Requests | The rate limit was exceeded. Honor the Retry-After header. |
500 | An unexpected server error. The message is generic; retry with backoff. |
Rate limits#
The API enforces a sliding-window, per-key limit.
- Default: 600 requests per minute per API key.
- Response when exceeded:
429 Too Many Requestswith aRetry-Afterheader (seconds to wait).
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Polling etiquette
When polling a checkpoint with GET /v1/checkpoints/{id}, keep a minimum of 1 second between polls of the same checkpoint — tighter polling is rejected. Prefer the long-poll endpoint GET /v1/checkpoints/{id}/wait, which holds the connection open and returns as soon as the state changes, so you make far fewer requests.
Idempotency#
Write endpoints (POST /v1/checkpoints, POST /v1/secure/scan) accept an optional Idempotency-Key header so retries are safe:
Idempotency-Key: 4f1e9c2a-7b3d-4a1f-9c8e-2d6b0a5f1e33
Reusing the same key with a different request body is rejected — a key is bound to the payload it was first seen with. Use a fresh UUID per logical operation, and reuse it only when retrying that exact operation.