Our engineers set up and run your first AI security scan. Get in touch

API rate limits

← All docs

The public API applies two layers of throttling: a token bucket on every authenticated request, and a tighter per-route cap on the small number of routes that start expensive background work.

Per-token bucket

Every authenticated request draws from one bucket per token.

PropertyValue
Capacity120 requests
Refill1 request per second

A client that stays at or below 1 request per second never depletes the bucket. A burst may run up to 120 requests before the bucket empties, after which the sustained rate applies.

Requests that fail authentication draw from a separate bucket keyed on client address, with a capacity of 60 and the same refill rate. That bucket exists to blunt token guessing and is consumed before any token lookup runs.

Per-route caps

These apply in addition to the bucket, and are keyed on the tenant and token.

RouteCap
POST /api/v2/reports6 per minute
POST /api/v2/scans6 per minute
POST /api/v2/rag-runs6 per hour, per asset
POST /api/v2/cloud-posture-runs2 per day

Each of these starts a worker job. The cap reflects how expensive the job is, not how expensive the HTTP request is.

POST /api/v2/reports carries a second control that is not a rate limit: at most 5 reports per tenant may be queued or generating at once. A caller inside the per-minute cap can still queue work faster than the worker drains it, so requests beyond that depth are refused with report_queue_full. Wait for a report to reach ready or failed before queueing another.

Request size and shape

Request bodies on write routes are capped at 1 MiB. A larger body is rejected with 413 and {"error": "payload_too_large", "limit_bytes": 1048576} before the handler runs.

Write routes also require the body to be a JSON object when one is sent:

ConditionResponse
Body absent or emptyAccepted; route defaults apply
Content-Type is not JSON415 unsupported_media_type
Body does not parse as JSON400 invalid_json
Body parses but is a list or scalar400 invalid_json_object

Nesting deeper than 512 levels does not parse and is answered with 400 invalid_json. These checks run before the handler, so a malformed request never reaches business logic and never consumes a per-route slot.

Response headers

Authenticated responses carry the bucket state:

HeaderMeaning
X-RateLimit-LimitBucket capacity
X-RateLimit-RemainingRequests left in the bucket
X-RateLimit-ResetSeconds until the bucket is full again

Handling 429

HTTP/1.1 429 Too Many Requests
Retry-After: 54
Content-Type: application/json

{
  "error": "rate_limited",
  "retry_after_seconds": 54
}

Retry-After and retry_after_seconds carry the same value. Wait for it rather than retrying on a fixed interval. A rejected request does not consume further budget, so backing off cleanly is enough to recover; there is no additional penalty for a client that retries too early, and no circuit breaker that disables a route platform-wide.

Queue-depth rejections also return 429, with error set to report_queue_full and no Retry-After. Poll an in-flight report instead of retrying immediately.

Polling asynchronous work

POST /api/v2/scans and POST /api/v2/reports return as soon as the job is queued. Poll the per-resource GET route to follow progress. Four seconds is a reasonable interval; anything under one second is wasted against the bucket. For high-volume integrations, use webhooks and treat polling as a fallback.

Related

Last reviewed: 2026-08-04. Reviewed by: Engineering. Content type: Developer documentation. Reach the maintainers: [email protected] .