Copiar página

Límites de tasa (rate limits)

El límite es por API key, en una ventana deslizante de 60 segundos, y lo cuenta un contador que comparten todas las réplicas de la API. Cada respuesta autenticada —incluidas las 4xx de scope o de validación, y hasta la de un cuerpo que no es JSON válido— cuenta, y te dice cuánto te queda y cuánto esperar, 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: 100
RateLimit-Remaining: 98
RateLimit-Reset: 60
RateLimit: "default";r=98;t=60
RateLimit-Policy: "default";q=100;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 (NO epoch): cuánto esperar para tener cupo seguro. Vale lo que dura la ventana de la política (60, o 300 en el enrolamiento de tabletas) y a veces un segundo menos: como la ventana es deslizante, es el techo de la espera, no el instante exacto en que se libera la siguiente petición.
RateLimitHeader draft IETF combinado: el nombre de la política, r = remaining y t = segundos al reinicio. El techo viaja en RateLimit-Policy.
RateLimit-PolicyLa política que te está midiendo: "default";q=100;w=60 = la de tu plan, con cuota de 100 por ventana de 60 s. Las demás políticas están en Endpoints con límite propio.
AllSign-Environmentlive, test o dev.

El bucket es por API key, no por IP: varias IPs o varios workers con la misma key comparten el cupo, y cada key del tenant tiene el suyo. El contador vive en un almacén compartido por todas las réplicas de la API, así que RateLimit-Remaining baja de uno en uno aunque cada petición la atienda una réplica distinta. Si ese almacén deja de responder, cada proceso cuenta por su lado mientras vuelve; por eso la última palabra la tiene el 429 con su Retry-After.

Cuánto te toca

El techo viene de tu plan de suscripción activo y aplica igual a las llaves dev, test y live del mismo tenant.

PlanLímite
Sin plan activo10 req/min
Freemium10 req/min
Esencial · Colabora · Conecta100 req/min
Business150 req/min

Si tu suscripción tiene un api_rpm_override acordado con nosotros, ese valor gana sobre el del plan.

Endpoints con límite propio

Algunas rutas tienen además un tope propio, porque cada llamada cuesta bastante más que una petición normal, o un cupo por IP, porque no llevan API key. Cada uno tiene su nombre de política, y ese nombre es el que ves en RateLimit-Policy del 429 cuando se agota.

EndpointPolíticaCupoPor qué
POST /v3/constanciasconstancias20/min por keyCada llamada es una emisión facturada ante el PSC. Se cuenta aparte y además gasta una petición de tu plan.
POST /v3/constancias/verifyconstancias-verify60/min por keyCada llamada corre los cuatro checks criptográficos de la constancia. También gasta una de tu plan.
POST /v3/documents/{id}/send—250 participantes/min por keySe cuentan participantes invitados, no llamadas: cada uno es un envío. Su 429 se reconoce por X-RateLimit-Reason: invite-fanout.
POST /v3/signing-sessions/{id}/init y GET /v3/signing-sessions/{id}/policysigning-session10/min por IP, compartidos entre las dosSon rutas públicas, sin API key: se limitan por IP y no tocan tu cupo.
POST /v3/devices/enrolldevice-enroll5 cada 5 min por IPFrena que alguien adivine códigos de enrolamiento de un solo uso.
POST /v3/devassist/chatdevassist10/min por IPCada turno dispara una llamada a un modelo de lenguaje. No lleva API key, así que no toca tu cupo.

Una respuesta que pasó trae los RateLimit-* de tu plan (default), también en las constancias; en un 429 traen la política que se agotó. Si RateLimit-Policy nombra algo que no es default, subir de plan no lo cambia.

El 429

Todo 429 de la v3 hecho con una API key válida es application/problem+json con code: "RATE_LIMITED". Cuando se agota un límite —el de tu plan, un tope propio o un cupo por IP— trae el header Retry-After y además el campo retryAfter en el cuerpo, ambos en segundos e iguales a RateLimit-Reset, junto con los RateLimit-* de la política agotada (con RateLimit-Remaining: 0). No tienes que parsear headers si prefieres leer el body.

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 60
RateLimit: "default";r=0;t=60
RateLimit-Policy: "default";q=100;w=60
AllSign-Environment: live

{
  "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED",
  "title": "Rate limited",
  "status": 429,
  "detail": "Rate limit exceeded. Limit: 100 requests per minute.",
  "instance": "/v3/documents",
  "code": "RATE_LIMITED",
  "requestId": "req_...",
  "retryAfter": 60
}

Uno por IP tiene la misma forma; cambian la política y la ventana. Este lo medimos contra la API:

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 300
RateLimit-Limit: 5
RateLimit-Remaining: 0
RateLimit-Reset: 300
RateLimit: "device-enroll";r=0;t=300
RateLimit-Policy: "device-enroll";q=5;w=300

{
  "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED",
  "title": "Rate limited",
  "status": 429,
  "detail": "Rate limit exceeded. Limit: 5 requests per 300 seconds.",
  "instance": "/v3/devices/enroll",
  "code": "RATE_LIMITED",
  "requestId": "req_...",
  "retryAfter": 300
}

