Como tratar erro 429 (rate limit) numa API REST em Python
Quando uma integração começa a falhar com o código HTTP 429, a API está a dizer-te que estás a enviar pedidos a mais. Saber tratar este erro com retries e backoff exponencial é o que separa um script frágil de uma integração de dados fiável. Vamos ver como fazer isso em Python, de forma simples e passo a passo.
Pré-requisitos
- Python 3.8 ou superior instalado.
- A biblioteca
requests(instala compip install requests). - Conhecimentos básicos de pedidos a uma API REST (GET e cabeçalhos).
- Um endpoint para testar — podes usar o serviço público
httpbin.org.
Passo 1: Perceber o erro 429 e o cabeçalho Retry-After
O código 429 Too Many Requests indica que ultrapassaste o limite de pedidos (rate limit) que a API permite num determinado intervalo de tempo. Este limite existe para proteger o serviço de sobrecarga e garantir acesso justo a todos os clientes. Muitas APIs respondem com um cabeçalho Retry-After que diz quantos segundos deves esperar antes de tentar de novo.
Se a resposta trouxer o cabeçalho Retry-After, respeita-o sempre. É a indicação oficial da API sobre quando podes voltar a tentar.
Passo 2: Detetar o 429 num pedido simples
Começa por fazer um pedido normal e verificar o código de estado. Assim consegues ver quando a API devolve 429 antes de construíres a lógica de repetição.
import requests
resposta = requests.get("https://httpbin.org/status/429", timeout=10)
if resposta.status_code == 429:
print("Recebi 429 — a API pediu para abrandar.")
else:
print("Tudo certo:", resposta.status_code)
Passo 3: Repetir com backoff exponencial
Em vez de tentar sempre com o mesmo intervalo, aumenta o tempo de espera a cada falha: 1s, 2s, 4s, 8s... A isto chama-se backoff exponencial e evita martelar a API. Se existir o cabeçalho Retry-After, usamos esse valor; caso contrário, calculamos a espera com base no número da tentativa.
import time
import requests
def get_com_retry(url, max_tentativas=5):
for tentativa in range(max_tentativas):
resposta = requests.get(url, timeout=10)
if resposta.status_code != 429:
resposta.raise_for_status()
return resposta
espera = int(resposta.headers.get("Retry-After", 2 ** tentativa))
print(f"429 recebido. A aguardar {espera}s (tentativa {tentativa + 1})")
time.sleep(espera)
raise RuntimeError("Limite de tentativas excedido")
O ciclo repete o pedido até obter sucesso ou esgotar as tentativas. A função devolve a resposta assim que a API responde bem e levanta um erro claro caso desista. O timeout garante que um pedido nunca fica pendurado para sempre.
Passo 4: A abordagem robusta com Session e Retry
Para produção, o requests integra-se com o mecanismo Retry do urllib3, que trata da repetição automaticamente em todos os pedidos de uma sessão. É menos código e cobre também erros de servidor (500, 502, 503 e 504).
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=5,
backoff_factor=1, # aumenta a espera a cada tentativa
status_forcelist=[429, 500, 502, 503, 504],
respect_retry_after_header=True,
)
sessao = requests.Session()
adaptador = HTTPAdapter(max_retries=retry)
sessao.mount("https://", adaptador)
sessao.mount("http://", adaptador)
resposta = sessao.get("https://httpbin.org/get", timeout=10)
print(resposta.status_code)
Com respect_retry_after_header=True, a sessão respeita automaticamente o cabeçalho Retry-After enviado pela API. O status_forcelist define quais os códigos de estado que disparam uma nova tentativa.
Verificar o resultado
Para confirmar que a lógica funciona, aponta a função para https://httpbin.org/status/429, que devolve sempre 429: vais ver as mensagens de espera a aparecer e o tempo entre tentativas a crescer. Depois troca para https://httpbin.org/get, que responde 200, e confirma que a função devolve a resposta logo à primeira. Se ligares o registo (logging), deves ver, no máximo, o número de tentativas que definiste.
Conclusão
Com poucas linhas passaste de um script que rebenta ao primeiro 429 para uma integração que abranda de forma educada e recupera sozinha. O próximo passo natural é juntar esta lógica à paginação e à autenticação da API, para teres um cliente completo e resistente a falhas. Que valor de max_tentativas faz sentido para a API com que trabalhas — e já sabes qual é o limite de pedidos que ela impõe?