StackPractices
intermediate Por Mathias Paulenko

Plantilla de Política de Rate Limiting de API

Plantilla para definir límites de tasa de API por tier de consumidor, incluyendo márgenes de ráfaga, períodos de cuota y rutas de escalado.

Resumen

El acceso ilimitado a API es una receta para abuso, DDoS accidental y costos impredecibles. El rate limiting protege tu infraestructura mientras da a los consumidores acceso predecible. Sin una política documentada, los consumidores descubren los límites solo cuando sus solicitudes comienzan a fallar con 429 Too Many Requests. Esta plantilla define límites de tasa por tier, los comunica transparentemente y proporciona una ruta de escalado para quienes necesitan más. Documentos relacionados: la guía de rate limiting para los algoritmos detrás de la política, Cómo documentar una API con OpenAPI, Swagger UI y Redoc y Implementar logging y audit trails de API.

Cuándo Usar

Usa este recurso cuando:

  • Lances una API pública o de partners
  • Definas tiers de precios para acceso a API
  • Experimentes picos de tráfico que degraden el servicio para otros
  • Negocies SLAs con clientes enterprise que necesitan límites más altos
  • Elijas entre limitar y encolar — el patrón de throttling cubre el lado de las colas

Sáltalo cuando la API sirve a un único consumidor interno conocido: las cuotas y el backpressure dentro del código ganan a una política pública. Y es prematuro antes de que existan los primeros consumidores reales — escríbela la semana que abres la API, no el mes que empiezas a construirla.

Solución

# Política de Rate Limiting de API

## Tiers y Límites

| Tier | Solicitudes / Minuto | Solicitudes / Hora | Burst | Costo |
|------|----------------------|--------------------|-------|-------|
| Gratis | 60 | 1,000 | 10 | $0 |
| Starter | 300 | 10,000 | 50 | $49/mes |
| Pro | 1,000 | 100,000 | 200 | $199/mes |
| Enterprise | 10,000 | 1,000,000 | 2,000 | Personalizado |

## Alcance del Límite

Los límites se aplican por **API key** en los siguientes alcances:
- **Global:** Todos los endpoints combinados cuentan hacia el mismo límite
- **Por endpoint:** `POST /orders` tiene su propio límite separado de `GET /products`
- **Por IP (solo tier Gratis):** Aplicación de respaldo cuando la API key esta ausente

## Períodos de Cuota

- **Límite por minuto:** Se reinicia al inicio de cada minuto (UTC)
- **Límite por hora:** Se reinicia al inicio de cada hora (UTC)
- **Ventana deslizante:** Una ventana deslizante de 60 segundos (más precisa pero computacionalmente costosa)

## Headers de Respuesta

Cada respuesta de API incluye los siguientes headers:

X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 X-RateLimit-Reset: 1719398400 X-RateLimit-Policy: pro;w=3600


| Header | Descripción |
|--------|-------------|
| `X-RateLimit-Limit` | Máximo de solicitudes permitidas en la ventana actual |
| `X-RateLimit-Remaining` | Solicitudes restantes en la ventana actual |
| `X-RateLimit-Reset` | Timestamp Unix cuando se reinicia la ventana actual |
| `X-RateLimit-Policy` | Identificador de tier y tamaño de ventana |

## Respuesta de Límite Excedido

