Skip to content
WhatIsUp.dev

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

BucketChaveado porBurstRecargaAplica-se a
Por IP (pré-auth)IP do cliente3030/sToda requisição, checado antes mesmo de a credencial ser lida
Por clienteSua conta601/sToda rota autenticada
Por canalConta + canal601/sQualquer 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:

HeaderSignificado
X-RateLimit-LimitCapacidade do bucket que decidiu.
X-RateLimit-RemainingTokens restantes nele depois desta requisição.
X-RateLimit-ScopeQual bucket decidiu: customer, channel ou ip.

No 429 Too Many Requests você também recebe:

HeaderSignificado
Retry-AfterSegundos 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.json e 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:

LimiteValor
Streams SSE /v1/events simultâneos10 por conta (429 sse_connection_limit)
Tamanho de mídia (upload / envio)25 MB
Comprimento do texto da mensagem4096 caracteres
Comprimento da legenda1024 caracteres
Comprimento do client_ref128 caracteres
Tamanho de página do histórico100

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.