API Rate Limits

Overview

ET Ducky enforces rate limits at two layers. Transport limits are applied by the API's built-in rate limiter, per policy, to groups of endpoints. Application limits are domain-specific caps (e.g. shell-command execution) enforced in service code. Exceeding either returns HTTP 429 Too Many Requests. Most limits are partitioned (per IP, per organization, per agent, per token, or per user and organization, as noted) so one tenant's traffic cannot consume another tenant's allowance. The fixed and sliding policies are global.

Transport rate-limit policies

PolicyLimitWindowPartitionQueueApplies to
fixed100 requests1 minuteglobal10AI / analysis endpoints (e.g. Analysis, Data Explorer)
sliding200 requests5 minutes (5 segments)global20Endpoints on the sliding-window policy
discovery-per-ip10 requests1 minutesource IPnoneAnonymous agent discovery / registration-token endpoints (brute-force protection)
oob-commands-per-org60 requests1 minuteorganizationnoneOut-of-band gateway REST & command endpoints
contact-report-per-ip5 requests1 minutesource IPnonePublic contact form & status-page issue reporter
health-per-ip120 requests1 minutesource IPnone/health and /ready
events-ingest-per-agent600 requests, burst 1,200token bucket, refilled each minuteagent tokennoneAgent event uploads (POST /api/events/batch and POST /api/events/correlated)
public-api-per-ip600 requeststoken bucket, refilled each minuteclient addressnonePublic read API (/api/v1) and MCP endpoint (/api/mcp)

The rejection status for all policies is 429.

Agent event uploads

Agents upload events once a minute. Each agent may make 600 upload requests a minute, with a burst of up to 1,200, counted across POST /api/events/batch and POST /api/events/correlated. Requests that carry agent tokens from one client address also share a bucket of 30,000 a minute with a burst of 60,000. A request over either limit is answered 429 with Retry-After and X-RateLimit-Policy: events-ingest-per-agent.

Public API and MCP endpoint

Two limits apply to the public read API and MCP endpoint:

LimitValueCountedX-RateLimit-Policy
Per client address600 requests a minuteOn every request to /api/v1 and /api/mcp, before the token is checked, whether or not the token is validpublic-api-per-ip
Per organization API token120 requests a minuteAfter the token is accepted, on both surfaces togetherpublic-api-per-token

Both are answered 429 with Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Policy.

Health endpoints

/health and /ready are limited to 120 requests a minute per client address (health-per-ip). See Health Monitoring for what each endpoint checks.

Request size limits

An agent event batch (POST /api/events/batch) is accepted up to 16 MiB. On the cloud service a larger body is refused with 413 at the edge, before it reaches the API. The API applies the same 16 MiB limit itself, to event batches and to correlated summaries (POST /api/events/correlated), and answers 413 above it.

Agents may send these bodies gzip-compressed. The server accepts gzip only and refuses any other encoding with 415. A compressed body is refused with 413 when it inflates past the route's limit, or when, past 1 MiB, it inflates to more than 50 times its compressed size.

Application limits

LimitValueScope
Shell command execution30 commandsper hour, per user, per organization
AI query quotaplan-based monthly quotaper organization (resets each billing period)

The AI quota is a monthly quota, not a sliding rate limit — it resets per billing period rather than per time window. Exhaustion returns 429 (or 402 where a plan upgrade resolves it) with a descriptive message.

Response headers on 429

When a request is rejected for exceeding a transport limit, the API returns standard rate-limit headers so clients can back off correctly:

HeaderMeaning
Retry-AfterSeconds until the limited window resets.
X-RateLimit-LimitThe rejecting policy's permit limit.
X-RateLimit-Remaining0 — the request was just rejected.
X-RateLimit-ResetSeconds until reset (mirrors Retry-After).
X-RateLimit-PolicyThe policy that refused the request. Sent by events-ingest-per-agent, public-api-per-ip and public-api-per-token.

X-RateLimit-Limit and X-RateLimit-Remaining are exposed via CORS so cross-origin browser clients can read them. The headers are sent on 429 responses only; the built-in limiter does not surface a per-request remaining count on successful responses.

Client guidance

On a 429, wait for the window to reset before retrying and respect the Retry-After header. The discovery, out-of-band, contact, health, event upload and public API limits have no queue, so excess requests are rejected immediately rather than delayed. Tenants on subdomains share their organization's partitioned allowances.

Error responses

When a request fails on an error the endpoint does not handle itself, the API answers with an application/problem+json body. Every such body carries a correlationId and a traceId:

{
  "title": "Internal server error",
  "status": 500,
  "correlationId": "...",
  "traceId": "..."
}
StatusTitle
400Invalid query (the detail field says what is wrong)
403Forbidden
404Not found
409Conflict
413Request body too large
429AI quota exhausted (with message, usedQuota and totalQuota)
502AI provider error (with provider and failureReason; the provider's own response is never included)
503Database unavailable
500Internal server error

A 5xx body never contains the error's internal message. API responses carry the correlation id in the X-Correlation-ID header. A client may send its own X-Correlation-ID of up to 64 letters, digits, hyphens, underscores and periods, and the API uses it for that request. When you contact support about a failed request, quote its correlation id.