What is limited
Most of the REST API is not rate limited. In particular, the routes you call with a REST key to send notifications and manage Apps, Templates, Segments and Devices answer without any request-level throttle, and no X-RateLimit-* header is sent on any route.
A small set of routes has a budget, because they are open to the internet, sensitive, or costly. When a budget is spent, the route answers 429 with this body and a Retry-After header holding the seconds left in the current window:
{ "error": { "code": "rate_limited", "message": "too many requests, try again later" } }How the limit is counted
- Fixed one-minute windows. The counter starts again when the window ends, so
Retry-Afteris at most 60 seconds. - Counters live in Redis and are shared across replicas.
- Each budget is counted per client IP, per device or per user, as the tables below say. The IP is the one the server sees; behind a proxy it honors only the trusted proxies configured for the deployment.
- If Redis is unavailable, the request is allowed. An outage of the counter must not lock people out, so a limit is not a guarantee of protection.
Limits per route
Device-facing routes used by the SDKs, per device:
| Route | Limit per minute |
|---|---|
POST /v1/apps/{app_id}/in-app-messages/eligible | 30 |
POST /v1/apps/{app_id}/in-app-messages/{id}/events | 120 |
Dashboard routes (session authenticated):
| Route | Limit per minute | Counted per |
|---|---|---|
| Test push to a device | 10 | user and App |
| Segment preview | 60 | user and App |
| Billing: fresh plan read | 30 | user |
| Billing: confirm a checkout session | 5 | user |
| Media upload | 30 | client IP |
| Test the media storage settings | 10 | client IP |
Sign-in, account and setup routes, per client IP:
| Route | Limit per minute |
|---|---|
| Login and re-authentication | 10 |
| Refresh | 30 |
| Accept invite | 10 |
| Forgot password | 5 |
| Reset password, check reset token, verify e-mail, set password | 10 each |
| OAuth start, callback, sign-up | 20, 30, 5 |
| Company onboarding | 5 |
The numbers come from the server code of this version and can change between versions. Some budgets are shared by name across routes: login and re-authentication count against the same 10 per minute.
Handling a 429
- Wait at least
Retry-Afterseconds before retrying the same call. Do not retry in a tight loop. - The In-App SDKs wait for
Retry-After: a429oneligiblewaits when it is 60 seconds or less (at most 3 waits) and otherwise gives up for the session; a429oneventspauses the queue forRetry-After, clamped between 1 and 300 seconds, and the event is retried with the sameevent_id. - Do not treat
429as a reason to drop data: keep the queued events and send them later with the sameevent_id.
Push sending speed by plan
Separately from the request limits above, the plan caps how many Android (FCM) deliveries Notti starts per minute for one App: 100 on Free, 250 on Start, 500 on Pro and 1,000 on Business. The excess is scheduled for the next minutes, not rejected, so the API call that sends the notification still answers normally and no 429 is involved. iOS deliveries are not affected. See Usage & Billing.
Outbound 429 from FCM and APNs
Notti's delivery worker also reads 429 Too Many Requests answers from FCM and APNs while it pushes notifications out. It treats them as retryable delivery errors and backs off before sending again to the device. That is provider-side throttling on the outbound leg, unrelated to how fast you can call this API, and it is not exposed to you as a caller.
Practical guidance
- Sending to a large Segment is one API call; the delivery is fanned out internally per Device, so you do not need to throttle sends to stay under a device count.
- When scripting bulk work against Apps, Templates or Segments, no ceiling is enforced today, but keep concurrency reasonable: the backend still has real CPU, database and Redis capacity behind it.