# 18 · Ritmo y cuotas

El Gateway acota su tráfico en tres planos distintos, y confundirlos lleva a dimensionar mal: el **ritmo** es
cuántas peticiones por segundo acepta su credencial, la **concurrencia** es cuántas atiende a la vez, y los
**créditos** son cuánto puede gastar en el periodo. Un 429 puede venir de cualquiera de los tres, y el `code`
del cuerpo dice de cuál.

## Ritmo por credencial

La cuenta es **por clave de API**, no por dirección IP: repartir la misma clave entre varias máquinas no
multiplica su cupo, y perderla no le regala tráfico a quien la encuentre.

| Política | Cuota | Ventana |
| --- | --- | --- |
| `burst` | 15 peticiones | 1 segundo |
| `sustained` | 600 peticiones | 60 segundos |

Las dos rigen a la vez, así que un pico de 15 en un segundo es legal pero no sostenible sesenta veces. La
ventana es **deslizante**: el cupo se recupera de forma continua, no de golpe al cambiar de minuto.

## Lea el saldo en la respuesta, no el reloj

Las respuestas **correctas** de `/v1`, y el rechazo por el techo de su clave, publican el estado en dos
cabeceras del borrador IETF `httpapi-ratelimit-headers`, que es el vocabulario al que converge la industria:

```http
RateLimit-Policy: "burst";q=15;w=1, "sustained";q=600;w=60
RateLimit: "burst";r=14;t=1, "sustained";r=587;t=60
```

`q` es la cuota y `w` la ventana en segundos; `r` es lo que le queda y `t` los segundos en que libera la
siguiente unidad. Regularse con `r` es mejor que contar peticiones en su lado: el saldo real ya tiene en cuenta
lo que gastaron sus otras máquinas con la misma clave.

Un `r` positivo no es una reserva. Es el saldo en el instante de esa respuesta, y otra máquina suya puede
consumirlo antes de su siguiente petición.

## Cuando le frenan: hay DOS techos, y no traen lo mismo

Sobre `/v1` actúan dos limitadores distintos. Los dos responden **429** y los dos traen `Retry-After` en
segundos, que es lo único que necesita para reintentar bien. Se diferencian en el `code` y en si hay saldo
que leer:

| `code` | Qué techo es | Cómo se reparte | ¿Trae `RateLimit`? |
| --- | --- | --- | --- |
| `key_rate_limited` | El de **su clave** | Por credencial | **Sí.** `r=0` en la política que le frenó |
| `rate_limited` | El de **la plataforma** | Por dirección IP de origen | No: el cupo no es suyo, es compartido |

Si llama desde **una sola máquina**, lo normal es encontrar primero el techo de la plataforma. El de su clave
aparece cuando reparte la misma credencial entre varias máquinas, que es lo que ese límite existe para acotar.

**Programe contra `Retry-After`, no contra el saldo.** El saldo sirve para no llegar al rechazo; la cabecera
de espera sirve para salir de él, y está en los dos casos. Los otros tres 429 —`concurrency_limit_reached`,
`call_concurrency_limit_reached` y `period_quota_exhausted`— no se arreglan bajando el ritmo, así que mire el `code` antes de reintentar
([página 16](./16-api-errors.md)).

## Concurrencia y cupo del periodo: por cuenta **y por entorno**

Los otros dos planos no se cuentan por clave sino por cuenta, y hay un juego de techos **para cada entorno**:
las peticiones y llamadas en vuelo de `ak_test_` no ocupan las ranuras de `ak_live_`. El dinero, en cambio,
**no** se separa: los dos entornos cobran y gastan del mismo saldo ([página 14](./14-api-quickstart.md)), y
ninguno tiene un techo de créditos propio. Lo que acota el gasto de un bucle de pruebas es el saldo y el tope de
gasto de la cuenta ([página 16](./16-api-errors.md)), no un cupo del entorno.

| Techo | Con el que se abre una cuenta en la beta, en cada entorno | Cuando se llena |
| --- | --- | --- |
| Peticiones simultáneas | **10** | `429 concurrency_limit_reached`, con `Retry-After` |
| Llamadas de voz simultáneas | **5** | `429 call_concurrency_limit_reached`, con `Retry-After` |
| Créditos del periodo | **sin techo**: manda el saldo de la cuenta | — |

Son dos recursos distintos y no un alias: una petición síncrona ocupa su ranura segundos y no cuesta nada
mientras espera, y una llamada ocupa un canal del proveedor y un flujo de audio durante minutos, con coste por
minuto. Con un solo techo, cinco llamadas largas le dejarían sin poder transcribir un audio.

Si a su cuenta le fijamos un techo de créditos por periodo, hay dos rechazos y no son el mismo:
`429 period_quota_exhausted`, con `Retry-After`, cuando lo que queda del periodo no cubre la operación, y
`409 request_exceeds_period_quota`, **sin** `Retry-After`, cuando la operación no cabe ni con el periodo vacío.
Esperar arregla el primero; el segundo solo se arregla subiendo el techo.

El periodo es el **mes natural en UTC**, el mismo con el que se cuenta su saldo, para que un techo no se
reinicie cuando el saldo no.

Los techos se cambian a petición. Si el suyo no coincide con esta tabla, manda el de su cuenta: pregúntenos antes
de dimensionar contra los números de arriba.

> **Una cuenta sin cupo en un entorno no opera en ese entorno**, y no responde `429` sino
> `403 gateway_not_enabled`: la ausencia de techo no significa «sin límite». Es lo que hace que una cuenta
> pueda estar admitida en pruebas y todavía no en producción, que es el estado normal mientras integra.

## Cadencia de sondeo de un trabajo

`GET /v1/jobs/{job_id}` es gratis y no consume ranura de concurrencia, pero sí consume ritmo. Si sigue un
trabajo por sondeo en vez de por webhook, **espere 2 segundos entre consultas** y aumente la espera al doble
tras cada respuesta que siga `queued` o `running`, hasta un techo de 30 segundos. Un bucle sin espera agota la
ventana `burst` de toda su clave en un segundo y frena las peticiones que sí trabajan.

Con receptor de webhooks no hace falta sondear: el `job.succeeded` llega solo
([página 11](./11-webhooks.md)).

## Qué NO cuenta contra su ritmo

El transporte en streaming del asistente mantiene la conexión abierta y no repite peticiones, así que no gasta
ventana mientras emite. Lo que sí la gasta es **abrir** el turno.
