Saltar al contenido principal

Límites de Tasa

Aprende sobre la limitación de tasa de la API y cómo manejar respuestas de límite de tasa.

Resumen

La API de LosCenotes implementa limitación de tasa para asegurar uso justo y mantener calidad de servicio para todos los partners. Los límites de tasa se aplican por clave API y varían por nivel de plan.

Niveles de Límite de Tasa

Entorno Sandbox

Todas las claves API de sandbox tienen límites de tasa mejorados para desarrollo y pruebas:

PlanPeticiones/HoraPeticiones/MinutoLímite de Ráfaga
Básico10,00020050
Pro100,0002,000500
EmpresarialIlimitadoIlimitadoIlimitado

Entorno de Producción

Los límites de tasa de producción son más conservadores para asegurar estabilidad:

PlanPeticiones/HoraPeticiones/MinutoLímite de Ráfaga
Básico1,0002010
Pro10,00020050
EmpresarialPersonalizadoPersonalizadoPersonalizado

Límites Específicos por Endpoint

Algunos endpoints tienen límites adicionales:

Tipo de EndpointLímite AdicionalRazón
POST /reservations100/horaPrevenir spam de reservaciones
POST /webhooks10/minutoConfiguración de webhooks
GET /cenotes/search500/horaOptimización de búsqueda

Headers de Límite de Tasa

Todas las respuestas de la API incluyen headers de límite de tasa:

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642261200
X-RateLimit-Window: 3600
X-RateLimit-Retry-After: 60

Descripción de Headers

HeaderDescripción
X-RateLimit-LimitMáximo de peticiones permitidas en la ventana actual
X-RateLimit-RemainingPeticiones restantes en la ventana actual
X-RateLimit-ResetTimestamp Unix cuando se restablece la ventana
X-RateLimit-WindowTamaño de ventana en segundos
X-RateLimit-Retry-AfterSegundos a esperar antes de la siguiente petición (cuando está limitado)

Respuesta de Límite de Tasa Excedido

Cuando excedas tu límite de tasa, recibirás un código de estado 429:

{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Demasiadas peticiones. Por favor intenta más tarde",
"status": 429,
"details": {
"limit": 1000,
"window": "1 hora",
"resetTime": "2024-01-15T11:00:00Z",
"retryAfter": 60
}
}
}

Manejo de Límites de Tasa

1. Monitorea los Headers de Límite de Tasa

import { LosCenotesClient } from "@loscenotes/partner-sdk";

const client = new LosCenotesClient({
apiKey: "sk_test_your_api_key",
onRateLimit: (headers) => {
console.log(`Límite de tasa: ${headers.remaining}/${headers.limit}`);
console.log(`Restablece en: ${new Date(headers.reset * 1000)}`);

// Alerta cuando se aproxime al límite
if (headers.remaining < 10) {
console.warn("¡Aproximándose al límite de tasa!");
}
},
});

2. Implementa Backoff Exponencial

class RateLimitHandler {
private baseDelay = 1000; // 1 segundo
private maxDelay = 60000; // 1 minuto
private maxRetries = 5;

async executeWithBackoff<T>(
apiCall: () => Promise<T>,
attempt: number = 0
): Promise<T> {
try {
return await apiCall();
} catch (error) {
if (error.status === 429 && attempt < this.maxRetries) {
const delay = Math.min(
this.baseDelay * Math.pow(2, attempt),
this.maxDelay
);

console.log(`Limitado por tasa. Reintentando en ${delay}ms...`);
await this.sleep(delay);

return this.executeWithBackoff(apiCall, attempt + 1);
}

throw error;
}
}

private sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

// Uso
const rateLimitHandler = new RateLimitHandler();

const cenotes = await rateLimitHandler.executeWithBackoff(() =>
client.cenotes.list({ page: 1, perPage: 10 })
);

3. Usa Cola de Peticiones

class RequestQueue {
private queue: Array<() => Promise<any>> = [];
private processing = false;
private requestsPerSecond = 5; // Mantente bajo el límite de tasa
private interval = 1000 / this.requestsPerSecond;

async enqueue<T>(apiCall: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await apiCall();
resolve(result);
} catch (error) {
reject(error);
}
});

