This guide is about sending events efficiently at scale: how to batch, how the limits compose, and how to retry without double-counting.
The limits, composed
Three limits apply to POST /v1-batch:
| Limit | Value |
|---|---|
| Events / request | 100 |
| Bytes / request | 1 MiB (1,048,576) |
| Request rate | 100 requests / minute per (org, IP) |
At the maximum batch size, the per-minute rate cap works out to ~10,000 events per minute per IP (100 requests × 100 events). The rate limit is keyed per (organization, IP): browser and mobile clients each have their own IP, so the cap is effectively per-client, while a single server sending from one IP shares one bucket.
On top of the per-IP limit, /v1-batch also enforces a coarse 2000 requests
per minute per organization (aggregated across all IPs) as an anti-abuse
backstop — an org fanning out across many IPs hits this ceiling and gets a 429
(Rate limit exceeded: max 2000 requests per minute per org, with Retry-After).
Most integrations never approach it.
Batch, don't drip
Accumulate events client-side and flush them in batches rather than one request per event. Aim for batches of up to 100 events that stay under 1 MiB — flush on a size threshold, a time interval, or page unload, whichever comes first.
If a batch would exceed 1 MiB (fat properties / context payloads can get
there well before 100 events), split it by bytes, not just count. The 413
response carries a machine-readable limit_bytes so you can resize
programmatically — see Errors & rate
limits.
Per-event limits
Beyond the per-request caps, two per-event limits matter at scale:
- Event name ≤ 200 characters. A
trackevent whoseeventname exceeds 200 chars is rejected on its own (event name too long) while the rest of the batch ingests. Keep names short and from a fixed vocabulary. - Property soft-caps (warn-only). Very large
properties/traits— more than ~300 keys, a value over ~32 KiB, or nesting deeper than 8 levels — are still stored but emit a server-side warning. Trim fat payloads to keep events lean; only the 1 MiB body and 100-event batch caps are hard rejections.
Idempotent retries
Every event carries a unique messageId, and the server de-duplicates on it. So
a retried request is safe: an event that already landed won't be counted twice.
- Reuse the same
messageIdwhen you retry — don't mint a new one. - Retry the per-minute rate-limit
429(honourRetry-After) and5xx/ network errors with exponential backoff and jitter. A monthly-quota429shouldn't be retried naively — see Rate limit vs. monthly quota below. - Don't retry other
4xx— fix the request instead. - If you use the Web SDK: it treats
429as permanent and does not honourRetry-Aftertoday, so the rate-limit-retry advice above is for direct-REST clients. Heavy senders that need rate-limit retry should post to/v1-batchdirectly.
The full policy is on the Errors & rate limits page.
Rate limit vs. monthly quota
Two different 429s, two different causes:
- Per-minute rate limit — you're sending too fast. Back off for the window
(
Retry-Aftertells you how long) and resume. - Monthly event quota — your plan's event allowance for the month is exhausted. Backing off won't help; ingestion resumes when the monthly counter resets or you upgrade.
Watch the failed / errors fields on every 200, too — a 200 with
failed > 0 means some events in the batch were rejected even though the request
succeeded.
Next
- Errors & rate limits — status codes, the retry policy, and the rate-limit headers.
- Event ingestion — the batch request shape and limits.
Last updated 2026-06-10