Tu deploy sale el viernes a las 4pm. El monitoring se pone rojo en tres dashboards. Los usuarios están viendo errores 429 en checkout, en login, en cada llamada API que importa. Tu rate limiter está rechazando solicitudes—o peor, una API de terceros te está rechazando a ti, y tu lógica de reintentos está empeorando las cosas. El código de estado HTTP 429 Too Many Requests acaba de convertirse en lo único entre tú y un fin de semana funcionando. La mayoría de desarrolladores saben que 429 significa 've más lento.' Lo que pierden es cuáles solicitudes reintentar, cuándo hacer backoff exponencial, y cómo configurar headers Retry-After que realmente prevengan la espiral de muerte. Aquí está lo que realmente sucede cuando tu API comienza a rechazar tráfico.

He estado en ambos lados de esto. He sido el desarrollador que accidentalmente hizo DDoS a una API de CMS por un proceso de build mal configurado, y he sido el que implementó rate limiting para proteger nuestros propios servidores de clientes descontrolados. Ambas experiencias me enseñaron cosas que los docs no cubren. Caminemos a través de todo.

Tabla de Contenidos

HTTP 429 Too Many Requests: Causas, Fixes, y Rate Limiting

¿Qué Significa Realmente HTTP 429?

HTTP 429 está definido en RFC 6585, publicado en 2012. La especificación es sorprendentemente corta. Aquí está la esencia: el usuario (o cliente) ha enviado demasiadas solicitudes en una cantidad de tiempo dada.

Eso es todo. Es una respuesta de rate limiting. El servidor está diciendo, "Entendí tu solicitud, probablemente es válida, pero necesitas ir más lento."

Esto es diferente de 403 Forbidden (no te lo permito) o 503 Service Unavailable (el servidor entero está teniendo dificultades). Un 429 es dirigido. Se trata de tu tasa de solicitud específicamente.

La respuesta DEBERÍA incluir un header Retry-After indicándole al cliente cuánto tiempo esperar antes de intentar nuevamente. Dije "debería" porque muchas APIs no se molestan, lo que complica la vida de todos.

Dónde Verás 429s en la Naturaleza

  • APIs de terceros: Stripe, OpenAI, GitHub, Contentful, Sanity—todos tienen rate limits
  • CDNs y plataformas de hosting: Vercel, Cloudflare, y AWS devolverán 429s si golpeas sus edge rate limits
  • Tus propias APIs: Si has implementado rate limiting (y deberías haberlo hecho)
  • Procesos de build: Generación de sitios estáticos que golpean una API CMS para cada página puede disparar fácilmente rate limits
  • Web scraping: Si estás obteniendo datos de fuentes externas agresivamente

Causas Comunes de Errores 429

Te desglosaremos los escenarios que realmente he encontrado en producción, clasificados aproximadamente por qué tan frecuentemente surgen.

1. Static Site Builds Golpeando un Headless CMS

Esta es la que más afecta a equipos que trabajan con arquitecturas headless. Tienes un sitio con 2,000 páginas, cada una necesitando datos de tu CMS. Tu proceso de build dispara todas esas solicitudes en paralelo, el CMS ve un pico masivo, y comienza a devolver 429s. Tu build falla.

Vemos esto regularmente al trabajar en proyectos headless CMS. La solución implica encapsulamiento de solicitudes y límites de concurrencia, que cubriré abajo.

2. Caching Faltante o Roto

Si cada carga de página dispara una llamada API fresca porque tu capa de caching no está funcionando, golpearás rate limits rápido—especialmente con picos de tráfico. Una vez debugueé una aplicación Next.js donde revalidate fue accidentalmente fijado a 0, significando que ISR fue efectivamente deshabilitado. Cada visitante disparaba una nueva llamada API a Contentful. Tomó aproximadamente 45 minutos de tráfico real para comenzar a obtener 429s.

3. Bucles de Reintentos sin Backoff

Tu código obtiene un error, reintenta inmediatamente, obtiene otro error, reintenta inmediatamente... felicidades, has construido una máquina disparadora de rate-limit. He visto este patrón en manejadores de webhooks, trabajos en background, e incluso llamadas fetch del lado del cliente.

4. Múltiples Servicios Compartiendo una API Key

