(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisboa

Como tratar erro 429 (rate limit) numa API REST em Python

João Barros 05 de July de 2026 4 min de leitura

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 com pip 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?