```json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Has excedido tu límite de tasa. Por favor reintenta despues de 2026-06-26T11:00:00Z.",
    "retryAfter": 3600,
    "documentationUrl": "https://docs.ejemplo.com/rate-limits"
  }
}

Estado HTTP: 429 Too Many Requests Header Requerido: Retry-After: 3600

Comportamiento de Burst

Los límites de burst permiten picos cortos por encima de la tasa sostenida:

  • Tier Pro: 200 solicitudes en 1 segundo, luego limitado a 1,000/hora promedio
  • Algoritmo: Token bucket con tasa de recarga = límite sostenido / tamaño de ventana
  • Penalizacion: Sin penalizacion por uso de burst dentro de los límites configurados

Aumentar Límites

  1. Actualizar tier: Cambia tu plan en el panel de desarrollador
  2. Solicitar excepción: Contactar api-soporte@ejemplo.com con:
    • Patrónes de uso actuales (solicitudes por endpoint, horas pico)
    • Justificacion de negocio (lanzamiento de producto, partner de integracion)
    • Cronograma esperado y volumen
  3. Negociacion enterprise: Capacidad dedicada, SLA personalizado, endpoints privados

Monitoreo y Alertas

AlertaCondiciónAccion
Cercano al Límite80% de cuota horaria consumidaNotificacion por email al admin
Límite ExcedidoRespuestas 429 > 1% del tráficoAlerta PagerDuty al de guardia
Patrón de Abuso10x volumen normal desde una sola keyAuto-throttle + revision manual

Política de Uso Justo

  • Sin evasión automatizada: Usar múltiples API keys para evadir límites viola los términos
  • Cachear agresivamente: Las respuestas marcadas Cache-Control: public deben ser cacheadas
  • Operaciones batch: Usar endpoints bulk en lugar de llamadas individuales
  • Preferir webhooks: Suscribirse a webhooks en lugar de hacer polling por cambios de estado

## Explicacion

Una nota sobre las implementaciónes de abajo: los pipelines de Redis mostrados son por claridad — el read-modify-write no es atómico entre instancias. En producción, corre el token bucket como script Lua `EVAL` sobre Redis (ver la [guía de rate limiting](/guides/api-rate-limiting-guide/) para el script) para que dos instancias no lean el mismo conteo de tokens a la vez.

La política separa los **límites sostenidos** (promedio en el tiempo) de los **límites de burst** (picos a corto plazo). El algoritmo de token bucket es el estándar de la industria porque permite ráfagas mientras impone promedios a largo plazo. Los headers de respuesta dan a los consumidores retroalimentación en tiempo real para que puedan retroceder antes de alcanzar los límites. La ruta de escalado previene tickets de soporte de consumidores que simplemente necesitan un tier más alto.

## Implementación de Token Bucket

El algoritmo de token bucket es el enfoque más común para rate limiting. Aqui hay una implementación basada en Redis:

### Token Bucket en Redis con Node.js

```javascript
const redis = require("redis");

async function rateLimit(redisClient, key, options) {
  const { capacity, refillIntervalSec } = options;
  const now = Date.now();
  const bucketKey = `ratelimit:${key}`;

  const result = await redisClient
    .multi()
    .hGetAll(bucketKey)
    .hSet(bucketKey, {
      tokens: capacity,
      lastRefill: now,
    })
    .expire(bucketKey, refillIntervalSec * 2)
    .exec();

  const bucket = result[0];
  let tokens = parseFloat(bucket.tokens) || capacity;
  let lastRefill = parseInt(bucket.lastRefill) || now;

  const elapsed = (now - lastRefill) / 1000;
  const refillAmount = elapsed * (capacity / refillIntervalSec);
  tokens = Math.min(capacity, tokens + refillAmount);

  if (tokens >= 1) {
    tokens -= 1;
    await redisClient.hSet(bucketKey, {
      tokens: tokens.toString(),
      lastRefill: now.toString(),
    });
    return { allowed: true, remaining: Math.floor(tokens) };
  } else {
    const retryAfter = Math.ceil((1 - tokens) / (capacity / refillIntervalSec));
    await redisClient.hSet(bucketKey, {
      tokens: tokens.toString(),
      lastRefill: now.toString(),
    });
    return { allowed: false, remaining: 0, retryAfter };
  }
}

Middleware en Express.js

const TIERS = {
  free: { capacity: 10, refillRate: 60, refillIntervalSec: 60 },
  starter: { capacity: 50, refillRate: 300, refillIntervalSec: 60 },
  pro: { capacity: 200, refillRate: 1000, refillIntervalSec: 60 },
  enterprise: { capacity: 2000, refillRate: 10000, refillIntervalSec: 60 },
};

async function rateLimitMiddleware(req, res, next) {
  const apiKey = req.headers["x-api-key"];
  const tier = await getTierForApiKey(apiKey);
  const options = TIERS[tier] || TIERS.free;

  const result = await rateLimit(redisClient, apiKey, options);

  res.setHeader("X-RateLimit-Limit", options.refillRate);
  res.setHeader("X-RateLimit-Remaining", result.remaining);
  res.setHeader("X-RateLimit-Policy", `${tier};w=${options.refillIntervalSec}`);

  if (!result.allowed) {
    res.setHeader("Retry-After", result.retryAfter);
    return res.status(429).json({
      error: {
        code: "RATE_LIMIT_EXCEEDED",
        message: "Límite de tasa excedido. Reintenta despues del retraso indicado.",
        retryAfter: result.retryAfter,
      },
    });
  }

  next();
}