Tu ambiente staging, tu ambiente producción, tu setup dev local, y tu pipeline CI/CD están todos usando la misma API key. Cada uno se ve bien individualmente, pero colectivamente están quemando tu presupuesto de rate limit.

5. Fetch del Lado del Cliente sin Debouncing

Una feature de búsqueda-mientras-escribes que dispara una llamada API en cada pulsación. Un dashboard que consulta cada 500ms. Un infinite scroll que dispara fetches más rápido de lo que el usuario puede hacer scroll. Estos patrones pueden absolutamente disparar 429s, especialmente cuando se multiplican en todos tus usuarios.

6. Abuso o Ataque Real

A veces un 429 está haciendo exactamente lo que debería—protegiendo tu servidor de alguien enviando un número irrazonable de solicitudes. Bots, credential stuffing, scraping—rate limiting es tu primera línea de defensa.

El Header Retry-After Explicado

El header Retry-After es la manera del servidor de decirte exactamente cuándo intentar nuevamente. Puede venir en dos formatos:

Segundos para esperar:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

Fecha/hora específica:

HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 01 Jan 2026 00:00:00 GMT

El formato de segundos es mucho más común. El formato de fecha usa HTTP-date como se define en RFC 7231.

Aquí está lo que la mayoría de tutoriales no te dirán: muchas APIs no envían Retry-After en absoluto, o lo envían inconsistentemente. La API de OpenAI generalmente lo incluye. La API de GitHub lo incluye junto con X-RateLimit-Reset. Muchas APIs más pequeñas solo envían un 429 desnudo y te dejan adivinando.

Algunas APIs también envían headers de rate limit adicionales:

Header Propósito Ejemplo
X-RateLimit-Limit Máximo de solicitudes permitidas por ventana 100
X-RateLimit-Remaining Solicitudes restantes en la ventana actual 0
X-RateLimit-Reset Unix timestamp cuando la ventana se reinicia 1735689600
Retry-After Segundos para esperar antes de reintentar 30

Siempre verifica estos headers. Te permiten implementar lógica de reintentos más inteligente e incluso desaceleración proactiva antes de que golpees el límite.

HTTP 429 Too Many Requests: Causas, Fixes, y Rate Limiting - architecture

Cómo Manejar Errores 429 como Cliente

Cuando eres tú quien está recibiendo errores 429, aquí está cómo manejarlos correctamente.

Exponential Backoff con Jitter

Este es el estándar de oro. No solo esperes una cantidad fija de tiempo—aumenta el retraso exponencialmente con cada reintento, y agrega algo de aleatoriedad (jitter) para prevenir problemas de thundering herd.

async function fetchWithRetry(
  url: string,
  options: RequestInit = {},
  maxRetries: number = 5
): Promise<Response> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    if (attempt === maxRetries) {
      throw new Error(`Still getting 429 after ${maxRetries} retries`);
    }

    // Check for Retry-After header first
    const retryAfter = response.headers.get('Retry-After');
    let delay: number;

    if (retryAfter) {
      // Could be seconds or a date
      const parsed = parseInt(retryAfter, 10);
      if (!isNaN(parsed)) {
        delay = parsed * 1000;
      } else {
        delay = new Date(retryAfter).getTime() - Date.now();
      }
    } else {
      // Exponential backoff with jitter
      const baseDelay = Math.pow(2, attempt) * 1000;
      const jitter = Math.random() * 1000;
      delay = baseDelay + jitter;
    }

    console.log(`Rate limited. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }

  // TypeScript wants this, though we'll never reach it
  throw new Error('Unexpected end of retry loop');
}

Request Queuing para Procesos de Build

Para generación de sitios estáticos donde necesitas hacer cientos o miles de llamadas API, usa una cola con control de concurrencia:

import pLimit from 'p-limit';

// Limit to 5 concurrent requests
const limit = pLimit(5);

const pages = await getAllPageSlugs(); // Returns ['/', '/about', '/blog/post-1', ...]

const results = await Promise.all(
  pages.map(slug =>
    limit(() => fetchWithRetry(`https://api.cms.com/pages/${slug}`))
  )
);

La librería p-limit (2.5M+ descargas semanales npm en 2026) es mi ir-a para esto. También puedes agregar un retraso entre solicitudes:

