429 Too Many Requests

Fundamentos Security Notes Jan 9, 2026 HTTP
http status-codes rate-limiting security rest

Definición

429 Too Many Requests es un código de estado HTTP que indica que el cliente ha enviado demasiadas solicitudes en un período de tiempo determinado. Esto forma parte del rate limiting (limitación de tasa), un mecanismo crítico de seguridad y rendimiento para prevenir abusos, ataques DoS y agotamiento de recursos.

La respuesta debe incluir:

  1. Encabezado Retry-After - Cuándo puede reintentar el cliente (segundos o fecha HTTP)
  2. Información de Límite de Tasa - Límites actuales y tiempo de reinicio (ej. encabezados X-RateLimit-*)
  3. Detalles del Error - Explicación de la política de límite de tasa

Estrategias comunes de límite de tasa:

  • Fixed Window (Ventana fija) - 100 solicitudes por hora (se reinicia al comienzo de cada hora)
  • Sliding Window (Ventana deslizante) - 100 solicitudes por ventana rodante de 60 minutos
  • Token Bucket (Cubo de tokens) - Las solicitudes consumen tokens; los tokens se rellenan a tasa constante
  • Leaky Bucket (Cubo con fugas) - Solicitudes en cola y procesadas a tasa constante

429 está definido en RFC 6585 y es esencial para proteger APIs de abusos y garantizar un uso justo de recursos.

Ejemplo

429 Too Many Requests - Límite de Tasa Excedido:

POST /api/users HTTP/1.1
Host: api.example.com
Authorization: Bearer YOUR_TOKEN

HTTP/1.1 429 Too Many Requests
Retry-After: 3600
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1736424000
Content-Type: application/json

{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. You can make 100 requests per hour.",
  "limit": 100,
  "remaining": 0,
  "resetAt": "2026-01-09T12:00:00Z",
  "retryAfter": 3600
}

429 con Fecha HTTP en Retry-After:

GET /api/search?q=example HTTP/1.1
Host: api.example.com

HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 09 Jan 2026 12:00:00 GMT
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1736424000
Content-Type: application/json

{
  "error": "Too Many Requests",
  "message": "Search rate limit exceeded. Try again after 2026-01-09T12:00:00Z.",
  "limit": 10,
  "remaining": 0,
  "resetAt": "2026-01-09T12:00:00Z"
}

Ejemplo de Código

JavaScript (Fetch API con Lógica de Reintentos):

const apiRequest = async (url, options = {}, maxRetries = 3) => {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          'Authorization': 'Bearer YOUR_TOKEN',
          'Accept': 'application/json',
          ...options.headers
        }
      });

      // Manejar 429 Too Many Requests
      if (response.status === 429) {
        const error = await response.json();
        console.warn('429 Too Many Requests:', error.message);

        // Obtener tiempo de reintento de los encabezados
        const retryAfter = response.headers.get('Retry-After');
        const ratelimitReset = response.headers.get('X-RateLimit-Reset');

        let delayMs;

        // Retry-After puede ser segundos o fecha HTTP
        if (retryAfter) {
          const retryAfterInt = parseInt(retryAfter);

          if (isNaN(retryAfterInt)) {
            // Formato de fecha HTTP
            const resetDate = new Date(retryAfter);
            delayMs = resetDate.getTime() - Date.now();
          } else {
            // Segundos
            delayMs = retryAfterInt * 1000;
          }
        } else if (ratelimitReset) {
          // Timestamp Unix
          const resetDate = new Date(parseInt(ratelimitReset) * 1000);
          delayMs = resetDate.getTime() - Date.now();
        } else {
          // Fallback: exponential backoff
          delayMs = Math.min(1000 * Math.pow(2, attempt), 60000);
        }

        console.log(`Esperando ${delayMs}ms antes de reintentar (intento ${attempt + 1}/${maxRetries})...`);

        // Esperar antes de reintentar
        await new Promise(resolve => setTimeout(resolve, delayMs));
        continue; // Reintentar
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();

    } catch (error) {
      if (attempt === maxRetries - 1) {
        console.error('Máximo de reintentos alcanzado:', error);
        throw error;
      }

      console.error(`Intento ${attempt + 1} falló:`, error.message);
    }
  }
};

