PushMesh
Sign in Request access
Open section navigation

Index

Limits

Every cap in one place — requests per second, per minute, body size, product limits, and the free plan's commercial allowance, including what you see when you exceed each one.

Limits

No limit here is a surprise: the published numbers are frozen by a contract test, precisely so that whoever integrated against a published number does not start taking 429s with no explanation. This page is the complete list.

1. Requests per second, per application

Each route class has its own bucket. Exhausting the read bucket does not affect the send bucket.

ClassRoutesCap
SendPOST /api/v1/notifications6,000 req/s per application
ReadGET /api/v1/notifications/{id}1,000 req/s per application
ExportPOST /api/v1/players/csv_export1 req/s per application

Device routes have no per-second, per-application cap. Registration, updates, receipts, In-App and the Firebase parameters are limited by a different path: the per-origin cap in section 2 and the 50,000 registrations per hour per application cap in section 4. If you are sizing a bulk registration load, those are the two numbers to plan against.

Repeating a read costs no quota

Before the cap is charged, a read is served from a 1-second cache. If your integration asks about the same push twice within the same second, the second answer comes from the cache, does not count against the bucket and never touches the database.

This is deliberate: an integration tracking thousands of pushes per cycle has every right to ask often, and data one second old has not changed. The cap then protects what actually costs — the database round trip. Who hits 429 is whoever asks for different things too fast.

The bucket survives losing the shared cache

With the shared cache up, the count is global per application. If it goes down, the cap still exists, just per service process — degraded, never switched off.


2. Requests per minute, per origin

The public routes called by the device are limited per origin:

LayerCapResponse
Service120 req/min per IP429 + Retry-After: 60
Edge240 req/min per IP, burst of 60429

The edge is deliberately looser than the service: nobody who passes the service is blocked by the edge. It exists as a second line, in case traffic grows faster than the process can refuse it.

Refused requests count too — hammering does not buy attempts.

{
  "errors": ["limite de requisições excedido"],
  "explain": {
    "causa": "mais de 120 req/min a partir do mesmo IP (203.0.113.10)",
    "como_corrigir": "aguarde a janela de 60 s e honre o Retry-After",
    "request_id": "0198f0b2-6e31-7a4c-9f10-2c9a1d4e7b55"
  }
}

The text arrives in Portuguese, exactly as shown. In English: “more than 120 req/min from the same IP” and “wait out the 60 s window and honour Retry-After”.

About the Retry-After header

Be precise when you program your backoff, because the two 429s are not the same:

  • the per-origin 429 (device routes) does emit Retry-After: 60;
  • the per-class 429 (send, read, export) carries the wait in the explain.como_corrigir text (“aguarde 1s e tente de novo” — wait 1 s and try again) and, in this version, does not add the header.

The safe rule for your client: read Retry-After when it is there; when it is not, honour the value quoted in explain.como_corrigir. Never retry in a tight loop.


3. Body size

RouteService capEdge cap
POST /api/v1/notifications256 KB256 KB
Device routes (players, receipts, inapp)16 KB16 KB
Other /api/v1 routes64 KB

The two caps differ on purpose. 16 KB is the body a device sends (registration, receipt) and it has no reason to grow. The send route carries a recipient list: at the maximum of 2,000 identifiers the request is around 80 KB, and 256 KB leaves three times the headroom over the largest valid batch.

The cap is applied in two stages: if the declared Content-Length already exceeds it, the response goes out without reading the body; if the actual body exceeds it (chunked, or a lying Content-Length), it is cut off while buffering. In both cases the answer is a 413 in the standard envelope, with the limit named — never a bare 413 with no explanation.

{
  "errors": ["corpo acima do limite da rota"],
  "explain": {
    "causa": "corpo excede o limite da rota (256 KB)",
    "como_corrigir": "reduza o corpo da requisição e reenvie",
    "request_id": "0198f0b2-6e31-7a4c-9f10-2c9a1d4e7b55"
  }
}

4. Product limits

Everything below returns a named 400 or 422, never a silent truncation.

Sending

LimitValue
Identifiers per call (include_player_ids / include_external_user_ids)2,000
Segments per call (included_segments)10
Targets per callexactly 1 of the three
ttl0 to 2,419,200 s (28 days); default 259,200 s (3 days)
collapse_id64 bytes
send_afterat most 28 days ahead
Rendered payload (what reaches the device)3,891 bytes

