Rate limits

/track and /engage are rate limited to keep the API healthy for everyone. Limits are enforced at two levels: per IP address and per project. Exceeding either returns 429.

Limits

LimitDefaultKeyed by
Per-IP rate limit100 requests / 10 secondsClient IP address
Per-project rate limit1,000 requests / 10 secondsProject token

Whichever limit is hit first returns the 429. Both apply to /track and /engage.

429 response

When a request is rate limited, the whole batch is rejected - nothing in it is inserted - so it is always safe to retry the exact same payload. The response includes a Retry-After header (seconds, as an integer of at least 1):

bash
curl -i -X POST https://cohorly-service.velloalabs.com/track \
-H "Content-Type: application/json" \
-H "X-Cohorly-Token: YOUR_PROJECT_TOKEN" \
-d '{ "event": "Signed Up", "properties": { "distinct_id": "user_123" } }'
HTTP/1.1 429 Too Many Requests
Retry-After: 8
Content-Type: application/json
{ "status": 0, "error": "rate limited" }

Monthly event quota

Separately from the rate limit, each plan has a monthly event quota. If a batch would push your project's account over its quota for the current calendar month, the whole batch is rejected with 429 and a fixed Retry-After: 3600:

json
{ "status": 0, "error": "monthly event quota exceeded" }

Check your plan's event allowance in your account's usage page. As with rate limiting, the batch is rejected atomically, so it is safe to retry later.

Recommended client behavior

On any 429, back off exponentially and retry rather than dropping the batch:

  • Respect the Retry-After header when present - wait at least that many seconds before retrying.
  • Otherwise, use exponential backoff (for example starting at 2 seconds, doubling on each attempt, capped at a reasonable maximum) with a little random jitter to avoid retry storms.
  • Keep queuing new events locally while backing off - a rate limit is transient, not a rejection of your data.
All official SDKs (Web, React, Next.js, React Native, iOS) handle this automatically: events are queued locally and retried with exponential backoff on 429, respecting Retry-After. You do not need to implement any of this yourself when using an SDK. See the SDK docs for details.
PreviousIngestion
NextAdmin API