Implementación en Python con Redis

import time
import redis

def rate_limit(redis_client, key, capacity, refill_rate, interval_sec):
    bucket_key = f"ratelimit:{key}"
    now = time.time()

    pipe = redis_client.pipeline()
    pipe.hgetall(bucket_key)
    pipe.hset(bucket_key, tokens=capacity, lastRefill=now)
    pipe.expire(bucket_key, interval_sec * 2)
    results = pipe.execute()

    bucket = results[0]
    tokens = float(bucket.get(b"tokens", capacity))
    last_refill = float(bucket.get(b"lastRefill", now))

    elapsed = now - last_refill
    refill_amount = elapsed * (capacity / interval_sec)
    tokens = min(capacity, tokens + refill_amount)

    if tokens >= 1:
        tokens -= 1
        redis_client.hset(bucket_key, tokens=tokens, lastRefill=now)
        return {"allowed": True, "remaining": int(tokens)}
    else:
        retry_after = int((1 - tokens) / (capacity / interval_sec)) + 1
        redis_client.hset(bucket_key, tokens=tokens, lastRefill=now)
        return {"allowed": False, "remaining": 0, "retryAfter": retry_after}

Patrón de Reintento del Lado del Cliente

Los consumidores deberian implementar backoff exponencial con jitter al recibir respuestas 429:

async function fetchWithRetry(url, options, maxRetries = 3) {
  let attempt = 0;
  while (attempt < maxRetries) {
    const response = await fetch(url, options);

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

    const retryAfter = parseInt(response.headers.get("Retry-After") || "1");
    const jitter = Math.random() * 0.5;
    const delay = (retryAfter + jitter) * 1000;

    console.warn(`Limitado. Reintentando en ${delay}ms (intento ${attempt + 1})`);
    await new Promise((resolve) => setTimeout(resolve, delay));
    attempt++;
  }

  throw new Error(`Máximos reintentos (${maxRetries}) excedidos`);
}

Rate Limiting Distribuido

Para despliegues multi-instancia, usa un almacén compartido (Redis, Memcached) en lugar de contadores en memoria:

EnfoqueProsContras
En memoriaMas rápido, sin dependencia externaNo compartido entre instancias
RedisEstado compartido, operaciones atómicasLatencia de red, dependencia de Redis
MemcachedSimple, rápidoSin persistencia, menos flexible
Base de datosPersistente, consultableLento, no apto para alto throughput

Despliegue de la Política

Una política de rate limiting que solo vive en el código es un rumor. Publica el documento antes de aplicarlo: anuncia los límites en el changelog, da a los consumidores existentes una ventana de gracia (dos a cuatro semanas es habitual) y versiona la política — una v1.1 que endurece el tier gratis es un breaking change y merece el mismo tratamiento que un endpoint eliminado. Mantén un historial de revisiones al final: fecha, qué cambió, quién lo aprobó. Cuando un número se mueve, el rastro de auditoría responde “desde cuándo” sin escarbar en el git blame.

Decide quién es dueño de cada campo antes de la primera violación. Los precios de los tiers pertenecen a producto, el enforcement a plataforma, y las aprobaciones de excepciónes a quien realmente pueda subir un límite — escribe el nombre, no el equipo. Una política con “contacta a soporte” para todo genera una cola de tickets; una política con un responsable genera una decisión.

Calculando Tiers desde la Capacidad

Trabaja los números hacia atrás. Supón que el backend sostiene 1.500 RPS y las lecturas superan a las escrituras 9:1. Una escritura cuesta unas cinco lecturas, así que el presupuesto efectivo de escrituras es unos 1.500 / (9 + 5·1) ≈ 107 escrituras por segundo entre todos los consumidores. El tier Pro a 1.000/minuto (≈16/s) más un centenar de keys activas ya consume más de lo que el servicio puede ofrecer — por eso los tiers públicos son techos aspiracionales, no garantías, y por eso existe la columna de ráfaga para absorber la realidad.