const limit = pLimit(3);

const delay = (ms: number) => new Promise(r => setTimeout(r, ms));

const results = await Promise.all(
  pages.map((slug, i) =>
    limit(async () => {
      if (i > 0) await delay(200); // 200ms between requests
      return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
    })
  )
);

Implementando Rate Limiting en Next.js API Routes

Ahora volvamos al otro lado—estás construyendo una API y necesitas protegerla. Si estás construyendo con Next.js, aquí está cómo agregar rate limiting a tus API routes.

Simple In-Memory Rate Limiter

Para un despliegue de servidor único o durante el desarrollo, esto funciona:

// lib/rate-limit.ts
type RateLimitEntry = {
  count: number;
  resetTime: number;
};

const rateLimitMap = new Map<string, RateLimitEntry>();

export function rateLimit({
  windowMs = 60 * 1000,
  maxRequests = 100,
}: {
  windowMs?: number;
  maxRequests?: number;
} = {}) {
  return function check(identifier: string): {
    allowed: boolean;
    remaining: number;
    resetIn: number;
  } {
    const now = Date.now();
    const entry = rateLimitMap.get(identifier);

    if (!entry || now > entry.resetTime) {
      rateLimitMap.set(identifier, {
        count: 1,
        resetTime: now + windowMs,
      });
      return { allowed: true, remaining: maxRequests - 1, resetIn: windowMs };
    }

    if (entry.count >= maxRequests) {
      return {
        allowed: false,
        remaining: 0,
        resetIn: entry.resetTime - now,
      };
    }

    entry.count++;
    return {
      allowed: true,
      remaining: maxRequests - entry.count,
      resetIn: entry.resetTime - now,
    };
  };
}

Usándolo en una App Router API route de Next.js:

// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';

const limiter = rateLimit({ windowMs: 60_000, maxRequests: 30 });

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
  const { allowed, remaining, resetIn } = limiter(ip);

  if (!allowed) {
    return NextResponse.json(
      { error: 'Too many requests. Please slow down.' },
      {
        status: 429,
        headers: {
          'Retry-After': String(Math.ceil(resetIn / 1000)),
          'X-RateLimit-Limit': '30',
          'X-RateLimit-Remaining': '0',
        },
      }
    );
  }

  // Your actual route logic here
  return NextResponse.json(
    { data: 'Here you go' },
    {
      headers: {
        'X-RateLimit-Limit': '30',
        'X-RateLimit-Remaining': String(remaining),
      },
    }
  );
}

Production Rate Limiting con Upstash Redis

El enfoque en memoria se desmorona cuando estás ejecutando en plataformas serverless como Vercel, porque cada invocación de función podría golpear una instancia diferente. Necesitas un almacén compartido. Upstash Redis es la opción más popular para esto en 2026.

npm install @upstash/ratelimit @upstash/redis
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

export const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '60 s'),
  analytics: true,
  prefix: 'api-ratelimit',
});
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, limit, remaining, reset } = await ratelimit.limit(ip);

  if (!success) {
    const retryAfter = Math.ceil((reset - Date.now()) / 1000);
    return NextResponse.json(
      { error: 'Rate limit exceeded' },
      {
        status: 429,
        headers: {
          'Retry-After': String(retryAfter),
          'X-RateLimit-Limit': String(limit),
          'X-RateLimit-Remaining': '0',
          'X-RateLimit-Reset': String(reset),
        },
      }
    );
  }

  return NextResponse.json({ data: 'Success' }, {
    headers: {
      'X-RateLimit-Limit': String(limit),
      'X-RateLimit-Remaining': String(remaining),
    },
  });
}

El tier gratuito de Upstash te da 10,000 solicitudes/día, que es amplio para proyectos pequeños. Su plan Pro comienza en $10/mes para 500K comandos diarios a partir de principios de 2026.

Middleware-Level Rate Limiting

