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:
| Tier | Events / sec | Monthly cap* | Overage behavior |
|---|---|---|---|
| Free | 100 | 50,000 pageviews | Rate-limit; upgrade prompt at 80 % |
| Builder | 1,000 | 500,000 | Rate-limit by default; opt-in overage billing at $1.00 / 100k |
| Scale | 5,000 | 5,000,000 | Rate-limit by default; opt-in overage billing at $0.50 / 100k |
| Network | 25,000 | 25,000,000 | Rate-limit by default; opt-in overage billing at $0.25 / 100k |
| Custom | 100,000 | 25,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
POST /v1/events— the main counter. Batched requests count asevents.length, not 1.GET /v1/signals/:visitorId— no separate limit; shares the per-key events/sec counter. Fans out to 10 Durable Object partitions on the server side, so a single client request costs ~10 DO reads internally.GET /signals/streamWebSocket — no subscribe-side rate limit. TheMAX_CONNECTIONS = 10 per tenantcap inside SignalsFeedDO is the backpressure instead. You can hold the 10 long-lived connections open forever (modulo the 60-min duration cap) without incrementing any counter.POST born.clickstream.com/v1/resolve(identity graph) — identity resolutions are metered on their own counter; overages bill to theidentity_resolutionmeter at $0.005 / resolve on Builder, cheaper on higher tiers.
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
- API keys + auth — permission scopes + rotation
- Event schema — what each accepted event actually records
- Pricing — tiers, meters, included quotas