// Ejemplo: Crear usuario con manejo de límite de tasa
const createUser = async (userData) => {
  try {
    const result = await apiRequest('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(userData)
    });

    console.log('Usuario creado:', result);
    return result;

  } catch (error) {
    console.error('Error al crear usuario:', error);
    throw error;
  }
};

// Ejemplo: Verificar encabezados de límite de tasa antes de solicitud
const checkRateLimit = async () => {
  const response = await fetch('https://api.example.com/rate-limit', {
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN'
    }
  });

  const limit = response.headers.get('X-RateLimit-Limit');
  const remaining = response.headers.get('X-RateLimit-Remaining');
  const reset = response.headers.get('X-RateLimit-Reset');

  console.log('Límite de Tasa:', {
    limit,
    remaining,
    resetAt: new Date(parseInt(reset) * 1000).toISOString()
  });

  return {
    limit: parseInt(limit),
    remaining: parseInt(remaining),
    resetAt: new Date(parseInt(reset) * 1000)
  };
};

Python (librería requests con Lógica de Reintentos):

import requests
import time
from datetime import datetime

def api_request(url, method='GET', json=None, max_retries=3):
    headers = {
        'Authorization': 'Bearer YOUR_TOKEN',
        'Accept': 'application/json'
    }

    for attempt in range(max_retries):
        try:
            response = requests.request(
                method,
                url,
                json=json,
                headers=headers
            )

            # Manejar 429 Too Many Requests
            if response.status_code == 429:
                error = response.json()
                print(f'429 Too Many Requests: {error.get("message")}')

                # Obtener tiempo de reintento de los encabezados
                retry_after = response.headers.get('Retry-After')
                ratelimit_reset = response.headers.get('X-RateLimit-Reset')

                delay_seconds = None

                # Retry-After puede ser segundos o fecha HTTP
                if retry_after:
                    try:
                        # Intentar parsear como entero (segundos)
                        delay_seconds = int(retry_after)
                    except ValueError:
                        # Parsear como fecha HTTP
                        reset_date = datetime.strptime(retry_after, '%a, %d %b %Y %H:%M:%S %Z')
                        delay_seconds = (reset_date - datetime.now()).total_seconds()

                elif ratelimit_reset:
                    # Timestamp Unix
                    reset_date = datetime.fromtimestamp(int(ratelimit_reset))
                    delay_seconds = (reset_date - datetime.now()).total_seconds()

                else:
                    # Fallback: exponential backoff
                    delay_seconds = min(2 ** attempt, 60)

                print(f'Esperando {delay_seconds}s antes de reintentar (intento {attempt + 1}/{max_retries})...')

                # Esperar antes de reintentar
                time.sleep(max(delay_seconds, 0))
                continue  # Reintentar

            response.raise_for_status()

            return response.json()

        except requests.exceptions.RequestException as e:
            if attempt == max_retries - 1:
                print(f'Máximo de reintentos alcanzado: {e}')
                raise

            print(f'Intento {attempt + 1} falló: {e}')

# Ejemplo: Crear usuario con manejo de límite de tasa
def create_user(user_data):
    try:
        result = api_request(
            'https://api.example.com/users',
            method='POST',
            json=user_data
        )

        print(f'Usuario creado: {result}')
        return result

    except Exception as error:
        print(f'Error al crear usuario: {error}')
        raise

# Ejemplo: Verificar encabezados de límite de tasa antes de solicitud
def check_rate_limit():
    response = requests.get(
        'https://api.example.com/rate-limit',
        headers={'Authorization': 'Bearer YOUR_TOKEN'}
    )

    limit = response.headers.get('X-RateLimit-Limit')
    remaining = response.headers.get('X-RateLimit-Remaining')
    reset = response.headers.get('X-RateLimit-Reset')

    print('Límite de Tasa:', {
        'limit': limit,
        'remaining': remaining,
        'resetAt': datetime.fromtimestamp(int(reset)).isoformat()
    })

    return {
        'limit': int(limit),
        'remaining': int(remaining),
        'resetAt': datetime.fromtimestamp(int(reset))
    }