Si quieres rate limiting en todas tus API routes, el middleware de Next.js es el lugar:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
    const { success, reset } = await ratelimit.limit(ip);

    if (!success) {
      return NextResponse.json(
        { error: 'Too many requests' },
        {
          status: 429,
          headers: {
            'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
          },
        }
      );
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

Estrategias de Rate Limiting Comparadas

No todos los algoritmos de rate limiting son iguales. Aquí está cómo se comparan los principales:

Algoritmo Cómo Funciona Pros Contras Mejor Para
Fixed Window Cuenta solicitudes en ventanas de tiempo fijas (ej. por minuto) Simple de implementar El burst en los límites de ventanas puede permitir 2x el límite APIs simples, herramientas internas
Sliding Window Cuenta solicitudes sobre un período de tiempo rodante Distribución más suave Ligeramente más complejo, más memoria La mayoría de APIs de producción
Token Bucket Los tokens se rellenan a una tasa constante, cada solicitud cuesta un token Permite bursts controlados Gestión de estado más compleja APIs que necesitan tolerancia de burst
Leaky Bucket Las solicitudes entran en una cola y se procesan a una tasa fija Tasa de salida muy suave Puede agregar latencia, las solicitudes pueden ser descartadas Entrega de webhook, procesamiento de trabajo
Sliding Window Log Almacena timestamp de cada solicitud Más preciso Alto uso de memoria a escala Necesidades de bajo volumen, alta precisión

Para la mayoría de aplicaciones web, sliding window es el punto dulce. Es lo que Upstash usa por defecto, y es lo que recomendaría a menos que tengas una razón específica para elegir otra cosa.

Rate Limiting en Astro y Otros Frameworks

Si estás construyendo con Astro, el rate limiting funciona diferente porque Astro es principalmente un framework static-first. Pero con los server endpoints de Astro (disponibles en modo SSR), los conceptos son los mismos:

// src/pages/api/data.ts
import type { APIRoute } from 'astro';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '60 s'),
});

