Error Handling, Rate Limits & Best Practices
A production-grade integration should handle API errors gracefully, respect rate limits, and follow caching and performance best practices. This article covers all three topics to help you build a reliable, performant integration.
Error Response Format
When an API call fails, the response body contains a structured error:
{
"message": "Description of the error",
"status": 400
}Some error responses include additional detail:
{
"error": {
"message": "Detailed error description",
"status": 400
}
}*
HTTP Status Codes
| Status | Meaning | Common Causes | Action |
|---|---|---|---|
200 | Success | Request completed normally. | Process the response data. |
400 | Bad Request | Missing required parameters, invalid date format, invalid coordinates, malformed request body. | Validate your request data before sending. Check required fields and data types. |
401 | Unauthorized | Missing, expired, or invalid API signature. | Generate a new signature and retry the request. |
404 | Not Found | Invalid property ID, booking ID, or endpoint path. | Verify the ID exists and the endpoint URL is correct. |
429 | Too Many Requests | Rate limit exceeded. | Back off and retry after the window resets. See Rate Limits below. |
500 | Internal Server Error | Unexpected server-side issue. | Retry after a brief delay. If persistent, contact support. |
503 | Service Unavailable | API is temporarily unavailable (maintenance, overload). | Retry with exponential backoff. |
*
Error Handling by Endpoint
Authentication Errors (401)
The most common cause is an expired signature. Implement auto-refresh logic (see Authentication & API Signatures) and retry the failed request with the new signature.
async function apiCall(method, endpoint, data, correlationId) { let signature = await signatureManager.getSignature();
try { return await makeRequest(method, endpoint, data, signature, correlationId); } catch (error) { if (error.status === 401) { // Signature may have expired — force refresh and retry once signature = await signatureManager.forceRefresh(); return await makeRequest(method, endpoint, data, signature, correlationId); } throw error; }}
Search Errors (No Results)
A hotel search may return zero results without an error status. This isn't an API failure — it means no properties matched your criteria. Handle this by:
- Suggesting nearby locations or broader date ranges.
- Relaxing filters (e.g., remove star rating or price constraints).
- Displaying a friendly "no results" message.
Availability Errors
Rooms may become unavailable between the search and the availability check. If the availability response returns an empty array:
- Inform the user that the room is no longer available.
- Suggest re-searching or checking a different hotel.
Pricing Errors
If the pricing confirmation fails or the token is invalid:
- Re-run the availability check to get a fresh availability token.
- Confirm the price again to get a new pricing token.
- Notify the guest if the price has changed.
Booking Errors
Booking failures can occur due to expired pricing tokens, sold-out rooms, or invalid guest data. Always:
- Validate guest information client-side before submitting.
- Check pricing token freshness (10-minute window) before booking.
- Display a clear error message and offer the option to retry.
*
Rate Limits
The Xeni Hotels API enforces rate limits to ensure fair usage and system stability.
| Scope | Limit | Window |
|---|---|---|
| General API calls | 100 requests | 15 minutes |
| Chat / message endpoints | 20 requests | 5 minutes |
Rate Limit Headers
Rate limit information is included in response headers:
| Header | Description |
|---|---|
RateLimit-Limit | Maximum number of requests allowed in the window. |
RateLimit-Remaining | Number of requests remaining in the current window. |
RateLimit-Reset | Timestamp (seconds) when the rate limit window resets. |
Handling 429 Responses
When you receive a 429 response:
- Stop making requests immediately.
- Wait until the rate limit window resets (check the
RateLimit-Resetheader). - Resume with normal request patterns.
Do not retry rate-limited requests in a tight loop — this will extend the cooldown period.
*
Caching Recommendations
Intelligent caching reduces API calls, improves response times, and stays within rate limits. Here are recommended cache durations by data type:
| Data Type | Recommended TTL | Rationale |
|---|---|---|
| Location autocomplete results | 1 hour | Locations rarely change. Safe to cache aggressively. |
| Hotel details (property info) | 1 hour | Hotel descriptions, amenities, and photos don't change frequently. |
| Hotel search results | 30 minutes | Prices and availability shift over time. Cache for short-term reuse. |
| Room availability | 5 minutes | Highly volatile. Rooms sell out and prices change rapidly. |
| Pricing confirmations | Do not cache | Each pricing confirmation is unique and token-specific. Always call the API fresh. |
Cache Key Strategy
Generate unique cache keys from the request parameters. A common approach:
// JavaScriptimport crypto from 'crypto';
function generateCacheKey(prefix, params) { const hash = crypto.createHash('md5') .update(JSON.stringify(params)) .digest('hex'); return cache:${prefix}:${hash};}
// Examples:generateCacheKey('location', { query: 'miami' });// → "cache:location:3f8c9a2b..."
generateCacheKey('search', { lat: 25.76, long: -80.19, checkIn: '2025-06-01', checkOut: '2025-06-05' });// → "cache:search:7d2e1f4a..."
*
Timeout Configuration
Set appropriate request timeouts for each API call. Our recommendations:
| Endpoint | Recommended Timeout |
|---|---|
| Signature generation | 10 seconds |
| Location autocomplete | 10 seconds |
| Hotel search | 30 seconds |
| Hotel details | 15 seconds |
| Availability check | 30 seconds |
| Price confirmation | 15 seconds |
| Booking creation | 30 seconds |
Hotel search and availability checks may take longer because they query multiple suppliers in real time.
*
Retry Strategy
Not all errors are permanent. Use a retry strategy with exponential backoff for transient failures:
| Error Type | Retry? | Strategy |
|---|---|---|
401 Unauthorized | Yes (once) | Refresh signature, then retry. |
429 Rate Limited | Yes (after wait) | Wait for the reset window, then retry. |
500 Server Error | Yes (up to 3) | Exponential backoff: wait 1s, 2s, 4s. |
503 Unavailable | Yes (up to 3) | Exponential backoff: wait 2s, 4s, 8s. |
400 Bad Request | No | Fix the request. Retrying with the same data will produce the same error. |
404 Not Found | No | Verify the resource ID. Retrying won't help. |
Example: Retry with Exponential Backoff
async function apiCallWithRetry(fn, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { const status = error.response?.status;
// Don't retry client errors (except 401 and 429) if (status && status >= 400 && status < 500 && status !== 401 && status !== 429) { throw error; }
if (attempt === maxRetries) throw error;
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); } }}
*
Production Readiness Checklist
Before going live, verify your integration meets these criteria:
| Item | Details | |
|---|---|---|
| ☐ | Signature auto-refresh | Signatures refresh before the 30-minute expiry window. |
| ☐ | Correlation ID management | Captured from first response and forwarded on all subsequent calls. Replaced on new location searches. |
| ☐ | Token lifecycle handling | Availability tokens are used for pricing only. Pricing tokens (10-minute TTL) are used for booking. Expiry is checked before use. |
| ☐ | Error handling | All HTTP error codes are handled. 401 triggers signature refresh. 429 triggers backoff. |
| ☐ | Rate limit compliance | Application respects rate limits and doesn't retry 429s aggressively. |
| ☐ | Caching | Locations and hotel details are cached. Availability is cached briefly or not at all. |
| ☐ | Timeouts | All API calls have appropriate timeouts configured. |
| ☐ | Credential security | API key and secret are stored in environment variables, not in source code. |
| ☐ | Environment configuration | UAT and production base URLs are configurable, not hard-coded. |
| ☐ | Graceful degradation | If caching layer (Redis) is unavailable, the application falls back to direct API calls. |
| ☐ | Logging | API calls, errors, and key events are logged for debugging and monitoring. |
| ☐ | Health checks | Application exposes a health endpoint that verifies connectivity to dependencies (Redis, Xeni API signature status). |
*
Getting Help
If you encounter issues that aren't resolved by this documentation:
- Check the error message: The API returns descriptive error messages that usually point to the root cause.
- Verify in UAT first: Test your request against the UAT environment to isolate the issue.
- Contact support: Reach out to your Xeni account representative with the following details:
- The full API request (endpoint, headers, body)
- The full API response (status code, headers, body)
- The correlation ID from the session
- The timestamp of the request