The payload limit deserves an explanation, because it surprises people the most. The 3,891-byte budget is measured on the final payload, including the 96 bytes the server still injects at send time (pm_msg_id and pm_rcpt — the trail that makes delivery and click receipts possible). If the front door ignored those bytes, a message right at the limit would pass here and die with a provider error — the push would vanish silently. The check runs before touching the database, and the 422 carries the exact count.

Devices

LimitValue
identifier (push token)194 bytes
external_user_id128 bytes
serialized tags2 KB
language32 bytes
timezone±86,400 seconds
Devices per external_user_id10
Registrations per hour, per application50,000

Base export

LimitValue
Requests1 per second, per application
Devices in the file5,000,000 (sanity cap)
Validity of the published file72 h

The export 429 teaches the right path: “the previous file is still valid for 72 h — download the URL you already received instead of asking for another”.

Idempotency

LimitValue
Idempotency key validity30 days
Processing reservation before it becomes orphaned60 s
Wait for a concurrent reservation before the 4093 s

Thirty days is not decoration: anyone with their own queue reprocesses days later, and a promise that “the same key never fires twice” valid for a single day would not be a promise, it would be luck.

Webhooks

LimitValue
Your endpoint’s response time10 s
Attempts per event30 (≈ 24 h)
Consecutive failures that disable the endpoint20

Details in Webhooks.

In-App messages

The In-App channel’s limits (blocks per campaign, event batch size, campaigns per session package, package validity and the minimum interval between two messages) live next to the contract that explains them, in In-App messages.


5. The commercial limit: the free plan allowance

This one is not a technical cap, and it is the only limit on this page that refuses the whole send.

The free plan delivers within a monthly active user allowance. Above it, POST /api/v1/notifications answers 422 and nothing is sent — not even to part of the list. Truncating would be worse: you would see “campaign sent”, and part of your base simply would not receive it, with no signal at all.

WhatHow it works
Where the number livesin the billing configuration, not hard-coded. The published value today is 1,000 monthly active users, free on any plan — the pricing page is the source of truth
Who is measuredthe organisation, not each application. Two applications of the same customer share one allowance
When it refuseswhen the measured number goes above the allowance. Exactly at the allowance still passes
Who is never refusedany account on a paid plan. It is not part of the measurement, so no code path can block it

The response carries the numbers so you can act without opening a ticket:

{
  "errors": ["plano Free atende até 1000 usuários ativos no mês — sua conta está com 1240"],
  "explain": {
    "causa": "o plano Free entrega dentro da franquia de 1000 usuários ativos no mês, e em 2026-08 a sua conta registrou 1240; acima da franquia o envio é recusado INTEIRO — entregar só para uma parte da base, escolhida por ordem de consulta, seria sumir com o resto em silêncio",
    "como_corrigir": "mude para o Premium (USD 0.005 por usuário ativo acima da franquia, sem mensalidade) e o envio volta na hora — os 1000 primeiros usuários ativos continuam gratuitos nos dois planos",
    "plano": "free",
    "mau": 1240,
    "mau_gratis": 1000,
    "mes": "2026-08",
    "moeda": "USD",
    "preco_por_mau": "0.005",
    "medido_ha_s": 12
  }
}

The prose arrives in Portuguese, exactly as printed above. In English, it reads: “the free plan delivers within an allowance of 1000 monthly active users, and in 2026-08 your account recorded 1240; above the allowance the send is refused ENTIRELY”, and the fix is “move to the paid plan and sending resumes immediately — the first 1000 monthly active users stay free on both plans”. Branch your code on explain.mau, explain.mau_gratis and explain.plano, never on the sentence.

Three honest notes about that number:

  1. It is not real time. Counting scans the whole base, so it is recomputed periodically in the background — at most once a minute — and the medido_ha_s field tells you the age of the number you are arguing with.
  2. It is a brake, not an invoice. Between one measurement and the next, an account can go past the allowance for a few minutes and keep delivering. Billing is settled by the invoice, measured over the whole month.
  3. Infrastructure failure never blocks anyone. With no database, no usable measurement, or a measurement that is too old (over 600 seconds), the check lets the send through. A customer who stops delivering because an internal query failed is a bigger loss than under-billing — and that is a written decision, not an accident.

And the retry rule: 422 belongs to the “fix the request, do not repeat” family. Here the fix is not in your JSON, it is in the plan — repeating the call changes nothing.


See also

TopicPage
the HTTP status table and the retry policyAuthentication and errors
every send field and limitSend a push
device route caps in practiceDevices
what is frozen by contract and what can changeVersioning