export const GET: APIRoute = async ({ request }) => {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, reset } = await ratelimit.limit(ip);

  if (!success) {
    return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
      status: 429,
      headers: {
        'Content-Type': 'application/json',
        'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
      },
    });
  }

  return new Response(JSON.stringify({ data: 'Hello' }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

Para aplicaciones desplegadas en el edge en Cloudflare Workers, también podrías considerar las reglas de Rate Limiting incorporadas de Cloudflare, que operan a nivel de infraestructura y pueden manejar mucho más tráfico que soluciones a nivel de aplicación. Su Advanced Rate Limiting comienza en $0.05 por 10,000 solicitudes buenas en el plan Business.

Monitoreo y Debug de Errores 429 en Producción

No puedes arreglar lo que no puedes ver. Aquí está mi checklist para tratar con errores 429 en producción:

Cuando Estás Recibiendo 429s

  1. Verifica cuál API está devolviendo 429—Mira la URL de respuesta, no solo el código de estado
  2. Registra el header Retry-After—Si consistentemente es muy largo, podrías necesitar un plan de tier más alto
  3. Audita tus patrones de solicitud—¿Estás haciendo llamadas redundantes? ¿Puedes hacer batch de solicitudes?
  4. Implementa caching—Usa stale-while-revalidate, caching Redis, o ISR de Next.js para reducir llamadas API
  5. Verifica si múltiples ambientes comparten API keys—Esta es la causa de misterio 429 más común

Cuando Estás Enviando 429s

  1. Configura dashboards—Rastrea tasas de respuesta 429 a lo largo del tiempo
  2. Identifica los principales infractores—¿Cuáles direcciones IP o API keys están golpeando límites más?
  3. Revisa tus límites—¿Son demasiado restrictivos? ¿Demasiado laxos? Verifica tu capacidad de servidor y ajusta
  4. Siempre envía Retry-After—Sé un buen ciudadano API
  5. Incluye un mensaje de error útil—Dile al cliente cuál límite golpearon y cuándo reintentar

Una respuesta 429 bien elaborada se ve así:

{
  "error": {
    "type": "rate_limit_exceeded",
    "message": "You've exceeded 30 requests per minute. Please wait before retrying.",
    "retryAfter": 42,
    "documentation": "https://docs.yourapi.com/rate-limits"
  }
}

Esto es infinitamente más útil que solo { "error": "Too many requests" }.

Si estás lidiando con problemas persistentes de rate limiting en una arquitectura headless—ya sea durante builds, en runtime, o ambos—podría valer la pena ponerse en contacto para discutir tu arquitectura. Hemos visto muchos de estos problemas en diferentes combinaciones de CMS y framework y generalmente hay una solución a nivel de patrón en lugar de solo parchear los síntomas.

FAQ

¿Qué significa HTTP 429 Too Many Requests?

HTTP 429 es un código de estado que significa que has enviado demasiadas solicitudes a un servidor dentro de un período de tiempo dado. El servidor te está haciendo rate limiting—te está pidiendo que vayas más lento. No es un error de autenticación o un error del servidor; tus solicitudes probablemente sean válidas, solo hay demasiadas. El servidor debería incluir un header Retry-After diciéndote cuándo intentar nuevamente.

¿Cómo arreglo un error 429?

Si estás recibiendo errores 429 de una API, implementa exponential backoff con jitter en tu lógica de reintentos, reduce tu frecuencia de solicitudes, agrega caching para evitar llamadas redundantes, y respeta el header Retry-After. Si estás golpeando el límite durante builds, usa encapsulamiento de solicitudes con límites de concurrencia. Si está sucediendo consistentemente, podrías necesitar actualizar a un plan API más alto con límites de tasa más generosos.

¿Qué es el header Retry-After?

El header Retry-After es enviado con una respuesta 429 (o 503) para decirle al cliente cuánto tiempo esperar antes de hacer otra solicitud. Puede ser especificado como un número de segundos (ej. Retry-After: 60) o como una fecha HTTP (ej. Retry-After: Thu, 01 Jan 2026 00:00:00 GMT). No todas las APIs incluyen este header, pero las bien diseñadas lo hacen.

¿Cómo implemento rate limiting en Next.js?

Para desarrollo o despliegues de servidor único, puedes usar un Map en memoria para rastrear conteos de solicitudes por dirección IP. Para despliegues serverless de producción en plataformas como Vercel, usa Upstash Redis con el paquete @upstash/ratelimit. Puedes aplicar rate limiting a nivel de ruta individual o en todas tus API routes usando Next.js middleware.

¿Cuál es la diferencia entre errores 429 y 503?

Un 429 Too Many Requests es específicamente sobre rate limiting—tu cliente está enviando demasiadas solicitudes. Un 503 Service Unavailable significa que el servidor está sobrecargado o en mantenimiento y no puede manejar ninguna solicitud de nadie. Ambos pueden incluir un header Retry-After, pero indican problemas muy diferentes. Un 429 está dirigido a ti; un 503 afecta a todos.

¿Puede el rate limiting prevenir ataques DDoS?

El rate limiting es una capa de defensa contra ataques DDoS, pero no es suficiente por sí solo. El rate limiting a nivel de aplicación (como lo que implementarías en Next.js) puede manejar abuso moderado, pero un ataque DDoS serio necesita ser mitigado a nivel de infraestructura—usando servicios como Cloudflare, AWS Shield, o las protecciones incorporadas de tu proveedor de hosting. Piensa en el rate limiting a nivel de app como un portero, y en la protección a nivel de infraestructura como los muros de la fortaleza.

¿Qué rate limit debería establecer para mi API?

Depende completamente de tu caso de uso. Un punto de partida común para APIs públicas es 60 solicitudes por minuto por IP, o 1,000 solicitudes por hora por API key. Para usuarios autenticados, podrías permitir más. La clave es monitorear patrones de uso real, establecer límites que acomoden uso legítimo con algo de margen, y ajustar basado en datos reales. Comienza más restrictivo y afloja—es más fácil que endurecerlos después de que los usuarios dependan de tasas más altas.

¿Por qué estoy obteniendo errores 429 durante mi build de sitio estático?

Los generadores de sitios estáticos como Next.js y Astro obtienen datos para cada página en tiempo de build. Si tienes cientos o miles de páginas, eso es cientos o miles de llamadas API en rápida sucesión. La mayoría de APIs CMS tienen límites de tasa entre 5-20 solicitudes por segundo. Usa p-limit o librerías similares para limitar concurrencia a 3-5 solicitudes simultáneas, agrega pequeños retrasos entre lotes, y considera usar builds incrementales (ISR en Next.js, o colecciones de contenido incremental de Astro) para evitar reconstruir todo de una vez.