
Por que derrubei uma API de terceiro — e o que aprendi sobre rate limit
Rodei um script de sincronização sem throttle. Em três minutos fiz 4.200 requests. A API bloqueou minha chave. O cliente ficou sem sistema por duas horas.
Fiz 4.200 requests para a API do CRM em três minutos.
Não foi erro de lógica — foi minha primeira sincronização em lote, rodando sem nenhum controle de velocidade. O limite da API era 100 requests por minuto. Ultrapassei por 14x.
A API bloqueou minha chave de API. Minha aplicação começou a retornar 429 (Too Many Requests) em tudo — não só na sincronização, mas em qualquer request que dependia da mesma chave, incluindo funcionalidade normal do sistema.
O cliente ficou sem sistema por duas horas até eu conseguir uma nova chave e configurar.
Rate limiting existe porque API é recurso compartilhado.
Quando você chama uma API externa, está usando infraestrutura que serve milhares de outros clientes. Sem limite, um cliente pode consumir tudo e degradar o serviço para os outros. Rate limit é proteção — do provedor e, indiretamente, dos outros usuários.
O erro que cometi foi comum: ler a documentação suficientemente para entender como autenticar e como chamar os endpoints, mas não ler a parte sobre limites.
O que implementei depois:
import time
from ratelimit import limits, sleep_and_retry
CALLS_PER_MINUTE = 80 # 80% do limite real como margem
@sleep_and_retry
@limits(calls=CALLS_PER_MINUTE, period=60)
def call_crm_api(endpoint, payload):
return requests.post(endpoint, json=payload, headers=auth_headers)
Ou, sem biblioteca, manualmente:
def sync_records_with_throttle(records, delay_seconds=0.8):
for i, record in enumerate(records):
call_api(record)
if i > 0 and i % 10 == 0:
time.sleep(1) # pausa a cada 10 calls
Tem outro nível nisso: respeitar rate limit não é só não ultrapassar. É tratar o erro 429 quando acontecer.
Mesmo com throttle local, a API pode ter limites dinâmicos, ou outros clientes podem estar usando a mesma quota compartilhada, ou você pode ter um pico legítimo.
429 com Retry-After header te diz quando tentar de novo:
response = requests.post(endpoint, ...)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
# tenta de novo
Tratar 429 graciosamente em vez de propagar erro para o usuário é a diferença entre sistema resiliente e sistema frágil.
Toda API que você integra tem limite. Parte do trabalho de integração é descobrir esse limite e planejar para ele — antes de rodar em produção.