Skip to main content

Best Practices

Production-ready guidelines for integrating with the LosCenotes Partner API.


Security Best Practices

1. API Key Management

Store Keys Securely

// ✅ Good - Environment variables
const apiKey = process.env.LOSCENOTES_API_KEY;

// ✅ Good - Secure key management service
const apiKey = await keyVault.getSecret('loscenotes-api-key');

// ❌ Bad - Hard-coded keys
const apiKey = 'pk_live_1234567890abcdef';

// ❌ Bad - Client-side exposure
// Never put API keys in frontend code

Rotate Keys Regularly

  1. Generate a new API key
  2. Update your application
  3. Verify the new key works
  4. Revoke the old key

2. Request Security

Always Use HTTPS

// ✅ Always HTTPS
const baseUrl = 'https://service-gateway.loscenotes.com';

// ❌ Never HTTP
const baseUrl = 'http://service-gateway.loscenotes.com';

Validate Input Data

function validateReservationData(data) {
const errors = [];

// UUID validation
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!data.cenoteId || !uuidRegex.test(data.cenoteId)) {
errors.push('Valid cenoteId is required');
}

// Date validation
if (!data.date || new Date(data.date) < new Date()) {
errors.push('Valid future date is required');
}

// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!data.guestEmail || !emailRegex.test(data.guestEmail)) {
errors.push('Valid email is required');
}

// Age breakdown validation
if (!data.ageBreakdown || !data.ageBreakdown.adult) {
errors.push('At least one adult is required');
}

return errors;
}

// Usage
const errors = validateReservationData(reservationData);
if (errors.length > 0) {
console.error('Validation errors:', errors);
return { success: false, errors };
}

Performance Best Practices

1. Caching Strategies

Cache Cenote Data

class CenoteCache {
constructor(ttlMs = 5 * 60 * 1000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttlMs;
}

async getCenotes(apiKey) {
const cacheKey = 'cenotes_list';
const cached = this.cache.get(cacheKey);

if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}

// Fetch fresh data
const response = await fetch(
'https://service-gateway.loscenotes.com/partner/cenotes',
{
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}
}
);

const data = await response.json();

// Cache the result
this.cache.set(cacheKey, {
data: data.data,
timestamp: Date.now()
});

return data.data;
}
}

// Usage
const cache = new CenoteCache();
const cenotes = await cache.getCenotes(apiKey);

2. Request Batching

// ❌ Bad - Many individual requests
for (const cenoteId of cenoteIds) {
const cenote = await fetchCenote(cenoteId);
results.push(cenote);
}

// ✅ Good - Use list endpoint with filters
const cenotes = await fetch(
`https://service-gateway.loscenotes.com/partner/cenotes?ids=${cenoteIds.join(',')}`,
{ headers: { 'X-API-Key': apiKey } }
);

3. Pagination

async function getAllCenotes(apiKey) {
const allCenotes = [];
let currentPage = 1;
let hasMore = true;

while (hasMore) {
const response = await fetch(
`https://service-gateway.loscenotes.com/partner/cenotes?page=${currentPage}&perPage=50`,
{
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}
}
);

const data = await response.json();
allCenotes.push(...data.data);

hasMore = data.pagination.currentPage < data.pagination.lastPage;
currentPage++;
}

return allCenotes;
}

Error Handling

Comprehensive Error Handler

async function apiRequest(endpoint, options = {}) {
const baseUrl = 'https://service-gateway.loscenotes.com';
const apiKey = process.env.LOSCENOTES_API_KEY;

try {
const response = await fetch(`${baseUrl}${endpoint}`, {
...options,
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
...options.headers
}
});

const data = await response.json();

if (!data.success) {
return handleApiError(data.error);
}

return { success: true, data: data.data };

} catch (error) {
return handleNetworkError(error);
}
}

function handleApiError(error) {
const errorMessages = {
'INVALID_API_KEY': 'API key is invalid. Check your credentials.',
'RATE_LIMIT_EXCEEDED': 'Rate limit exceeded. Try again later.',
'RESOURCE_NOT_FOUND': 'Requested resource not found.',
'VALIDATION_ERROR': 'Invalid request data.',
'INSUFFICIENT_CAPACITY': 'Not enough capacity available.'
};

return {
success: false,
error: {
code: error.code,
message: errorMessages[error.code] || error.message,
details: error.details
}
};
}

function handleNetworkError(error) {
if (error.name === 'TypeError') {
return {
success: false,
error: {
code: 'NETWORK_ERROR',
message: 'Network error. Check your internet connection.'
}
};
}

return {
success: false,
error: {
code: 'UNKNOWN_ERROR',
message: error.message
}
};
}

Monitoring

1. Logging

function logApiCall(endpoint, method, startTime, response) {
const duration = Date.now() - startTime;

const logEntry = {
timestamp: new Date().toISOString(),
endpoint,
method,
duration: `${duration}ms`,
success: response.success,
statusCode: response.statusCode,
rateLimit: {
remaining: response.headers?.['x-ratelimit-remaining'],
limit: response.headers?.['x-ratelimit-limit']
}
};

if (!response.success) {
logEntry.error = response.error;
console.error('API Error:', JSON.stringify(logEntry, null, 2));
} else {
console.log('API Call:', JSON.stringify(logEntry));
}
}

2. Health Checks

async function checkApiHealth(apiKey) {
const startTime = Date.now();

try {
const response = await fetch(
'https://service-gateway.loscenotes.com/partner/cenotes?perPage=1',
{
headers: { 'X-API-Key': apiKey }
}
);

const duration = Date.now() - startTime;

return {
healthy: response.ok,
latency: duration,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
healthy: false,
error: error.message,
timestamp: new Date().toISOString()
};
}
}

// Run health check periodically
setInterval(async () => {
const health = await checkApiHealth(process.env.LOSCENOTES_API_KEY);
console.log('API Health:', health);
}, 60000); // Every minute

Production Checklist

Before Going Live

  • API Key: Using production key (pk_live_*)
  • Environment Variables: API key stored in environment variables
  • Error Handling: Comprehensive error handling implemented
  • Rate Limiting: Exponential backoff implemented
  • Logging: API calls are logged
  • Monitoring: Health checks configured
  • Caching: Appropriate caching for static data
  • Validation: Input validation before API calls

Security Checklist

  • API keys never exposed in frontend
  • HTTPS only
  • Input sanitization
  • Rate limit handling
  • Error messages don't leak sensitive info

Next Steps