Skip to main content

Rate Limits

Learn about API rate limiting and how to handle rate limit responses.


Overview

LosCenotes API implements rate limiting to ensure fair usage and maintain service quality for all partners.


Rate Limit Tiers

PlanRequests/HourRequests/MinuteBurst Limit
Basic1,0002010
Pro10,00020050
EnterpriseCustomCustomCustom

Endpoint-Specific Limits

EndpointAdditional LimitReason
POST /reservations100/hourPrevent spam
GET /cenotes500/hourSearch optimization

Rate Limit Headers

All API responses include rate limit headers:

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

Header Descriptions

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in current window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when window resets
X-RateLimit-WindowWindow size in seconds
X-RateLimit-Retry-AfterSeconds to wait before next request

Rate Limit Exceeded Response

When you exceed your rate limit, you'll receive a 429 status code:

{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again later",
"status": 429,
"details": {
"limit": 1000,
"window": "1 hour",
"resetTime": "2025-01-15T11:00:00Z",
"retryAfter": 60
}
}
}

Handling Rate Limits

1. Monitor Rate Limit Headers

async function makeApiCall(endpoint) {
const response = await fetch(`https://service-gateway.loscenotes.com${endpoint}`, {
headers: { 'X-API-Key': apiKey }
});

// Check rate limit headers
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
const reset = parseInt(response.headers.get('X-RateLimit-Reset'));

console.log(`Rate limit: ${remaining}/${limit}`);
console.log(`Resets at: ${new Date(reset * 1000).toISOString()}`);

// Alert when approaching limit
if (remaining < 10) {
console.warn('⚠️ Approaching rate limit!');
}

return response.json();
}

2. Implement Exponential Backoff

async function executeWithBackoff(apiCall, maxRetries = 5) {
const baseDelay = 1000; // 1 second
const maxDelay = 60000; // 1 minute

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await apiCall();
const data = await response.json();

if (data.success) {
return data;
}

if (data.error.status === 429) {
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}

throw data.error;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}

throw new Error('Max retries exceeded');
}

// Usage
const data = await executeWithBackoff(() =>
fetch('https://service-gateway.loscenotes.com/partner/cenotes', {
headers: { 'X-API-Key': apiKey }
})
);

3. Use Request Queuing

class RequestQueue {
constructor(requestsPerSecond = 5) {
this.queue = [];
this.processing = false;
this.interval = 1000 / requestsPerSecond;
}

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

this.processQueue();
});
}

async processQueue() {
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('Request failed:', error);
}

// Wait before next request
await new Promise(resolve => setTimeout(resolve, this.interval));
}
}

this.processing = false;
}
}

// Usage
const queue = new RequestQueue(5); // 5 requests per second

const results = await Promise.all([
queue.enqueue(() => fetch(url1)),
queue.enqueue(() => fetch(url2)),
queue.enqueue(() => fetch(url3))
]);

4. Implement Circuit Breaker

class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 60000;
this.failures = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.lastFailureTime = null;
}

async execute(apiCall) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.resetTimeout) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}

try {
const result = await apiCall();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}

onSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}

onFailure() {
this.failures++;
this.lastFailureTime = Date.now();

if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
console.warn('Circuit breaker opened!');
}
}
}

// Usage
const breaker = new CircuitBreaker({ failureThreshold: 5, resetTimeout: 60000 });

try {
const data = await breaker.execute(() =>
fetch('https://service-gateway.loscenotes.com/partner/cenotes')
);
} catch (error) {
if (error.message === 'Circuit breaker is OPEN') {
console.log('Service temporarily unavailable, using cached data');
}
}

Best Practices

✅ Do

  • Monitor rate limit headers on every response
  • Implement exponential backoff for retries
  • Cache responses when possible
  • Use request queuing for bulk operations

❌ Don't

  • Don't ignore rate limit headers
  • Don't retry immediately after rate limit
  • Don't make unnecessary duplicate requests
  • Don't exceed your plan limits

Upgrading Your Plan

If you consistently hit rate limits, consider upgrading:


Next Steps