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
| Plan | Requests/Hour | Requests/Minute | Burst Limit |
|---|---|---|---|
| Basic | 1,000 | 20 | 10 |
| Pro | 10,000 | 200 | 50 |
| Enterprise | Custom | Custom | Custom |
Endpoint-Specific Limits
| Endpoint | Additional Limit | Reason |
|---|---|---|
| POST /reservations | 100/hour | Prevent spam |
| GET /cenotes | 500/hour | Search 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
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in current window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when window resets |
X-RateLimit-Window | Window size in seconds |
X-RateLimit-Retry-After | Seconds 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:
- 📧 Contact: partners@loscenotes.com
- 📊 Subject: Rate Limit Upgrade Request