El texto de detail cambia con la ventana («per minute», «per 300 seconds»). No lo parsees: decide con code y espera retryAfter.

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

Cómo distinguir cada 429

CasoCómo lo reconocesQué hacer
Límite de tu planRateLimit-Policy: "default" y Retry-After.Espera Retry-After. Si te pasa seguido, baja el ritmo o sube de plan.
Tope propio o cupo por IPRateLimit-Policy con otro nombre (constancias, constancias-verify, signing-session, device-enroll, devassist) y Retry-After.Espera Retry-After. Subir de plan no lo cambia.
Invitaciones de sendX-RateLimit-Reason: invite-fanout, Retry-After y los miembros limit, requested y remaining.Espera Retry-After o invita a menos firmantes por envío. No se cobró ni se invitó a nadie.
Recordatorio a firmanteSin Retry-After ni retryAfter.No lo reintentes: es un recordatorio cada 4 horas por firmante.

En el de invitaciones, los RateLimit-* son los de tu plan y no los del tope de invitaciones, así que RateLimit-Remaining no viene en 0. Lo que dice cuánto esperar es Retry-After:

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60
X-RateLimit-Limit: 250
X-RateLimit-Remaining: 12
X-RateLimit-Reason: invite-fanout
RateLimit-Limit: 100
RateLimit-Remaining: 87
RateLimit-Reset: 60
RateLimit-Policy: "default";q=100;w=60

{
  "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED",
  "title": "Rate limited",
  "status": 429,
  "detail": "Invitation limit reached: 250 participants per minute. This limit counts participants, not calls, and is independent of your plan's request limit.",
  "instance": "/v3/documents/doc_.../send",
  "code": "RATE_LIMITED",
  "requestId": "req_...",
  "limit": 250,
  "requested": 30,
  "remaining": 12,
  "retryAfter": 60
}

El del recordatorio a firmante (POST /v3/documents/{id}/signers/{signerId}/remind) también trae los RateLimit-* de tu plan, pero la hora en que se libera va solo dentro de detail: no trae Retry-After (ni retryAfter, ni un miembro nextAllowedAt). No lo reintentes con backoff: guarda el nextAllowedAt de la respuesta 200 del recordatorio anterior y no vuelvas a recordar antes de esa hora.

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
RateLimit-Limit: 100
RateLimit-Remaining: 91
RateLimit-Reset: 60
RateLimit-Policy: "default";q=100;w=60

{
  "type": "https://allsign.io/developers/docs/errors#RATE_LIMITED",
  "title": "Rate limited",
  "status": 429,
  "detail": "Reminder rate-limited. Next allowed at 2026-10-02T21:30:08.632417+00:00.",
  "instance": "/v3/documents/doc_.../signers/sgr_.../remind",
  "code": "RATE_LIMITED",
  "requestId": "req_..."
}

Sin una API key válida (sin Authorization, con una key revocada o mal copiada) la v3 no te mide por minuto: el 401 no trae RateLimit-*. Esas peticiones sí cuentan contra un tope diario por IP, y su 429 no es problem+json: es un JSON con "error": "daily_ip_limit_exceeded", el header X-RateLimit-Reason: daily-ip y un Retry-After que puede ser de horas. No lo esperes: arregla la credencial. Con una key válida ese tope no aplica.

Reintentar con backoff

El patrón correcto: al ver 429, espera lo que diga Retry-After (equivale a retryAfter y a RateLimit-Reset), súmale un poco de jitter y reintenta. No reintentes antes: el cupo se libera conforme salen de la ventana las peticiones viejas, y mientras tanto solo juntas más 429. El jitter importa si varios workers comparten la key: sin él, todos vuelven en el mismo segundo y se topan otra vez. Si el 429 no trae Retry-After, no reintentes: es el freno de 4 horas del recordatorio a firmante, y un backoff de segundos solo te daría más 429.

async function requestWithRetry(doRequest, { maxRetries = 5 } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await doRequest();
    if (res.status !== 429 || attempt >= maxRetries) return res;
    const retryAfter = Number(res.headers.get("Retry-After"));
    if (!Number.isFinite(retryAfter) || retryAfter <= 0) return res;
    const jitterMs = Math.random() * 1000;
    await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitterMs));
  }
}
import random
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
        retry_after = resp.headers.get("Retry-After")
        if retry_after is None:
            return resp
        time.sleep(int(retry_after) + random.random())

Para no llegar al 429: cuando RateLimit-Remaining llegue a 0, espera RateLimit-Reset segundos antes de la siguiente petición.

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.

En v3 ya no viajan los X-RateLimit-* de v2: una respuesta con tu API key trae solo los RateLimit-* sin prefijo, también en las constancias y en sus 429. Quedan dos rastros, los dos en un 429: el de invitaciones de POST /v3/documents/{id}/send, que trae X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reason: invite-fanout (sin X-RateLimit-Reset); y el tope diario de una petición sin key válida, que trae X-RateLimit-Reason: daily-ip y los X-DailyRateLimit-*, con un X-DailyRateLimit-Reset que es un epoch. Para saber cuánto esperar, lee solo Retry-After (o retryAfter) y los RateLimit-*.