Skip to content

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:

json
{ "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-After is 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:

RouteLimit per minute
POST /v1/apps/{app_id}/in-app-messages/eligible30
POST /v1/apps/{app_id}/in-app-messages/{id}/events120

Dashboard routes (session authenticated):

RouteLimit per minuteCounted per
Test push to a device10user and App
Segment preview60user and App
Billing: fresh plan read30user
Billing: confirm a checkout session5user
Media upload30client IP
Test the media storage settings10client IP

Sign-in, account and setup routes, per client IP:

RouteLimit per minute
Login and re-authentication10
Refresh30
Accept invite10
Forgot password5
Reset password, check reset token, verify e-mail, set password10 each
OAuth start, callback, sign-up20, 30, 5
Company onboarding5

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-After seconds before retrying the same call. Do not retry in a tight loop.
  • The In-App SDKs wait for Retry-After: a 429 on eligible waits when it is 60 seconds or less (at most 3 waits) and otherwise gives up for the session; a 429 on events pauses the queue for Retry-After, clamped between 1 and 300 seconds, and the event is retried with the same event_id.
  • Do not treat 429 as a reason to drop data: keep the queued events and send them later with the same event_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.