Rate limits
El límite de llamadas de la API y cómo reaccionar cuando se alcanza.
Esta guía cubre el límite de llamadas de la API: la respuesta que se recibe al superarlo, las cabeceras RateLimit-Limit y RateLimit-Remaining, y cómo esperar antes de volver a intentar.
El límite
La API limita a 120 requests por minuto. El límite no se cuenta por credencial: se cuenta por la dirección de red desde la que la conexión llega al servidor de la aplicación, que detrás de la infraestructura actual es la de un intermediario de red, y no la del cliente. El cupo podría entonces compartirse con otros clientes, y un cliente podría recibir el rechazo sin haber hecho 120 requests propios. Esto no está verificado contra el servidor desplegado, que podría contar por cliente. No asuma un cupo propio de 120 por minuto.
Cómo se cuenta:
- La ventana es fija, de 60 segundos, y empieza con el primer request que la abre. No es una ventana deslizante.
- Los requests rechazados también cuentan. Insistir no adelanta la reapertura: la ventana se reabre a los 60 segundos de haber empezado, insista o no.
- El contador vive en la memoria del proceso de la aplicación y vuelve a cero cuando ese proceso se recicla.
El rechazo: 409, no 429
Cuando se supera el límite, la API responde 409 Conflict, no 429, y no ejecuta el request. El cuerpo es este texto, que no es JSON:
The allowed number of requests has been exceeded.El código de la API lo emite con Content-Type: text/plain; charset=utf-8. Que ninguna capa intermedia cambie ese header no está verificado contra el servidor desplegado, así que lea el cuerpo como texto sin depender del header. Para reconocer el rechazo, basta con el status: en esta API, un 409 siempre es el rate limit.
El 429 que puede devolver la API es otra cosa: es el de CAF_REQUEST_IN_PROGRESS, una solicitud de folios que ya está en curso, y ese sí trae Retry-After. Está en Errores y reintentos.
Las cabeceras
Toda respuesta de la API trae dos cabeceras, sea un éxito o un error:
| Cabecera | Qué dice |
|---|---|
RateLimit-Limit | Requests permitidos por ventana de 60 segundos. Hoy, 120. |
RateLimit-Remaining | Requests que quedan en la ventana. En un 409, 0. |
El 409 no trae Retry-After, ni ninguna cabecera que diga cuándo se reabre la ventana. Los Retry-After que sí manda la API son de otros errores: el 429 de CAF_REQUEST_IN_PROGRESS y, desde la versión 1.1, los errores de negocio reintentables (ver Errores y reintentos).
Tome RateLimit-Remaining como una señal aproximada, no como una cuenta exacta:
- con requests concurrentes, el valor es aproximado;
- si el cupo se comparte con otros clientes, también baja por el tráfico de ellos.
Cómo espaciar los pedidos
- Reparta los pedidos en el tiempo. 120 por minuto son 2 por segundo de promedio. Como el cupo puede ser compartido, quédese bien por debajo y no mande ráfagas.
- Ponga un tope de requests concurrentes. Lo más simple es que su sistema haga un request a la vez por proceso.
- Pida el estado con calma. Para seguir una certificación no hace falta consultar seguido: ver Ciclo de vida de una certificación.
- Baje el ritmo cuando
RateLimit-Remainingse acerque a 0, sin esperar al 409. - Ante un 409, espere al menos 60 segundos antes de reintentar. Reintentar antes no sirve: el rechazo también cuenta y la ventana no se reabre antes. Sume una espera al azar de unos segundos, para que sus procesos no vuelvan todos juntos.
El request rechazado no se ejecutó, así que repetirlo es seguro en cualquier operación, también en las que no son idempotentes. La salvedad es caf_v1_descargar cuando el 409 llega al repetir una descarga cuya respuesta no recibió: lo que no se ejecutó es la repetición, no el intento original, que pudo haber timbrado folios; antes de repetir, siga lo que dice «Cuidado con caf_v1_descargar» en Errores y reintentos.
// Repite el pedido mientras la API responda 409 (rate limit), esperando entre intentos.
const esperar = (ms) => new Promise((listo) => setTimeout(listo, ms));
async function conRateLimit(hacerPedido, intentos = 3) {
for (let intento = 1; intento <= intentos; intento++) {
const respuesta = await hacerPedido();
if (respuesta.status !== 409) return respuesta;
if (intento === intentos) break; // el último 409 no espera: no habrá otro intento
// Sin Retry-After: la ventana es de 60 s. Se suma una espera al azar de hasta 10 s.
await esperar(60_000 + Math.random() * 10_000);
}
throw new Error('La API siguió respondiendo 409 después de ' + intentos + ' intentos');
}Si el 409 sigue después de varias ventanas aunque su sistema mande pocos requests, el cupo probablemente se está compartiendo con tráfico ajeno: mantenga las esperas y contacte a soporte.