# Límites de uso

> Peticiones y tiempo de consulta por minuto, qué pasa al superarlos, las cabeceras que lo cuentan y por qué conviene usar webhooks en vez de consultar en bucle.

Página: https://safeonuba.com/desarrolladores/limites · API de SafeOnuba v1 (`api_version: "2026-09-24"`), en vista previa: el contrato puede cambiar.

Cada cliente tiene dos límites por minuto. Los dos existen para que una integración que consulta de más no quite capacidad a las alertas de nadie, la tuya incluida.

## Los dos límites

| Límite | De serie |
| --- | --- |
| Peticiones por minuto | 600 |
| Tiempo de consulta por minuto | 30 segundos |

El segundo es menos habitual. Cada petición gasta el tiempo que tarda en resolverse, así que **una lista larga cuenta más que una alerta suelta**: pedir 500 alertas de un mes consume más de ese presupuesto que pedir una por su `id`. Si tu integración pide listas grandes a menudo, pagina con `limit` más bajos o, mejor, deja de consultar y usa webhooks.

Los dos se pueden ampliar por contrato.

El endpoint del token (`POST /v1/oauth/token`) tiene además su propio límite, por IP. Pide un token por hora y reutilízalo (ver [Renovarlo](https://safeonuba.com/desarrolladores/autenticacion#renovarlo)).

## Cabeceras

Las respuestas llevan cuánto margen te queda:

| Cabecera | Qué dice |
| --- | --- |
| `X-RateLimit-Limit` | Tu límite. |
| `X-RateLimit-Remaining` | Lo que te queda en la ventana actual. |

## Al superarlos

La API responde `429` en `application/problem+json`, con la cabecera `Retry-After`: los segundos que tienes que esperar antes de volver a intentarlo.

```javascript
async function pedir(url, opciones = {}, intentos = 3) {
  const res = await fetch(url, opciones);
  if (res.status === 429 && intentos > 0) {
    const espera = Number(res.headers.get('Retry-After') ?? 1);
    await new Promise((r) => setTimeout(r, espera * 1000));
    return pedir(url, opciones, intentos - 1);
  }
  return res;
}
```

```python
import time
import requests

def pedir(metodo: str, url: str, intentos: int = 3, **kwargs) -> requests.Response:
    res = requests.request(metodo, url, timeout=10, **kwargs)
    if res.status_code == 429 and intentos > 0:
        time.sleep(float(res.headers.get("Retry-After", "1")))
        return pedir(metodo, url, intentos - 1, **kwargs)
    return res
```

```csharp
async Task<HttpResponseMessage> Pedir(Func<Task<HttpResponseMessage>> peticion, int intentos = 3)
{
    var res = await peticion();
    if ((int)res.StatusCode == 429 && intentos > 0)
    {
        var espera = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);
        await Task.Delay(espera);
        return await Pedir(peticion, intentos - 1);
    }
    return res;
}
```

Respeta siempre `Retry-After`. Reintentar antes solo alarga la espera.

## Webhooks en vez de consultar en bucle

La causa más común de un `429` es preguntar cada pocos segundos «¿hay alertas nuevas?». Es lo que los webhooks hacen mejor: te llega cada alerta en uno o dos segundos, sin gastar ni una petición.

- Para enterarte de lo que pasa: [webhooks](https://safeonuba.com/desarrolladores/webhooks).
- Para ponerte al día tras un corte: [`GET /v1/events`](https://safeonuba.com/desarrolladores/eventos#recuperar-lo-perdido).
- Para cargar el estado al arrancar o buscar algo concreto: las listas, paginadas.
