Límites de uso
Neuroon aplica rate limiting por endpoint, no por plan de subscripción. La cuota por plan (productos sincronizados, búsquedas mensuales) es un concepto separado del throttling de tasa que documenta esta página. Ver Cuotas de plan vs rate limit más abajo.
Estado actual del throttling
Hoy el único filtro activo es WidgetRateLimitFilter (/api/widget/*, autenticado con X-Widget-Token). El filtro RateLimitFilter para la API de plugin existe en código pero su matcher (path.contains("/from-plugin") en RateLimitFilter.java) no coincide con ninguna ruta servida por los controladores actuales — los endpoints reales del plugin son /api/plugin/shops/me, /api/plugin/shops/{shopId}/products/sync, etc., sin el sufijo /from-plugin. Mientras eso siga así, la API de plugin no aplica throttling aunque los buckets estén configurados.
Límites en producción — Widget API
| Endpoint interno | Coincide con | Límite | Ventana |
|---|---|---|---|
widget-search | /api/widget/search* | 200 | 1 min |
widget-suggestions | /api/widget/suggestions* | 300 | 1 min |
widget-compare | /api/widget/.../compare* | 10 | 1 min |
widget-analytics | /api/widget/.../analytics* | 500 | 1 min |
Valores en RateLimitService.java. Granularidad: por shop (shopId extraído del X-Widget-Token).
Límites configurados — Plugin API (no enforzados todavía)
Estos valores están en código pero el filtro no se activa hoy. Documentados aquí porque seguramente se activarán en una próxima release; planifica con ellos en mente:
| Endpoint interno | Coincide con | Límite | Ventana |
|---|---|---|---|
sync | /api/plugin/shops/{shopId}/products/sync | 100 | 1 min |
shop-info | /api/plugin/shops/me | 60 | 1 min |
verification | /api/plugin/shops/{shopId}/verification-data | 20 | 1 min |
verify-action (POST) | /api/plugin/shops/{shopId}/verify | 5 | 5 min |
unverify-action (DELETE) | /api/plugin/shops/{shopId}/verify | 5 | 5 min |
Headers de respuesta
| Header | Cuándo se emite | Significado |
|---|---|---|
X-RateLimit-Limit | Toda respuesta de /api/widget/* | Cuota total para este endpoint |
X-RateLimit-Remaining | Toda respuesta de /api/widget/* | Requests restantes en la ventana actual |
Retry-After | Sólo en 429 | Segundos a esperar antes de reintentar (igual al tamaño de ventana) |
Ejemplo de respuesta correcta:
HTTP/1.1 200 OK
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 187
Content-Type: application/json
Cuerpo del error 429
Cuando el bucket se agota, la respuesta es:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
Retry-After: 60
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"retryAfter": 60,
"limit": 200,
"remaining": 0,
"timestamp": "2026-05-06T10:15:00Z"
}
Backoff exponencial
Reintenta sólo en 429 y 5xx. Respeta Retry-After. Patrón recomendado: 1s, 2s, 4s, 8s con jitter, máximo 3-5 intentos.
import pRetry, { AbortError } from 'p-retry';
async function search(query: string) {
return pRetry(async () => {
const res = await fetch('https://api.neuroon.ai/api/widget/search', {
method: 'POST',
headers: {
'X-Widget-Token': process.env.NEUROON_WIDGET_TOKEN!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
});
if (res.status === 429 || res.status >= 500) {
const retryAfter = Number(res.headers.get('Retry-After')) || 0;
if (retryAfter > 0) await new Promise(r => setTimeout(r, retryAfter * 1000));
throw new Error(`Retryable ${res.status}`);
}
if (!res.ok) throw new AbortError(`Non-retryable ${res.status}`);
return res.json();
}, { retries: 3, factor: 2, minTimeout: 1000, randomize: true });
}Cuotas de plan vs rate limit
Son dos capas distintas. No las confundas:
| Concepto | Qué controla | Dónde se ve | Granularidad |
|---|---|---|---|
| Rate limit (esta página) | Picos por minuto/cinco minutos | Headers X-RateLimit-*, error 429 | Por endpoint, igual para todos |
| Cuota de plan | Volumen total mensual y catálogo máximo | GET /api/plugin/shops/me → maxProducts, productsCount, maxSearchesPerMonth, searchesThisMonth | Por shop, depende del plan |
El plan tier (BASIC, GROWTH, PRO, ENTERPRISE) afecta a maxProducts y maxSearchesPerMonth retornados por /shops/me, no a los rate limits por endpoint, que son globales. Si tu integración necesita más volumen mensual, contacta soporte para subir de plan; los rate limits por endpoint no se elevan por plan.
Buenas prácticas
- Cachea
/api/plugin/shops/meunos 5 min en el cliente como hace el plugin WordPress oficial; aunque el filtro de plugin no esté activo, el endpoint es relativamente caro yproductsCount/searchesThisMonthcambian lentamente. - No abuses de
widget-compare(10/min): cachea resultados o agrupa comparaciones. - Sincroniza productos en lotes de hasta 500 (
BATCH_SIZEdel plugin) y espacia las llamadas; el bucket desyncpermitirá 100 req/min cuando el filtro se active. - No reintentes en
400,401,403,404ni422— son errores deterministas, reintentar no los corrige. - Respeta
Retry-Afterestrictamente. Ignorarlo puede llevar a bloqueo manual.
Próximas lecturas
- Errores — estructura completa del resto de errores 4xx/5xx.
- Shop API Key — generación, rotación y validación de Origin.
- Widget Token — TTL 24 h y renovación.