Comprueba contra tráfico real antes de públicar: extrae un mes de conteos por key, encuentra al consumidor p95 y coloca el tier de pago justo por encima. El tier gratis debe quedar lo bastante bajo para frenar scrapers pero lo bastante alto para que un desarrollador curioso termine su integración sin chocar contra el muro el primer día — eso suele ser la primera media hora de uso honesto.

Lo que el Consumidor Realmente Ve

El documento que leen los consumidores es más corto que el que escribiste. Lo que sobrevive al contacto: la tabla de tiers, el cuerpo del 429 y los headers en cada respuesta. Si alguno de los tres contradice al resto, el consumidor confía en lo que dicen sus logs — así que la deriva entre docs y enforcement no es un bug de documentación, es un contrato roto. Seguro barato: un job nocturno que golpee un endpoint por tier y compare el X-RateLimit-Limit devuelto contra la tabla públicada. Cuando discrepan, notifica al dueño de la política, no al consumidor.

Decide también cómo descubre su tier el consumidor antes de necesitarlo. Un endpoint GET /v1/me/limits que devuelva el tier del llamante, el uso de la ventana actual y el tiempo de reset ahorra un ticket de soporte por integración confundida — Stripe y GitHub exponen un equivalente, y construirlo cuesta una tarde.

Manejo de Excepciones y Overrides

Los consumidores reales necesitan válvulas de escape reales, y la política debe nombrarlas. Tres pertenecen a todo documento: un flag de override por key que deja a soporte subir el límite de una cuenta por 30 días sin tocar la tabla de tiers (un lanzamiento de producto, un piloto enterprise); un kill switch de emergencia que endurece los límites globalmente cuando el backend arde — la diferencia entre una caída controlada y una incontrolada; y un aumento temporal atado a una fecha, porque una excepción sin caducidad se convierte en el nuevo piso y nadie recuerda revertirla.

Registra cada override: quién pidió, quién aprobó, cuál fue el nuevo número y cuándo vence. Cuando la lista de excepciones supera las cinco keys, la tabla de tiers está mal — corrige la tabla en vez de apilar excepciones.

Comparando Políticas Publicadas

Mirar cómo documentan límites las APIs establecidas ayuda a calibrar la tuya. Stripe oculta la mayoría de números pero publica la forma del 429 y la ruta de escalado; GitHub publica números exactos por minuto por tipo de autenticación más un limitador secundario para requests concurrentes; Cloudflare publica límites por plan en sus docs con la misma tabla que el consumidor ve en su dashboard. El patrón que importa: todas tratan el documento de límites como superficie de producto, no como memo interno — los consumidores lo leen antes de firmar un contrato, y soporte enlaza a él en vez de escribir ensayos.

Dos detalles que vale la pena copiar de esas referencias: cada política publicada separa la válvula por segundo del presupuesto por hora, y cada una documenta qué pasa entre ambas — margen de ráfaga, comportamiento de encolado y la semántica del reset. Copia esa separación en la tuya y la tabla de tiers empieza a responder preguntas en vez de plantearlas. Nota también lo que omiten: ninguna publica los parámetros exactos del algoritmo, porque los números de tuning cambian más rápido que cualquier documento puede seguir. Publica el contrato (números, headers, forma del error, escalado) y mantén las perillas de implementación internas — esa división es la que te permite ajustar el bucket sin renegociar la política.

Cómo Adaptar la Plantilla

No toda API necesita las siete secciones. Una primera API pública puede salir con tiers, headers y el cuerpo del 429 — el monitoreo y la cláusula de uso justo pueden llegar cuando existan consumidores reales. Una API de partners necesita el énfasis opuesto: la ruta de escalado y el lenguaje de SLA suben arriba, porque el documento funciona como anexo de un contrato más que como docs públicas. Y una API de plataforma interna puede quitar las columnas de precio por completo y conservar las secciones de alcance y cuota, que es donde vive el fallo de “un equipo ahoga a los otros”.

