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| Header | Qué es |
|---|---|
RateLimit-Limit | Techo de peticiones en la ventana. |
RateLimit-Remaining | Cuántas te quedan en la ventana actual. |
RateLimit-Reset | Segundos-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. |
RateLimit | Header draft IETF combinado: el nombre de la política, r = remaining y t = segundos al reinicio. El techo viaja en RateLimit-Policy. |
RateLimit-Policy | La 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-Environment | live, test o dev. |
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.
| Plan | Límite |
|---|---|
| Sin plan activo | 10 req/min |
| Freemium | 10 req/min |
| Esencial · Colabora · Conecta | 100 req/min |
| Business | 150 req/min |
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.
| Endpoint | Política | Cupo | Por qué |
|---|---|---|---|
POST /v3/constancias | constancias | 20/min por key | Cada 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/verify | constancias-verify | 60/min por key | Cada 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 key | Se 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}/policy | signing-session | 10/min por IP, compartidos entre las dos | Son rutas públicas, sin API key: se limitan por IP y no tocan tu cupo. |
POST /v3/devices/enroll | device-enroll | 5 cada 5 min por IP | Frena que alguien adivine códigos de enrolamiento de un solo uso. |
POST /v3/devassist/chat | devassist | 10/min por IP | Cada turno dispara una llamada a un modelo de lenguaje. No lleva API key, así que no toca tu cupo. |
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 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
| Caso | Cómo lo reconoces | Qué hacer |
|---|---|---|
| Límite de tu plan | RateLimit-Policy: "default" y Retry-After. | Espera Retry-After. Si te pasa seguido, baja el ritmo o sube de plan. |
| Tope propio o cupo por IP | RateLimit-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 send | X-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 firmante | Sin 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())Diferencia con v2
Si vienes de v2, tres cosas cambiaron en los headers de rate limit:
| v2 | v3 |
|---|---|
X-RateLimit-Reset | RateLimit-Reset |
Reset = epoch (timestamp Unix) | Reset = segundos-delta (cuántos segundos faltan) |
Prefijo X- en todos | Se cae el prefijo X- (RFC 6648) |
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-*.