Límites de tasa (rate limits)

El límite es por API key, por minuto. Cada respuesta autenticada —incluidas las 4xx de scope o validación— te dice exactamente cuánto te queda y cuándo se reinicia, así que no tienes que adivinar.

Headers en cada respuesta

Mandamos dos formatos juntos para máxima compatibilidad: el trío clásico y el par del draft IETF, más el entorno.

HTTP/1.1 200 OK
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 42
RateLimit: "default";r=118;t=42
RateLimit-Policy: "default";q=120;w=60
AllSign-Environment: live
HeaderQué es
RateLimit-LimitTecho de peticiones en la ventana.
RateLimit-RemainingCuántas te quedan en la ventana actual.
RateLimit-ResetSegundos-delta hasta el reinicio (NO epoch). 42 = "en 42 s".
RateLimitHeader draft IETF combinado: r = remaining, t = segundos al reinicio. El techo viaja en RateLimit-Policy.
RateLimit-PolicyLa política: "default";q=120;w=60 = cuota 120 por ventana de 60 s.
AllSign-Environmentlive, test o dev.

El bucket es por principal (tu API key), no por IP: varias IPs con la misma key comparten el cupo. El contador es in-process, así que entre workers puede irse algo suelto — trata los números como una guía muy cercana, no como una aduana exacta al último dígito.

El 429

Si te pasas del límite recibes 429 RATE_LIMITED (problem+json). Trae el header Retry-After y además el campo retryAfter en el cuerpo — ambos en segundos, y ambos iguales a RateLimit-Reset. No tienes que parsear headers si prefieres leer el body.

HTTP/1.1 429 Too Many Requests
Retry-After: 42
RateLimit-Reset: 42
Content-Type: application/problem+json

{
  "type": "https://developers.allsign.io/errors#RATE_LIMITED",
  "title": "Rate limited",
  "status": 429,
  "code": "RATE_LIMITED",
  "retryAfter": 42,
  "requestId": "req_..."
}

El 429 es seguro de reintentar (idempotency-safe): la petición no se procesó, así que reintentar no duplica nada. Aun así, para POST combina el reintento con tu Idempotency-Key como red de seguridad.

Reintentar con backoff

El patrón correcto: al ver 429, espera lo que diga Retry-After (equivale a RateLimit-Reset) y reintenta; si no viniera, cae a un backoff exponencial.

async function requestWithRetry(doRequest, { maxRetries = 5 } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await doRequest();
    if (res.status !== 429 || attempt >= maxRetries) return res;
    // Confía en el servidor: espera lo que dice Retry-After (== RateLimit-Reset).
    const wait = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
}
import time

def request_with_retry(do_request, max_retries=5):
    for attempt in range(max_retries + 1):
        resp = do_request()
        if resp.status_code != 429 or attempt == max_retries:
            return resp
        wait = int(resp.headers.get("Retry-After", 2 ** attempt))
        time.sleep(wait)

Diferencia con v2

Si vienes de v2, tres cosas cambiaron en los headers de rate limit:

v2v3
X-RateLimit-ResetRateLimit-Reset
Reset = epoch (timestamp Unix)Reset = segundos-delta (cuántos segundos faltan)
Prefijo X- en todosSe cae el prefijo X- (RFC 6648)

Si tu cliente v2 hacía reset - now() para calcular la espera, en v3 el valor ya es la espera en segundos — úsalo directo.