Diagrama

sequenceDiagram
    participant Client as Cliente
    participant API
    participant RateLimiter as Limitador de Tasa

    Note over Client,RateLimiter: Solicitudes Iniciales
    Client->>API: Solicitud 1
    API->>RateLimiter: Verificar límite
    RateLimiter-->>API: OK (99 restantes)
    API->>Client: 200 OK
X-RateLimit-Remaining: 99 Client->>API: Solicitud 2 API->>RateLimiter: Verificar límite RateLimiter-->>API: OK (98 restantes) API->>Client: 200 OK
X-RateLimit-Remaining: 98 Note over Client,RateLimiter: ... 97 solicitudes más ... Client->>API: Solicitud 101 API->>RateLimiter: Verificar límite RateLimiter-->>API: LÍMITE EXCEDIDO API->>Client: 429 Too Many Requests
Retry-After: 3600
X-RateLimit-Remaining: 0 Note over Client: Esperar Retry-After Client->>Client: Dormir 3600s Note over RateLimiter: Ventana de límite se reinicia Client->>API: Solicitud (después del reinicio) API->>RateLimiter: Verificar límite RateLimiter-->>API: OK (99 restantes) API->>Client: 200 OK
X-RateLimit-Remaining: 99

Notas de Seguridad

SECURITY NOTES

CRÍTICO - …

Configuración y Validación:

  • Implementar limitación de tasa para prevenir ataques DoS e intentos de fuerza bruta.
  • Incluir siempre el encabezado Retry-After para evitar que clientes saturen la API.
  • Usar límites de tasa diferentes para solicitudes autenticadas vs no autenticadas.
  • Rastrear límites de tasa por usuario/IP/clave API no globalmente.

Monitoreo y Protección:

  • Registrar violaciones de límite de tasa para monitoreo de seguridad.
  • Establecer límites agresivos en endpoints de autenticación para prevenir credential stuffing.
  • Usar 429 en lugar de 503 para limitación de tasa para distinguir de problemas del servidor.
  • Implementar limitación de tasa distribuida en despliegues multi-servidor para prevenir bypass.

Analogía

Piensa en 429 como un restaurante con capacidad limitada:

  • Límite de Tasa → “Solo podemos atender 100 clientes por hora”
  • Respuesta 429 → “Estamos a capacidad. Vuelve en 30 minutos”
  • Retry-After → “Tendremos una mesa disponible a las 19:00”

Sin limitación de tasa, el restaurante (servidor) estaría saturado y no podría atender correctamente a nadie.

Mejores Prácticas

  1. Incluir Siempre Retry-After - Indicar a los clientes cuándo pueden reintentar
  2. Usar Encabezados X-RateLimit-* - Proporcionar información de límite, restantes y reinicio
  3. Diferentes Límites por Endpoint - Más restrictivo para operaciones costosas
  4. Rastrear por Usuario/IP/Clave - No globalmente (limitación de tasa por recurso)
  5. Implementar Exponential Backoff - Los clientes deben retroceder si los reintentos fallan
  6. Registrar Hits de Límite de Tasa - Monitorear patrones de abuso
  7. Documentar Límites de Tasa - Documentar claramente límites en la documentación de la API
  8. Usar 429, no 503 - Distinguir limitación de tasa de errores del servidor

Errores Comunes

  • Sin Encabezado Retry-After - No indicar a los clientes cuándo reintentar
  • Sin Información de Límite de Tasa - No incluir encabezados X-RateLimit-*
  • Limitación de Tasa Global - No rastrear por usuario/IP (permite uso injusto)
  • Mismo Límite en Todas Partes - No ajustar límites según el costo del endpoint
  • Usar 503 en Lugar de 429 - Confundir limitación de tasa con indisponibilidad del servidor
  • No Registrar Violaciones - Perder patrones de abuso y ataques
  • Sin Lógica de Reintentos del Cliente - Clientes no respetando el encabezado Retry-After
  • Ventanas Inconsistentes - Mezclar estrategias de ventana fija y deslizante

Estándares y RFCs

Términos Relacionados