Diil Docs
  1. Documentación
  2. Guías

Límites de peticiones y cómo no toparte con ellos

Actualizado:

La Content API es generosa, pero no es un pozo sin fondo. Unos pocos límites sencillos la mantienen rápida para todos, y una web que cachea con cabeza nunca se los va a encontrar. Piensa en esta página como en las señales de velocidad de una carretera por la que casi siempre vas a circular muy por debajo del límite.

Los límites

Qué se cuentaLímiteQué pasa si lo superas
Peticiones desde una dirección IP300 / minutoEsa IP recibe 429 hasta que acabe el minuto.
Peticiones con una clave de sitio (desde todas las IP juntas)600 / minutoLas peticiones con esa clave reciben 429 hasta que acabe el minuto.
Peticiones con una clave incorrecta desde una IP20 / minutoLa IP queda bloqueada el resto del minuto: incluso las peticiones con una clave válida reciben 429.

Ventanas fijas de un minuto

Los contadores funcionan en ventanas fijas de un minuto, no con una media móvil. Las peticiones van llenando el contador y, cuando acaba el minuto, vuelve a empezar desde cero. Dos consecuencias prácticas:

  • Una ráfaga justo al principio de un minuto no es problema mientras el total de ese minuto se quede por debajo del límite.
  • En cuanto recibes un 429, nada de lo que hagas en ese mismo minuto servirá. Esperar al siguiente, sí.

Qué límite te encontrarás primero

Depende de dónde salgan tus peticiones:

  • Renderizado en el servidor en una sola máquina. Todas las peticiones salen de la IP de tu servidor, así que las 300 por IP son el techo que tocarías primero. Varios servidores tienen cada uno sus propias 300, pero juntos siguen compartiendo las 600 de la clave.
  • Peticiones desde el navegador. Cada visitante tiene su propia IP, así que el límite por IP casi nunca importa. Pero todos los visitantes comparten una misma clave de sitio, y la clave tiene 600 por minuto para todo el sitio. La caché del navegador ayuda a cada visitante por separado, no a la multitud.

Así es un 429

429 Too Many Requestshttp
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Cache-Control: no-store

{"message":"rate_limit_exceeded"}

Eso es todo. No hay cabecera Retry-After ni cabeceras RateLimit-*, así que no las busques. Como las ventanas son minutos fijos, la regla es sencilla: espera más o menos un minuto (con un poco de aleatoriedad, mira más abajo) y vuelve a intentarlo.

Reintentos con backoff y jitter

Un pequeño envoltorio de fetch que hace lo correcto con cada tipo de fallo:

  • 429: espera a que pase la ventana: unos 60 segundos más unos cuantos segundos aleatorios.
  • 5xx y errores de red: backoff exponencial con jitter: más o menos 0,5–1 s, luego 1–2 s, luego 2–4 s.
  • Cualquier otro 4xx: ni un reintento. Esperar no va a arreglar una errata en un marcador.
lib/fetch-with-retry.tsts
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

type RetryOptions = {
  retries?: number;   // cuántos intentos extra
  maxWaitMs?: number; // nunca esperar más que esto; en su lugar, devolver el error
};

export async function fetchWithRetry(
  url: string,
  init: RequestInit = {},
  { retries = 3, maxWaitMs = 70_000 }: RetryOptions = {},
): Promise<Response> {
  for (let attempt = 0; ; attempt++) {
    let res: Response | undefined;
    let error: unknown;

    try {
      res = await fetch(url, init);
      // 2xx, 304 o un 4xx que no se arregla esperando (400, 401, 404…): se devuelve tal cual
      if (res.status !== 429 && res.status < 500) return res;
    } catch (err) {
      error = err; // problemas de red: DNS, timeout, conexión reiniciada
    }

    const wait =
      res?.status === 429
        ? 60_000 + Math.random() * 5_000             // la ventana es de un minuto: la esperamos, más jitter
        : 2 ** attempt * 500 * (1 + Math.random());  // 5xx / red: 0,5–1 s, 1–2 s, 2–4 s…

    if (attempt >= retries || wait > maxWaitMs) {
      if (res) return res; // que quien llama vea el 429 / 5xx
      throw error;
    }
    await sleep(wait);
  }
}

