Ingest has two independent guards: a per-key rate limit that stops floods, and a plan quota that caps billable volume. They fail differently on purpose, and the SDK handles both automatically.
Per-key rate limit
Each API key may send at most 1,200 requests per minute to the ingest endpoint (a fixed window). A healthy browser client flushes at most ~12 requests/minute, so even a large site stays far under this - the limit exists to contain a leaked or abused key. Exceed it and ingest responds:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{ "error": "rate_limited" }The SDK backs off exponentially and retries - events stay queued and send once the window clears, so nothing is lost to a brief spike.
Fails open
The rate counter is best-effort: if the check itself errors, ingest accepts the request rather than dropping data.
Plan quota & the dropped marker
Separately, every plan has a monthly events quota (events_count) with a 10% grace buffer. Only non-bot events count. When the quota (plus grace) is exhausted - or the account has no active plan - ingest deliberately does something different from the rate limit: it returns HTTP 200 with a dropped marker instead of an error.
{
"ok": true,
"inserted": 0,
"snapshots": 0,
"dropped": "quota_exceeded"
}dropped is a stable marker telling the client the batch was intentionally not stored:
| dropped value | Type | Description |
|---|---|---|
quota_exceeded | string | The plan's monthly events quota (plus the 10% grace) is used up. |
no_active_plan | string | The account has no active plan, so events can't be billed or stored. |
Why 200, not 429?
A 429 tells a client to retry - but an over-quota batch will never be accepted, so retrying would loop forever and drain the device. A 200 counts as delivered, so the SDK drops the batch and moves on - correct for a condition that only resolves when the plan resets or upgrades. The dropped field is there so your own tooling can tell the difference.
Payload & batch caps
Hard ceilings bound any single ingest request. The SDK stays well under them on its own; they exist to stop a runaway client or a decompression bomb.
| Limit | Type | Description |
|---|---|---|
Events per batch | ≤ 500 | More returns 413 batch_too_large. |
Request body (compressed) | ≤ 2 MB | Larger returns 413 payload_too_large. |
Decompressed size | ≤ 12 MB | Upper bound after gunzip; the SDK caps an uncompressed payload at ~900 KB. |
Client-side throttling
Before anything reaches the network, the SDK throttles capture with a token bucket (a sustained rate with burst headroom), batches events, and gzips the payload. Ordinary apps never approach the server limits - these guards fire almost only on genuinely abnormal traffic.
Read limits don't apply here
This page is about ingest (writing events). The read surfaces - the REST API and MCP server - clamp result sizes with per-endpoint limit caps rather than a request-rate quota.