Rate limits

ClickStream rate-limits per API key. The limit is a sliding-window counter on events-per-second (or requests-per-second for query APIs) with a generous 2× burst allowance for real-world traffic spikes.

The collector caches the per-key counter in memory for zero-latency fast-path decisions, then syncs to a RateLimiterDO Durable Object every 10 seconds for cross-isolate authoritative state (SOC2-friendly).

Per-tier caps

Event ingestion caps, per API key, measured as sustained events/second:

TierEvents / secMonthly cap*Overage behavior
Free10050,000 pageviewsRate-limit; upgrade prompt at 80 %
Builder1,000500,000Rate-limit by default; opt-in overage billing at $1.00 / 100k
Scale5,0005,000,000Rate-limit by default; opt-in overage billing at $0.50 / 100k
Network25,00025,000,000Rate-limit by default; opt-in overage billing at $0.25 / 100k
Custom100,00025,000,000+Negotiated

*Monthly cap is the included quota for pageviews (the volume-dominant event type). Click, scroll, form, and identify events don't count against the pageview cap. See the Pricing page for the full overage-meter matrix (pageviews, identity resolutions, ig_signals, ig_resolve, ig_graph_query).

Burst allowance

The effective cap is 2× the sustained rate for short bursts. A Builder-tier key can push 2,000 events/second for ~1 second before the limiter starts rejecting. The sustained 1,000/sec limit reasserts within a few seconds as the sliding window advances.

This exists because real-world traffic isn't smooth: a TV-ad spike or a Twitter thread can ×5 your baseline for tens of seconds. The burst multiplier absorbs the first few seconds so you don't drop user events during the initial rush; sustained load beyond that still hits the cap.

429 response

When you blow the window, the collector returns:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 47
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1713797700

{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded (1000 req/min). Retry in 47s.",
  "retryAfter": 47
}

Well-behaved clients (including the ClickStream SDK) respect Retry-After and back off. Raw HTTP callers should mirror that behavior — exponential backoff on repeated 429s is the minimum; a token bucket on the client side is better.

Headers on successful responses

Every authenticated response (not just 429s) carries the rate-limit state so you can budget preemptively:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1713797700   # unix epoch seconds

Overage billing vs hard-gate

By default, Builder / Scale / Network tiers rate-limit when you exceed the monthly pageview cap — the collector starts rejecting events with 429 rate_limit_exceeded once the 2× burst allowance is exhausted.

You can opt in to overage billing in the dashboard (Settings → Billing → Overages enabled). With overage billing on, the collector keeps accepting events beyond the included cap and meters the overage to Stripe; you get billed monthly at the per-tier unit rate (see table above).

The dashboard shows an upgrade recommendation when your trailing-3-month overage cost exceeds the next tier's monthly price — at that point it's cheaper to move up a tier than keep paying per event.

Per-endpoint notes

WAF layer (Cloudflare, future)

The in-memory + DO counter described above is the application-layer limiter. A separate Cloudflare WAF Advanced Rate Limiting tier is in the roadmap; when it's enabled, gross abuse (thousands of requests/second from a single IP) gets dropped at the edge before it even hits a Worker invocation.

The WAF add-on is not currently provisioned on the ClickStream zone. The collector's in-memory limiter handles all real traffic today.

See also