Limites de taxa
Token buckets. São três, são independentes, e qualquer um deles pode rejeitar uma requisição.
Os buckets são chaveados por conta e canal — não por chave de API. Emitir uma segunda chave não compra um segundo orçamento; as duas chaves puxam do mesmo bucket de cliente.
Os três buckets
| Bucket | Chaveado por | Burst | Recarga | Aplica-se a |
|---|---|---|---|---|
| Por IP (pré-auth) | IP do cliente | 30 | 30/s | Toda requisição, checado antes mesmo de a credencial ser lida |
| Por cliente | Sua conta | 60 | 1/s | Toda rota autenticada |
| Por canal | Conta + canal | 60 | 1/s | Qualquer rota cujo path contenha um id de canal |
O portão por IP existe para que uma enxurrada de tokens inválidos de um endereço só não queime nosso caminho de autenticação. Você só encontra ele martelando de um IP único.
Quanto custa uma requisição
Um token em cada bucket aplicável. Uma chamada a
/v1/channels/{id}/messages gasta um token de cliente e um token de canal.
Uma chamada a /v1/channels gasta só o de cliente.
O bucket com menos tokens restantes decide a resposta, e X-RateLimit-Scope diz
qual foi.
Ou seja, a vazão sustentada é de 1 requisição/segundo por canal, com um burst de 60 para absorver picos. Dimensione seu fan-out por isso: 500 mensagens num canal levam cerca de 440 segundos, não 500 milissegundos. Distribua entre canais ou controle o ritmo dos envios.
Headers de resposta
Respostas autenticadas carregam:
| Header | Significado |
|---|---|
X-RateLimit-Limit | Capacidade do bucket que decidiu. |
X-RateLimit-Remaining | Tokens restantes nele depois desta requisição. |
X-RateLimit-Scope | Qual bucket decidiu: customer, channel ou ip. |
No 429 Too Many Requests você também recebe:
| Header | Significado |
|---|---|
Retry-After | Segundos para esperar. Respeite. |
Não existe header X-RateLimit-Reset. Revisões anteriores desta página
documentavam um; código que lê ele recebe null. Use Retry-After no 429, ou
reset_at do GET /v1/limits.
O corpo é o envelope padrão com code: "rate_limited":
{
"error": {
"code": "rate_limited",
"message": "Channel is over the per-second limit (60 burst). Retry in ~4s."
}
}Lendo sua folga
Passe ?channel_id=<uuid> para preencher per_channel, ou autentique com uma
chave presa a um canal e ele vem preenchido sozinho:
GET /v1/limits?channel_id=8d653c66-e4ff-43ee-97da-3de5ad5680d4
{
"per_customer": { "limit": 60, "remaining": 47, "reset_at": "2026-09-10T14:22:31.000Z" },
"per_channel": { "limit": 60, "remaining": 12, "reset_at": "2026-09-10T14:22:29.000Z" },
"channel_id": "8d653c66-e4ff-43ee-97da-3de5ad5680d4",
"per_key": null
}Um per_channel null significa nenhum canal identificado, não que não
exista limite por canal. Como um envio de canal é cobrado dos dois buckets, a
folga real é min(per_customer.remaining, per_channel.remaining).
per_key é sempre null — não existe bucket por chave.
Esta chamada gasta um token, como toda chamada autenticada — ela só não cobra em dobro pela leitura em si.
Padrão de backoff
async function callApi(url, init) {
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
// Retry-After é autoritativo — é calculado da taxa de recarga real.
const wait = Number(res.headers.get('Retry-After') ?? 1) * 1000;
await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
}
throw new Error('Rate limited after 5 retries');
}Não use backoff exponencial contra 429 — o Retry-After já reflete a taxa de
recarga exatamente. Guarde o exponencial para 5xx transitórios.
O que não conta
- Entregas de webhook partem de nós e nunca tocam seu orçamento de entrada.
- Rotas públicas —
/healthz,/readyz,/v1/status/summary,/openapi.jsone o proxy de mídia assinado/v1/media/{token}— não são cobradas dos buckets da sua conta. O portão por IP continua valendo.
Limites relacionados
São tetos separados, não token buckets:
| Limite | Valor |
|---|---|
Streams SSE /v1/events simultâneos | 10 por conta (429 sse_connection_limit) |
| Tamanho de mídia (upload / envio) | 25 MB |
| Comprimento do texto da mensagem | 4096 caracteres |
| Comprimento da legenda | 1024 caracteres |
Comprimento do client_ref | 128 caracteres |
| Tamanho de página do histórico | 100 |
Precisa de mais?
Abra um ticket (POST /v1/support/tickets) ou escreva para
hello@whatisup.dev. Limites maiores fazem parte do plano Pro e são sob medida
no Scale.