# Límites de uso
URL: https://docs.fluenta.io/docs/rate-limits

Cuántas solicitudes por minuto acepta la API por credencial y por plan, los encabezados X-RateLimit, el 429 y cómo reintentar bien.



La API cuenta las solicitudes de cada credencial en ventanas de 60 segundos.
Al pasar de un presupuesto responde 429 `RATE_LIMITED` y te dice cuánto
esperar.

## Presupuestos [#presupuestos]

Una solicitud se cuenta contra todos los presupuestos que le aplican.

| Presupuesto        | Qué cuenta                                                               | Light | Pro |
| ------------------ | ------------------------------------------------------------------------ | ----- | --- |
| Por organización   | Las solicitudes de una credencial en una organización, en cualquier ruta | 60    | 120 |
| Rutas pesadas      | Esa misma pareja, en las rutas pesadas                                   | 20    | 20  |
| Toda la credencial | Las solicitudes de una credencial en todas sus organizaciones, sumadas   | 300   | 300 |
| Rutas de cuenta    | `getMe`, `getMyUsage` y `listOrganizations`, que no nombran organización | 60    | 60  |

* Los números son solicitudes por ventana de 60 segundos.
* Light o Pro es el plan de la organización de la ruta. Una prueba de Pro y
  una organización exenta cuentan como Pro.
* Una solicitud pesada gasta uno del presupuesto pesado y uno del presupuesto
  por organización.
* Las rutas de cuenta se cuentan aparte de los otros tres presupuestos.
* El límite es por credencial: cada token de API tiene sus propios
  presupuestos.

`getPlan` responde los límites de una organización en `rateLimits`:
`windowSeconds`, `organization`, `heavy`, `credential` y `account`.

## Rutas pesadas [#rutas-pesadas]

Son las que llevan `x-rate-limit-heavy: true` en el documento OpenAPI:

| Operación                                      | Qué lee                                                          |
| ---------------------------------------------- | ---------------------------------------------------------------- |
| `getFloor`                                     | La planta entera en una respuesta                                |
| `getEfficiency`, `getProduction`, `getBilling` | Una cifra sobre un rango de días                                 |
| `getOperatorReport`                            | El reporte de un «operario» sobre un rango                       |
| `getModuleEfficiency`, `getOperatorEfficiency` | La «Eficiencia» de un «módulo» o de un «operario» sobre un rango |
| `getMonth`                                     | El mes                                                           |
| `getPauses`                                    | El reporte de «Pausas»                                           |

Salvo `getFloor`, son rutas de reporte: responden según el plan de la
organización. Mira [Planes](/docs/plans).

## La ventana [#la-ventana]

La ventana es fija. Se abre con la primera solicitud de un presupuesto y
termina 60 segundos después, sin importar cuántas llegaron en medio. Al
terminar, la cuenta vuelve a empezar.

## Encabezados [#encabezados]

Toda respuesta los lleva, también un error:

| Encabezado              | Valor                                                                  |
| ----------------------- | ---------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Solicitudes por ventana del presupuesto que estás más cerca de gastar  |
| `X-RateLimit-Remaining` | Solicitudes que le quedan a ese presupuesto en su ventana. 0 en un 429 |
| `X-RateLimit-Reset`     | Cuándo termina esa ventana, en segundos epoch (UTC)                    |
| `Retry-After`           | Solo en un 429: segundos que debes esperar                             |

`getMe` responde lo mismo en `rateLimit` (`limit`, `remaining`, `reset`) para
el presupuesto de las rutas de cuenta.

## El 429 [#el-429]

```text
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1794499260
Retry-After: 23
```

```json
{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "retryAfterSeconds": 23,
  "message": "Demasiadas solicitudes. Espera 23 segundos antes de volver a intentar."
}
```

`retryAfterSeconds` repite el valor de `Retry-After`. La misma llamada antes
de ese tiempo vuelve a ser rechazada.

## Qué cuenta y qué no [#qué-cuenta-y-qué-no]

* **Los errores también cuentan.** Una solicitud con una credencial válida
  gasta su presupuesto aunque la respuesta sea un 4xx.
* Un 403 `INSUFFICIENT_SCOPE` y un 404 `ORGANIZATION_NOT_FOUND` se cuentan
  contra el presupuesto de las rutas de cuenta, sea cual sea la ruta.
* **Un 429 no cuenta contra nada.** Seguir pidiendo no alarga tu ventana.
* Un 401 `INVALID_API_TOKEN` tampoco cuenta: no hay credencial verificada a
  la cual cargarlo.

## Reintentar bien [#reintentar-bien]

Espera lo que dice `Retry-After` y pon un tope de intentos. Lee el token de
una variable de entorno, no del código.

```js
const BASE = 'https://api.fluenta.io/v1/external';

async function fluenta(path, maxRetries = 5) {
  for (let attempt = 0; ; attempt += 1) {
    const response = await fetch(`${BASE}${path}`, {
      headers: { Authorization: `Bearer ${process.env.FLUENTA_TOKEN}` },
    });

    if (response.status !== 429) return response;
    if (attempt >= maxRetries) throw new Error('Rate limited: retries spent');

    const seconds = Number(response.headers.get('Retry-After')) || 1;
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
  }
}

const response = await fluenta('/organizations/42/floor');
```

```python
import os
import time

import requests

BASE = "https://api.fluenta.io/v1/external"


def fluenta(path, max_retries=5):
    headers = {"Authorization": f"Bearer {os.environ['FLUENTA_TOKEN']}"}

    for attempt in range(max_retries + 1):
        response = requests.get(f"{BASE}{path}", headers=headers, timeout=30)

        if response.status_code != 429:
            return response
        if attempt < max_retries:
            time.sleep(int(response.headers.get("Retry-After", "1")))

    raise RuntimeError("Rate limited: retries spent")


response = fluenta("/organizations/42/floor")
```

Para no llegar al 429, mira `X-RateLimit-Remaining`: cuando llegue a 0, espera
hasta `X-RateLimit-Reset` antes de la siguiente solicitud.

## Ver tu propio uso [#ver-tu-propio-uso]

`getMyUsage` responde lo que tu credencial pidió a la API:

```bash
curl 'https://api.fluenta.io/v1/external/me/usage?days=7' \
  -H 'Authorization: Bearer fluenta_live_xxxxxxxxxxxxxxxx'
```

* `days` va de 1 a 90 y vale 30 si no lo envías. El uso se guarda 90 días.
* `totals` suma el periodo, `endpoints` lo separa por método y ruta, y `days`
  por día.
* Cada uno trae `requests` (rechazos incluidos), `errors` (las respuestas 400
  o más), `averageDurationMs`, `maxDurationMs` y `lastAt`.
* Los días son UTC, no los de la planta.
* Solo cuenta esta credencial, en todas sus organizaciones. Las cifras pueden
  ir unos segundos atrás.