this.processQueue();
});
}

private async processQueue(): Promise<void> {
if (this.processing || this.queue.length === 0) {
return;
}

this.processing = true;

while (this.queue.length > 0) {
const request = this.queue.shift();

if (request) {
try {
await request();
} catch (error) {
console.error("Falló la petición:", error);
}

// Espera antes de la siguiente petición
await new Promise((resolve) => setTimeout(resolve, this.interval));
}
}

this.processing = false;
}
}

// Uso
const requestQueue = new RequestQueue();

// Todas las peticiones serán automáticamente encoladas y limitadas por tasa
const cenote1 = await requestQueue.enqueue(() => client.cenotes.get("cenote1"));
const cenote2 = await requestQueue.enqueue(() => client.cenotes.get("cenote2"));
const cenote3 = await requestQueue.enqueue(() => client.cenotes.get("cenote3"));

4. Implementa Patrón Circuit Breaker

enum CircuitBreakerState {
CLOSED = "CLOSED",
OPEN = "OPEN",
HALF_OPEN = "HALF_OPEN",
}

class CircuitBreaker {
private state = CircuitBreakerState.CLOSED;
private failures = 0;
private lastFailureTime = 0;
private threshold = 5; // Abre después de 5 fallas
private timeout = 60000; // 1 minuto de timeout

async execute<T>(apiCall: () => Promise<T>): Promise<T> {
if (this.state === CircuitBreakerState.OPEN) {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = CircuitBreakerState.HALF_OPEN;
} else {
throw new Error("Circuit breaker está ABIERTO");
}
}

try {
const result = await apiCall();

// Éxito - restablece circuit breaker
if (this.state === CircuitBreakerState.HALF_OPEN) {
this.state = CircuitBreakerState.CLOSED;
this.failures = 0;
}

return result;
} catch (error) {
this.failures++;
this.lastFailureTime = Date.now();

if (error.status === 429) {
// Abre circuito en límite de tasa
if (this.failures >= this.threshold) {
this.state = CircuitBreakerState.OPEN;
}
}

throw error;
}
}
}

Mejores Prácticas

1. Cache de Datos Frecuentemente Solicitados

class CachedClient {
private cache = new Map<string, { data: any; expires: number }>();
private cacheTTL = 300000; // 5 minutos

async getCenoteWithCache(cenoteId: string) {
const cacheKey = `cenote:${cenoteId}`;
const cached = this.cache.get(cacheKey);

if (cached && cached.expires > Date.now()) {
return cached.data;
}

// Hacer llamada API solo si no está en cache
const cenote = await client.cenotes.get(cenoteId);

this.cache.set(cacheKey, {
data: cenote,
expires: Date.now() + this.cacheTTL,
});

return cenote;
}
}

2. Agrupa Peticiones Cuando sea Posible

// En lugar de múltiples peticiones individuales
const cenote1 = await client.cenotes.get("id1");
const cenote2 = await client.cenotes.get("id2");
const cenote3 = await client.cenotes.get("id3");

// Usa endpoints batch
const cenotes = await client.cenotes.getBatch(["id1", "id2", "id3"]);

3. Usa Webhooks en lugar de Polling

// En lugar de polling para actualizaciones de estado
setInterval(async () => {
const reservation = await client.reservations.get(reservationId);
if (reservation.status === "confirmed") {
// Maneja confirmación
}
}, 30000); // Cada 30 segundos

// Usa webhooks para actualizaciones en tiempo real
app.post("/webhooks/loscenotes", (req, res) => {
const event = req.body;

if (event.type === "reservation.confirmed") {
// Maneja confirmación inmediatamente
handleReservationConfirmed(event.data);
}

res.status(200).json({ received: true });
});

4. Monitorea el Uso de Límite de Tasa