Úsalo con paciencia donde nadie espera, y con prisa donde hay una persona esperando:

Dos formas de llamarlots
const headers = { 'x-crm-key': process.env.CRM_KEY! };
const url = 'https://back.sitecog.com/content/v1/pages/home?lang=en';

// Un script de build o una tarea en segundo plano: encantados de esperar a que pase un 429
const res = await fetchWithRetry(url, { headers });

// Renderizar una página para un visitante: nadie espera un minuto por una página.
// Un 429 vuelve al momento, y sirves la copia que cacheaste antes.
const quick = await fetchWithRetry(url, { headers }, { retries: 1, maxWaitMs: 2_000 });

Las cuentas del SSR: ¿cuántas peticiones hace tu servidor?

Hagamos números. Supongamos que el renderizado típico de una página pide dos cosas: la propia página (/v1/pages/about) y la cabecera y el pie compartidos (/v1/pages/common). Son dos peticiones por visita a página. El sitio tiene 30 páginas en 2 idiomas.

EnfoquePeticiones a la API por minutoDónde se rompe
Sin caché, página + common en cada visita2 × visitas150 visitas / minuto: 2,5 visitantes por segundo; basta con una newsletter
Sin caché, cada bloque pedido por separado (pongamos 20 bloques)20 × visitas15 visitas / minuto: una hora de la comida movidita
revalidate: 60, página + commoncomo máximo 30 × 2 + 1 × 2 = 62nunca: las mismas 62 para 10 visitas o para 100 000

La última fila es la clave de todo: con una caché en el servidor, cada URL única se pide como mucho una vez por minuto, así que tu uso de la API lo limita el tamaño de tu web, no su popularidad. Hacerse viral deja de ser un problema de infraestructura.

Ojo con los builds

La generación estática es una ráfaga por naturaleza: un build que renderiza 400 páginas en 2 idiomas en paralelo puede lanzar 800 peticiones en unos segundos desde una sola máquina, muy por encima de 300 por minuto. Limita la concurrencia a un puñado de páginas a la vez y usa el fetchWithRetry paciente de arriba para que un 429 haga que el build se pare un minuto en lugar de fallar.

Cómo mantenerte bien lejos de los límites

  1. Cachea en tu lado. revalidate en Next.js, ISR o una pequeña caché en memoria en cualquier servidor Node. Las recetas están en la página Caché.
  2. Una petición de página en lugar de muchos bloques. /v1/pages/:marker devuelve todas las secciones y bloques de una vez. Pedir una pizza porción a porción solo le hace gracia al repartidor.
  3. Pide el contenido compartido una sola vez. Los bloques de cabecera y pie de una página common son los mismos en todas las rutas: deja que una sola petición cacheada sirva a todas.
  4. Usa ETags. No reducen el número de peticiones, pero hacen que cada petición repetida salga casi gratis en ancho de banda. Consulta ETag y 304.
  5. No hagas polling. Preguntar a la API cada segundo si algo ha cambiado es exactamente el tipo de tráfico para el que existen los límites. Una caché de 60 segundos da a los editores un ciclo de respuesta suficientemente rápido, y el modo Live se lo da al instante.
  6. Ten las claves en orden. Un despliegue de preview viejo o un cron olvidado con una clave revocada, corriendo en el mismo servidor que producción, puede quemar las 20 peticiones con clave incorrecta en un minuto, y entonces producción en esa IP también recibe 429. Una manzana podrida sí que pudre el cesto.
Comprobación rápida de una clavebash
curl -s -o /dev/null -w "%{http_code}\n" \
  "https://back.sitecog.com/content/v1/langs" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"
# 200: todo bien; 401: arregla la clave antes de que algo empiece a reintentar con ella

¿Te ha llegado un 429 de todos modos y quieres saber qué más puede salir mal? Todos los estados están en la página Errores.