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

Cómo tratar el error 429 (rate limit) en una API en Python

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

Cuando una integración empieza a fallar con el código HTTP 429, la API te está diciendo que estás enviando demasiadas peticiones. Saber gestionar este error con reintentos y backoff exponencial es lo que separa un script frágil de una integración de datos fiable. Veamos cómo hacerlo en Python, de forma sencilla y paso a paso.

Requisitos previos

  • Python 3.8 o superior instalado.
  • La biblioteca requests (instálala con pip install requests).
  • Conocimientos básicos de peticiones a una API REST (GET y cabeceras).
  • Un endpoint para probar — puedes usar el servicio público httpbin.org.

Paso 1: Entender el error 429 y la cabecera Retry-After

El código 429 Too Many Requests indica que has superado el límite de peticiones (rate limit) que la API permite en un intervalo de tiempo determinado. Este límite existe para proteger el servicio de la sobrecarga y garantizar un acceso justo a todos los clientes. Muchas APIs responden con una cabecera Retry-After que indica cuántos segundos debes esperar antes de volver a intentarlo.

Si la respuesta incluye la cabecera Retry-After, respétala siempre. Es la señal oficial de la API sobre cuándo puedes volver a intentarlo.

Paso 2: Detectar el 429 en una petición simple

Empieza con una petición normal y comprueba el código de estado. Así puedes ver cuándo la API devuelve 429 antes de construir la lógica de reintento.

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)

Paso 3: Reintentar con backoff exponencial

En lugar de reintentar siempre con el mismo intervalo, aumenta el tiempo de espera tras cada fallo: 1s, 2s, 4s, 8s... Esto se llama backoff exponencial y evita saturar la API. Si existe la cabecera Retry-After, usamos su valor; si no, calculamos la espera según el número de intento.

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")

El bucle repite la petición hasta tener éxito o agotar los intentos. La función devuelve la respuesta en cuanto la API responde bien y lanza un error claro si se rinde. El timeout garantiza que una petición nunca se quede colgada para siempre.

Paso 4: El enfoque robusto con Session y Retry

Para producción, requests se integra con el mecanismo Retry de urllib3, que gestiona los reintentos automáticamente en todas las peticiones de una sesión. Es menos código y también cubre errores de servidor (500, 502, 503 y 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)

Con respect_retry_after_header=True, la sesión respeta automáticamente la cabecera Retry-After enviada por la API. El status_forcelist define qué códigos de estado disparan un nuevo intento.

Verificar el resultado

Para confirmar que la lógica funciona, apunta la función a https://httpbin.org/status/429, que siempre devuelve 429: verás aparecer los mensajes de espera y crecer el tiempo entre intentos. Después cambia a https://httpbin.org/get, que responde 200, y confirma que la función devuelve la respuesta al primer intento. Si activas el logging, deberías ver como máximo el número de intentos que definiste.

Conclusión

Con unas pocas líneas has pasado de un script que se rompe al primer 429 a una integración que reduce el ritmo de forma educada y se recupera sola. El siguiente paso natural es unir esta lógica con la paginación y la autenticación de la API, para tener un cliente completo y tolerante a fallos. ¿Qué valor de max_tentativas tiene sentido para la API con la que trabajas — y ya sabes el límite de peticiones que impone?