Límites de Tasa
Entiende los límites de tasa de la API y cómo manejarlos.
Vista General
Los límites de tasa protegen la API del abuso y aseguran disponibilidad justa para todos los usuarios.
Límites Predeterminados
| Tipo de Endpoint | Límite | Ventana |
|---|---|---|
| API General | 100 solicitudes | Por minuto |
| Endpoints de Escritura | 30 solicitudes | Por minuto |
| API Pública | 60 solicitudes | Por minuto |
| Webhooks | 10 solicitudes | Por minuto |
Los límites aplican por clave API o sesión de usuario.
Headers de Respuesta
Cada respuesta de API incluye headers de límite de tasa:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200| Header | Descripción |
|---|---|
X-RateLimit-Limit | Solicitudes máximas por ventana |
X-RateLimit-Remaining | Solicitudes restantes |
X-RateLimit-Reset | Timestamp Unix cuando se resetea el límite |
Respuesta de Límite Excedido
Cuando se excede el límite de tasa:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Límite de tasa excedido. Intenta de nuevo en 60 segundos.",
"details": {
"retryAfter": 60,
"limit": 100,
"remaining": 0,
"resetAt": "2026-01-23T12:00:00Z"
}
}
}Mejores Prácticas
1. Monitorea los Headers
async function fetchWithRateLimit(url: string) {
const response = await fetch(url, {
headers: { 'Authorization': 'Bearer sk_live_xxx' }
});
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
const resetAt = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
if (remaining < 10) {
console.warn(`Límite de tasa bajo: ${remaining} solicitudes restantes`);
}
return response;
}2. Implementa Backoff Exponencial
async function fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(
response.headers.get('Retry-After') || '60'
);
const delay = Math.min(
retryAfter * 1000,
Math.pow(2, attempt) * 1000
);
console.log(`Límite de tasa alcanzado, reintentando en ${delay}ms`);
await sleep(delay);
continue;
}
return response;
}
throw new Error('Máximo de reintentos excedido');
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}3. Agrupa Solicitudes
En lugar de múltiples solicitudes individuales:
// ❌ Malo - Múltiples solicitudes
for (const id of componentIds) {
await fetch(`/api/v1/components/${id}`);
}
// ✅ Bueno - Solicitud única con filtro
const response = await fetch('/api/v1/components?ids=' + componentIds.join(','));4. Usa Webhooks para Actualizaciones
En lugar de polling:
// ❌ Malo - Polling frecuente
setInterval(async () => {
const incidents = await fetch('/api/v1/incidents');
}, 5000);
// ✅ Bueno - Suscripción a webhooks
// Configura un webhook para recibir actualizaciones push5. Cachea Respuestas
const cache = new Map<string, { data: any; expiresAt: number }>();
async function fetchWithCache(url: string, ttlMs = 60000) {
const cached = cache.get(url);
if (cached && cached.expiresAt > Date.now()) {
return cached.data;
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, {
data,
expiresAt: Date.now() + ttlMs
});
return data;
}Límites por Tipo de Recurso
Algunos endpoints tienen límites específicos:
| Recurso | Límite de Creación | Límite de Lectura |
|---|---|---|
| Componentes | 10/min | 100/min |
| Incidentes | 10/min | 100/min |
| Suscriptores | 50/min | 100/min |
| Notificaciones | 100/min | 100/min |
Incremento de Límites
Para límites más altos:
- Plan Enterprise: Límites personalizados disponibles
- Contacta Soporte: Incrementos temporales para migraciones
- Optimiza Uso: Revisa patrones de solicitudes
⚠️
Los abusos repetidos de límites de tasa pueden resultar en bloqueos temporales de IP.
Cola de Solicitudes
Implementa una cola para respetar límites:
class RateLimitedQueue {
private queue: Array<() => Promise<any>> = [];
private processing = false;
private lastRequestTime = 0;
private minInterval = 600; // ms entre solicitudes (100/min)
async add<T>(fn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
});
this.process();
});
}
private async process() {
if (this.processing) return;
this.processing = true;
while (this.queue.length > 0) {
const now = Date.now();
const waitTime = this.minInterval - (now - this.lastRequestTime);
if (waitTime > 0) {
await sleep(waitTime);
}
const fn = this.queue.shift();
if (fn) {
this.lastRequestTime = Date.now();
await fn();
}
}
this.processing = false;
}
}
// Uso
const queue = new RateLimitedQueue();
const results = await Promise.all(
componentIds.map(id =>
queue.add(() => fetch(`/api/v1/components/${id}`))
)
);