Dos campos que los equipos olvidan hasta que alguien pregunta: la fecha efectiva en que cada límite entra en vigor (los consumidores planean integraciones alrededor de ella, y tu propio soporte la necesita para responder “¿por qué esto empezó a fallar de repente?”), y la identidad de la ventana de medición — si X-RateLimit-Reset reporta un borde fijo o una ventana móvil cambia por completo la lógica de reintento del cliente. Pon ambos en la sección de headers, no en notas al pie.

Variantes

ContextoEnfoqueNotas
SaaS PublicoPrecios por tier con tier gratisOrientado a conversion, los límites impulsan upgrades
Plataforma internaCuotas por equipo con pool compartidoPreviene que un equipo ahogue a otros
API de PartnersLímites negociados por contratoDefinidos en acuerdos legales
API GraphQLLímites por complejidad de queryCosto = número de campos, profundidad y peso de resolvers

Lo que funciona

  1. Retornar headers en cada respuesta — no solo cuando los límites están cerca
  2. Usar un algoritmo estándar (token bucket o leaky bucket) — la lógica personalizada confunde a los consumidores
  3. Documentar el comportamiento de reinicio — los consumidores necesitan saber cuando reintentar
  4. Proporcionar endpoints bulk — un POST /orders/bulk es mejor que 100 POST /orders
  5. Monitorear tasas de 429 — tasas altas de 429 indican límites mal configurados o abuso de consumidores
  6. Usar Redis para despliegues distribuidos — los límites en memoria son inexactos con múltiples instancias
  7. Separar límites de lectura y escritura — las escrituras son más costosas y deberían tener límites más estrictos

Errores Comunes

  1. Retornar 403 en lugar de 429 — los consumidores no pueden distinguir falla de auth de rate limiting
  2. No documentar el algoritmo — los consumidores no pueden predecir cuando seran limitados
  3. Límites inconsistentes entre endpoints — misma key, diferentes reglas, confusión del consumidor
  4. Sin allowance de burst — picos de tráfico legítimos son bloqueados
  5. Límites estrictos sin escalado — los clientes enterprise no pueden negociar mayor capacidad
  6. Usar contadores en memoria con múltiples instancias — cada instancia rastrea por separado, permitiendo N x el límite
  7. No retornar el header Retry-After — los consumidores adivinan cuando reintentar, causando thundering herd
  8. Rate limiting solo por IP — NAT y proxies hacen los límites por IP poco confiables para tiers pagos
Mermaid flowchart LR diagram

Lectura Adicional

Preguntas frecuentes

¿Qué pasa si excedo tanto el límite por minuto como por hora?

Gana el límite más restrictivo. Si superas el cap por minuto recibes un 429 al instante, aunque te sobre cuota horaria. Si superas el cap por hora, todo espera al reinicio de la hora.

¿Deberían ser iguales los límites para operaciones de lectura y escritura?

No — las escrituras cuestán más, así que merecen caps más ajustados. La división habitual: GET el más alto, POST/PUT/PATCH medio, DELETE el más bajo.

¿Cómo pruebo mi integración sin chocar con los límites?

Apunta tus tests a un entorno sandbox con límites ampliados o sin límites — la mayoría de proveedores de API ofrecen uno. Alternativamente, simula las respuestas en tu suite y verifica que realmente parseas los headers de rate limit.

¿Debería usar ventana fija o ventana deslizante?

La ventana fija es más barata de correr pero tolera una ráfaga de 2x en el borde — un pico al final de una ventana más otro al inicio de la siguiente. La ventana deslizante cierra ese agujero a costa de más memoria. Ventana fija con margen de ráfaga cubre la mayoría de APIs.

¿Cómo manejo el rate limiting en GraphQL?

Cuenta coste, no requests. Asigna un peso a cada campo según la complejidad del resolver y capa el coste total por query — graphql-cost-analysis y herramientas similares lo aplican de fábrica.

¿Cuál es la diferencia entre rate limiting y throttling?

Rate limiting capa requests por ventana y rechaza el exceso con un 429. Throttling encola el exceso y lo hace esperar. La mayoría de APIs elige lo primero — más simple, y el consumidor recibe una señal clara.

¿Debería limitar requests autenticados y no autenticados de forma distinta?

Sí — los no autenticados reciben caps más bajos (o rechazo directo) porque no puedes atarlos a una cuenta. Los autenticados se mapean al tier del consumidor y se facturan contra él.