Skip to main content

Error Handling

This guide explains how to handle errors from the Bundleport API, including error codes, retry strategies, and best practices.

Supplier tracing and sandbox flags​

When debugging integration issues with support or your connectivity team:

  • settings.auditTransactions: set to true to include supplier-level trace data in auditData on successful responses (useful to see raw protocol exchanges). Do not store or log full audit payloads in production systems without redaction; never commit them to source control.
  • settings.testMode: set to true in sandbox when supported so downstream handling treats traffic as non-production.
  • tracing.accessSpans: on search and similar calls, inspect per-connection status (success, timeout, error) when one supplier fails and others succeed—partial failures often return 200 with warnings rather than a top-level error.

See Sandbox & testing for the full sandbox checklist.

Error Response Format​

All API errors follow a consistent format:

{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
"field": "Additional context about the error"
}
}
}

HTTP Status Codes​

Status CodeMeaningAction
200SuccessProcess the response normally
400Bad RequestFix the request payload and retry
401UnauthorizedCheck your API key
403ForbiddenVerify API key scopes
404Not FoundCheck the endpoint URL and resource ID
429Too Many RequestsImplement exponential backoff and retry
500Internal Server ErrorRetry with exponential backoff
502, 503, 504Gateway/Service UnavailableRetry with exponential backoff

Common Error Codes​

Authentication Errors​

UNAUTHORIZED​

Invalid or missing API key.

{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}

Solution: Verify your Authorization header format: ApiKey YOUR_KEY

FORBIDDEN​

API key doesn't have required permissions.

{
"error": {
"code": "FORBIDDEN",
"message": "API key does not have required scope: hotels:book"
}
}

Solution: Update your API key scopes in the dashboard

Validation Errors​

VALIDATION_ERROR​

Request payload doesn't match the API schema.

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request payload",
"details": {
"field": "stay.checkIn",
"reason": "Date must be in YYYY-MM-DD format"
}
}
}

Solution: Fix the validation errors and retry

INVALID_DESTINATION​

Destination code is invalid or not supported.

{
"error": {
"code": "INVALID_DESTINATION",
"message": "Destination code 'XXX' is not valid"
}
}

Solution: Use valid destination codes (IATA codes for airports, city codes, etc.)

Business Logic Errors​

NO_AVAILABILITY​

No hotels available for the search criteria.

{
"error": {
"code": "NO_AVAILABILITY",
"message": "No hotels found matching your criteria"
}
}

Solution: Adjust search criteria (dates, destination, filters)

PRICE_CHANGED​

Price changed between quote and booking.

{
"error": {
"code": "PRICE_CHANGED",
"message": "Price has changed. Please recheck availability.",
"details": {
"originalPrice": 150.00,
"newPrice": 165.00
}
}
}

Solution: Recheck availability and present updated price to user

BOOKING_FAILED​

Booking could not be completed.

{
"error": {
"code": "BOOKING_FAILED",
"message": "Booking failed: Room no longer available",
"details": {
"provider": "SUPPLIER_1",
"reason": "INVENTORY_EXHAUSTED"
}
}
}

Solution: Search again and try a different option

Rate Limiting​

RATE_LIMIT_EXCEEDED​

Too many requests in a short period.

{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds."
}
}

Solution: Implement exponential backoff (see below)

Retry Strategies​

Exponential Backoff​

For transient errors (5xx, 429), implement exponential backoff:

async function makeRequestWithRetry(url, options, maxRetries = 3) {
let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);

// Success
if (response.ok) {
return await response.json();
}

// Rate limited - check Retry-After header
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
await sleep(retryAfter * 1000);
continue;
}

// Client errors (4xx) - don't retry
if (response.status >= 400 && response.status < 500) {
const error = await response.json();
throw new Error(error.error?.message || 'Client error');
}

// Server errors (5xx) - retry
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}

} catch (error) {
lastError = error;

// Calculate backoff delay: 2^attempt seconds
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await sleep(delay);
}
}

throw lastError;
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Retry Logic by Error Type​

Error TypeRetry?Strategy
400 Bad Request❌ NoFix the request
401 Unauthorized❌ NoCheck API key
403 Forbidden❌ NoUpdate scopes
404 Not Found❌ NoCheck resource ID
429 Rate Limited✅ YesExponential backoff with Retry-After
500 Internal Error✅ YesExponential backoff (3-5 retries)
502/503/504 Gateway✅ YesExponential backoff (3-5 retries)

Handling Warnings​

Some responses include warnings that don't prevent the request from succeeding:

{
"options": [...],
"warnings": [
{
"code": "UNMAPPED_HOTEL",
"message": "Hotel code '12345' not found in catalog",
"severity": "INFO"
}
]
}

Best Practice: Log warnings for monitoring but don't treat them as errors.

Error Handling Best Practices​

1. Always Check Response Status​

const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || 'Request failed');
}

2. Log Errors for Debugging​

try {
const data = await makeRequest(url, options);
} catch (error) {
console.error('API Error:', {
url,
status: error.status,
code: error.code,
message: error.message,
timestamp: new Date().toISOString(),
});
// Send to error tracking service (Sentry, etc.)
throw error;
}

3. Provide User-Friendly Messages​

function getUserFriendlyMessage(error) {
const errorMessages = {
'UNAUTHORIZED': 'Please check your API credentials',
'NO_AVAILABILITY': 'No hotels found. Try different dates or destination.',
'PRICE_CHANGED': 'Price updated. Please review and confirm.',
'BOOKING_FAILED': 'Booking unavailable. Please try another option.',
};

return errorMessages[error.code] || error.message || 'An error occurred';
}

4. Implement Circuit Breaker Pattern​

For production systems, consider a circuit breaker to prevent cascading failures:

class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}

async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}

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

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

onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}

Next Steps​