Limites
Todos os tetos da API em um lugar — requisições por segundo, por minuto, tamanho de corpo, limites de produto e a franquia comercial do plano gratuito, com o que você vê quando estoura cada um.
Limites
Nenhum limite aqui é surpresa: os números publicados estão travados por teste de
contrato, justamente para que quem integrou pelo número publicado não comece a
tomar 429 sem explicação. Esta página é a lista completa.
1. Requisições por segundo, por aplicativo
Cada classe de rota tem o próprio balde. Estourar o balde da consulta não afeta o do envio.
| Classe | Rotas | Teto |
|---|---|---|
| Envio | POST /api/v1/notifications | 6.000 req/s por aplicativo |
| Consulta | GET /api/v1/notifications/{id} | 1.000 req/s por aplicativo |
| Exportação | POST /api/v1/players/csv_export | 1 req/s por aplicativo |
As rotas de aparelho não têm teto por segundo por aplicativo. Registro, atualização, recibos, In-App e parâmetros do Firebase são limitados por outro caminho: o teto por endereço de origem da seção 2 e o teto de 50.000 registros por hora por aplicativo da seção 4. Se você está dimensionando uma carga em massa de registro, é contra esses dois números que a conta precisa ser feita.
A consulta repetida não custa cota
Antes de cobrar o teto, a consulta é servida de um cache de 1 segundo. Se a sua integração pergunta duas vezes pelo mesmo disparo no mesmo segundo, a segunda resposta vem do cache, não conta no balde e não toca o banco.
Isso é deliberado: uma integração que acompanha milhares de disparos por ciclo
tem todo o direito de perguntar muito, e o dado tem 1 segundo de idade — não
mudou nada. O teto passa a proteger o que realmente custa, que é a ida ao banco.
Quem cai no 429 é quem pede coisas diferentes rápido demais.
O balde sobrevive à queda do cache compartilhado
Com o cache compartilhado no ar, a conta é global do aplicativo. Se ele cair, o teto continua existindo, só que por processo do serviço — degradado, nunca desligado.
2. Requisições por minuto, por endereço de origem
As rotas públicas chamadas pelo aparelho são limitadas por origem:
| Camada | Teto | Resposta |
|---|---|---|
| Serviço | 120 req/min por IP | 429 + Retry-After: 60 |
| Borda | 240 req/min por IP, com rajada de 60 | 429 |
A borda é propositalmente mais frouxa que o serviço: ninguém que passa pelo serviço é barrado pela borda. Ela existe como segunda linha, para o caso de o tráfego crescer mais rápido do que o processo consegue recusar.
Requisições recusadas também contam — martelar não ganha tentativas.
{
"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"
}
}
Sobre o cabeçalho Retry-After
Seja preciso ao programar o seu backoff, porque os dois 429 não são iguais:
- o
429por origem (rotas do aparelho) emiteRetry-After: 60; - o
429por classe (envio, consulta, exportação) traz o tempo de espera no texto deexplain.como_corrigir(“aguarde 1s e tente de novo”) e, nesta versão, não acrescenta o cabeçalho.
A regra segura para o seu cliente: leia Retry-After quando ele existir; na
ausência dele, respeite o valor citado em explain.como_corrigir. Nunca repita
em laço apertado.
3. Tamanho do corpo
| Rota | Teto no serviço | Teto na borda |
|---|---|---|
POST /api/v1/notifications | 256 KB | 256 KB |
Rotas do aparelho (players, receipts, inapp) | 16 KB | 16 KB |
Demais rotas /api/v1 | — | 64 KB |
Os dois tetos são diferentes de propósito. 16 KB é o corpo que um aparelho manda (registro, recibo) e não tem por que crescer. A rota de envio carrega lista de destinatários: com o máximo de 2.000 identificadores, o pedido tem cerca de 80 KB, e 256 KB dá folga de três vezes sobre o maior lote válido.
O teto é aplicado em duas etapas: se o Content-Length declarado já passa, a
resposta sai sem ler o corpo; se o corpo real passar (chunked, ou
Content-Length mentiroso), ele é interrompido ao bufferizar. Nos dois casos a
resposta é o 413 no envelope padrão, com o limite nomeado — nunca um 413 cru
sem explicação.
{
"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. Limites de produto
Tudo abaixo devolve um 400 ou 422 nomeado, nunca um truncamento silencioso.
Envio
| Limite | Valor |
|---|---|
Identificadores por chamada (include_player_ids / include_external_user_ids) | 2.000 |
Segmentos por chamada (included_segments) | 10 |
| Alvos por chamada | exatamente 1 dos três |
ttl | 0 a 2.419.200 s (28 dias); padrão 259.200 s (3 dias) |
collapse_id | 64 bytes |
send_after | no máximo 28 dias à frente |
| Payload renderizado (o que chega ao aparelho) | 3.891 bytes |
O limite de payload merece explicação, porque é o que mais surpreende. O
orçamento de 3.891 bytes é medido sobre o payload final, incluindo os 96
bytes que o servidor ainda injeta no envio (pm_msg_id e pm_rcpt — o rastro
que permite recibo de entrega e de clique). Se a porta ignorasse esses bytes, uma
mensagem no limite passaria aqui e morreria com erro no provedor de push — o
push sumiria em silêncio. A medição é feita antes de tocar o banco, e o 422
traz a contagem exata.
Aparelhos
| Limite | Valor |
|---|---|
identifier (token de push) | 194 bytes |
external_user_id | 128 bytes |
tags serializadas | 2 KB |
language | 32 bytes |
timezone | ±86.400 segundos |
Aparelhos por external_user_id | 10 |
| Registros por hora, por aplicativo | 50.000 |
Exportação da base
| Limite | Valor |
|---|---|
| Pedidos | 1 por segundo, por aplicativo |
| Aparelhos no arquivo | 5.000.000 (teto de sanidade) |
| Validade do arquivo publicado | 72 h |
O 429 da exportação ensina o caminho certo: “o arquivo anterior continua
valendo por 72h — baixe pela URL que já foi devolvida em vez de pedir outro”.
Idempotência
| Limite | Valor |
|---|---|
| Validade da chave de idempotência | 30 dias |
| Reserva em processamento antes de virar órfã | 60 s |
| Espera por uma reserva concorrente antes do 409 | 3 s |
Trinta dias não é enfeite: quem tem fila própria reprocessa dias depois, e a promessa “a mesma chave nunca dispara duas vezes” que valesse só por um dia não seria promessa, seria sorte.
Webhooks
| Limite | Valor |
|---|---|
| Tempo de resposta do seu endpoint | 10 s |
| Tentativas por evento | 30 (≈ 24 h) |
| Falhas seguidas que desligam o destino | 20 |
Detalhes em Webhooks.
Mensagens In-App
Os limites do canal In-App (blocos por campanha, tamanho do lote de eventos, campanhas por pacote de sessão, validade do pacote e o intervalo mínimo entre duas mensagens) vivem junto do contrato que os explica, em Mensagens In-App.
5. O limite comercial: a franquia do plano gratuito
Este não é um teto técnico, e é o único da página que recusa o envio inteiro.
O plano gratuito entrega dentro de uma franquia de usuários ativos no mês.
Acima dela, o POST /api/v1/notifications responde 422 e nada é
enviado — nem para uma parte da lista. Truncar seria pior: você veria “campanha
enviada”, e parte da sua base simplesmente não receberia, sem nenhum sinal.
| O que | Como funciona |
|---|---|
| Onde vive o número | na configuração de cobrança, não fixo no código. Hoje o valor publicado é de 1.000 usuários ativos por mês, gratuitos em qualquer plano — a página de preços é a fonte da verdade |
| Quem é medido | a organização, não cada aplicativo. Dois aplicativos do mesmo cliente dividem a mesma franquia |
| Quando recusa | quando o número medido passa da franquia. Exatamente na franquia ainda passa |
| Quem nunca é recusado | qualquer conta em plano pago. Ela não entra na medição, então não existe caminho de código que a barre |
A resposta traz os números para você agir sem abrir um chamado:
{
"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
}
}
Três honestidades sobre esse número:
- Ele não é tempo real. A contagem varre a base inteira, então ela é
refeita periodicamente em segundo plano — no máximo uma vez por minuto — e o
campo
medido_ha_sdiz a idade do número com que você está discutindo. - Ele é freio, não fatura. Entre uma medição e a seguinte, uma conta pode passar da franquia por alguns minutos e continuar entregando. Quem cobra é a fatura, medida pelo mês inteiro.
- Falha de infraestrutura nunca barra ninguém. Sem banco, sem medição utilizável, ou com uma medição velha demais (mais de 600 segundos), a conferência libera. Um cliente que para de entregar porque uma consulta interna falhou seria um estrago maior do que uma cobrança a menor — e essa é uma decisão escrita, não um acidente.
E a regra de retry: 422 é da família “corrija o pedido, não repita”. Aqui a
correção não é no seu JSON, é no plano — repetir a chamada não muda nada.
Veja também
| Assunto | Página |
|---|---|
| a tabela de códigos HTTP e a política de retry | Autenticação e erros |
| todos os campos e limites do disparo | Enviar push |
| tetos das rotas de aparelho, na prática | Aparelhos |
| o que é congelado por contrato e o que pode mudar | Versionamento |