Saltar a contenido

Límites de uso (rate limits)

Algunos endpoints aplican un límite de uso para proteger la estabilidad de la plataforma. Los límites se cuentan por organización (salvo el login, que se cuenta por IP).


Límites por endpoint

Método Endpoint Límite
POST /api/auth/login 5 requests / 5 min por IP
POST /api/campaign/start 5 requests / min por organización
POST /api/campaign/start-upload 5 requests / min por organización
POST /api/campaign/schedule 10 requests / 5 min por organización
GET /api/campaigns 100 requests / min por organización
GET /api/analytics/stats 30 requests / min por organización

Los endpoints que no aparecen en esta tabla no tienen un límite específico documentado. Aun así, haz un uso razonable: evita ráfagas innecesarias y agrupa tus consultas.


No hay headers X-RateLimit-*

La API no devuelve headers de tipo X-RateLimit-Limit, X-RateLimit-Remaining ni X-RateLimit-Reset. No intentes leer el estado del límite desde headers: no existen.

En su lugar, el cliente infiere el límite a partir de la respuesta 429:

  • Cuando superas un límite, la API responde 429.
  • Algunas respuestas 429 incluyen el header Retry-After con la cantidad de segundos que debes esperar antes de reintentar. No todas lo incluyen — trata el header como opcional.

Hoy Retry-After viene en el 429 de estos endpoints:

Método Endpoint
GET /api/campaigns
GET /api/analytics/stats
POST /api/campaign/schedule

En el resto de los endpoints — incluidos POST /api/auth/login, POST /api/campaign/start y POST /api/campaign/start-upload — el 429 no trae Retry-After. Para POST /api/campaign/schedule el header aparece sólo cuando se supera el límite general del endpoint; si lo que se supera es la cuota por organización, el 429 llega sin header.

Diseña tu cliente para que funcione sin el header (ver backoff exponencial abajo) y úsalo como refinamiento cuando esté presente.

El cuerpo del 429 tiene dos formas

La misma división de arriba se ve en el cuerpo: el 429 no tiene un shape único. Escribe tu manejo de errores para tolerar las dos.

Forma A — el 429 que trae Retry-After (los tres endpoints de la tabla anterior, cuando se supera el límite general del endpoint):

HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
  "error": "Too many requests. Please slow down.",
  "error_code": "RATE_LIMITED",
  "retry_after_seconds": 30
}

retry_after_seconds repite en el body el valor del header Retry-After.

Forma B — el 429 que no trae Retry-After (POST /api/auth/login, POST /api/campaign/start, POST /api/campaign/start-upload, y POST /api/campaign/schedule cuando lo que se supera es la cuota por organización). El cuerpo trae sólo error, con un texto descriptivo del límite que tocaste:

{ "error": "Too many campaign starts. Please wait before starting another campaign." }

Con una excepción: el 429 de POST /api/auth/login agrega además "success": false.

Lo único que puedes dar por garantizado en cualquier 429 es el campo error. error_code, retry_after_seconds y success aparecen sólo en algunas formas. Los textos de error son descriptivos y pueden cambiar: no los uses para ramificar lógica — ramifica por el código HTTP 429.

El 429 no incluye error_id ni request_id. Esos campos aparecen en las respuestas 500, no aquí; no los busques en el cuerpo de un rate limit.

Si el 429 trae Retry-After, respétalo. Reintentar antes de ese tiempo volverá a fallar y puede prolongar el bloqueo. Si no lo trae, aplica backoff exponencial (ver abajo).


Recomendación: backoff exponencial

Para integraciones robustas, maneja el 429 con backoff exponencial. El backoff es la base —funciona con o sin header—; usa Retry-After como espera mínima cuando venga:

import time
import requests

API_BASE_URL = "https://api.neuronstudio.ai"
HEADERS = {"X-API-Key": "nrn_live_abcdef1234567890abcdef1234567890"}

def get_with_backoff(path, params=None, max_intentos=5):
    espera = 1
    for intento in range(max_intentos):
        resp = requests.get(f"{API_BASE_URL}{path}", headers=HEADERS, params=params)
        if resp.status_code != 429:
            return resp
        # Respetar Retry-After si viene; si no, usar backoff exponencial.
        retry_after = int(resp.headers.get("Retry-After", espera))
        time.sleep(max(retry_after, espera))
        espera = min(espera * 2, 60)  # tope de 60s
    return resp  # último intento (probablemente sigue siendo 429)

Pautas:

  • Empieza con una espera corta y duplícala en cada reintento, con un tope (ej. 60s).
  • Cuando el 429 traiga Retry-After, úsalo como mínimo: nunca esperes menos que ese valor. Cuando no venga, no asumas un default del servidor — usa tu propio backoff.
  • Agrega un poco de jitter (aleatoriedad) si tienes muchas integraciones concurrentes, para evitar reintentos sincronizados.

Buenas prácticas para no llegar al límite

  • Pagina con cursor en GET /api/analytics/calls en vez de repetir consultas amplias.
  • Espacia el polling: consulta GET /api/analytics/stats o calls cada N minutos, no en bucle cerrado.
  • Reutiliza una sola campaña activa (recuerda: máximo 1 campaña corriendo por organización).
  • Una API Key por integración ayuda a que el consumo de cada sistema sea independiente y más fácil de monitorear.

Ver también: Errores y troubleshooting para el detalle del 429 dentro del catálogo de códigos.