class RateLimitMonitor {
private metrics = {
requestsLastHour: 0,
requestsLastMinute: 0,
rateLimitHits: 0,
averageResponseTime: 0,
};

recordRequest(responseTime: number, rateLimitHit: boolean = false): void {
this.metrics.requestsLastHour++;
this.metrics.requestsLastMinute++;

if (rateLimitHit) {
this.metrics.rateLimitHits++;
}

// Actualiza tiempo de respuesta promedio
this.metrics.averageResponseTime =
(this.metrics.averageResponseTime + responseTime) / 2;

// Envía métricas al servicio de monitoreo
this.sendMetrics();
}

private sendMetrics(): void {
// Envía a tu servicio de monitoreo (DataDog, New Relic, etc.)
console.log("Métricas de límite de tasa:", this.metrics);
}
}

Soporte de Límite de Tasa en SDK

SDK de TypeScript

import { LosCenotesClient } from "@loscenotes/partner-sdk";

const client = new LosCenotesClient({
apiKey: "sk_test_your_api_key",
rateLimitOptions: {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 30000,
backoffFactor: 2,
},
});

// SDK maneja automáticamente límites de tasa con backoff exponencial
const cenotes = await client.cenotes.list();

SDK de Python

from loscenotes import LosCenotesClient

client = LosCenotesClient(
api_key='sk_test_your_api_key',
rate_limit_options={
'max_retries': 3,
'base_delay': 1.0,
'max_delay': 30.0,
'backoff_factor': 2
}
)

# SDK maneja límites de tasa automáticamente
cenotes = client.cenotes.list()

SDK de PHP

<?php
use LosCenotes\LosCenotesClient;

$client = new LosCenotesClient([
'apiKey' => 'sk_test_your_api_key',
'rateLimitOptions' => [
'maxRetries' => 3,
'baseDelay' => 1000,
'maxDelay' => 30000,
'backoffFactor' => 2
]
]);

// SDK maneja límites de tasa automáticamente
$cenotes = $client->cenotes->getList();
?>

Probando Límites de Tasa

Pruebas de Límite de Tasa en Sandbox

# Prueba límite de tasa en sandbox
for i in {1..50}; do
curl -X GET https://service-gateway.loscenotes.com/v1/test/rate-limit \
-H "Authorization: Bearer sk_test_your_api_key" \
-w "Estado: %{http_code}, Tiempo: %{time_total}s\n"
sleep 0.1
done

Script de Prueba de Carga

async function loadTest(): Promise<void> {
const promises: Promise<any>[] = [];
const startTime = Date.now();

// Envía 100 peticiones concurrentes
for (let i = 0; i < 100; i++) {
promises.push(
client.cenotes
.list({ page: 1, perPage: 1 })
.catch((error) => ({ error: error.status }))
);
}

const results = await Promise.all(promises);
const endTime = Date.now();

const successful = results.filter((r) => !r.error).length;
const rateLimited = results.filter((r) => r.error === 429).length;

console.log(`Prueba de carga completada en ${endTime - startTime}ms`);
console.log(`Peticiones exitosas: ${successful}`);
console.log(`Peticiones limitadas por tasa: ${rateLimited}`);
}

Monitoreo y Alertas

Configura Alertas de Límite de Tasa

function setupRateLimitAlerts() {
const threshold = 0.8; // Alerta al 80% del límite de tasa

client.onResponse((response, headers) => {
const usage = 1 - headers.remaining / headers.limit;

if (usage >= threshold) {
// Envía alerta
console.warn(`Uso de límite de tasa: ${(usage * 100).toFixed(1)}%`);

// Envía al servicio de monitoreo
sendAlert({
type: "RATE_LIMIT_WARNING",
usage: usage,
remaining: headers.remaining,
limit: headers.limit,
resetTime: new Date(headers.reset * 1000),
});
}
});
}

Métricas de Dashboard

Rastrea estas métricas en tu dashboard:

  • Peticiones por hora/minuto
  • Porcentaje de límite de tasa alcanzado
  • Tiempo de respuesta promedio
  • Tasa de éxito
  • Longitud de cola (si usas cola de peticiones)
  • Estado de circuit breaker

Actualizando tu Plan

Si constantemente alcanzas límites de tasa, considera actualizar:

  1. Analiza Patrones de Uso

    • Identifica horas pico de uso
    • Encuentra oportunidades de optimización
    • Calcula capacidad requerida
  2. Optimiza Primero

    • Implementa caching
    • Usa endpoints batch
    • Agrega cola de peticiones
  3. Contacta Ventas

Próximos Pasos