Skip to main content

Best Practices

This guide covers best practices for building robust integrations with Connect Hotels API.

Shopping Flow

1. Always Quote Before Booking

Why: Prices and availability change quickly. Always quote immediately before booking.

// ✅ Good
const search = await searchHotels(criteria);
const option = search.options[0];
const quote = await quoteOption(option.optionRefId); // Quote first
const booking = await bookOption(option.optionRefId, travellerData);

// ❌ Bad
const search = await searchHotels(criteria);
const option = search.options[0];
// Option may expire or price may change
const booking = await bookOption(option.optionRefId, travellerData);

2. Handle Price Changes

const quote = await quoteOption(optionRefId);

if (quote.warnings?.some(w => w.code === 'PRICE_CHANGED')) {
const oldPrice = storedOption.price.net;
const newPrice = quote.price.net;

// Always show price changes to user
const confirmed = await confirmPriceChange({
oldPrice,
newPrice,
message: `Price has changed from ${oldPrice} to ${newPrice}`,
});

if (!confirmed) {
// User declined, search again
return searchHotels(criteria);
}
}

3. Use Appropriate Filters

Narrow down search results to improve performance:

// ✅ Good - Specific filters
{
filters: {
currency: 'EUR',
minPrice: 100,
maxPrice: 200,
boardTypes: ['BB'],
starRating: [4, 5],
},
}

// ❌ Bad - Too broad
{
filters: {
currency: 'EUR',
// No other filters = too many results
},
}

Booking Management

1. Store All References

Always store all booking references for later operations:

const booking = await bookOption(optionRefId, travellerData);

await saveBooking({
bundleportId: booking.booking.id,
clientReference: booking.booking.reference.clientReference,
providerReference: booking.booking.reference.providerReference,
confirmationNumber: booking.booking.reference.confirmationNumber,
status: booking.booking.status,
createdAt: booking.booking.createdAt,
});

2. Handle On Request Bookings

Some bookings require supplier confirmation:

const booking = await bookOption(optionRefId, travellerData);

if (booking.booking.status === 'ON_REQUEST') {
// Inform user
await notifyUser({
message: 'Your booking is being confirmed. You will receive an email when confirmed.',
bookingId: booking.booking.id,
});

// Set up webhook or polling
await monitorBookingStatus(booking.booking.id);
}

3. Validate Guest Information

Ensure guest information matches occupancy:

function validateBooking(booking, occupancies) {
if (booking.rooms.length !== occupancies.length) {
throw new Error('Room count mismatch');
}

booking.rooms.forEach((room, index) => {
const occupancy = occupancies[index];
const totalGuests = room.paxes.length;
const expectedTotal = occupancy.adults + (occupancy.children || 0);

if (totalGuests !== expectedTotal) {
throw new Error(`Room ${index + 1}: Expected ${expectedTotal} guests, got ${totalGuests}`);
}
});
}

Content API

1. Cache Content Data

Content data changes infrequently. Cache it:

// Cache destinations for 24 hours
const destinations = await cache.get('destinations', async () => {
return await getDestinations({ query: {} });
}, { ttl: 24 * 60 * 60 * 1000 });

2. Incremental Sync

Sync only changed content:

// Get audit history
const auditHistory = await getScopeAuditHistory({
contentType: 'hotel',
connectionCode: 'CONN_1',
limit: 100,
});

// Update only changed items
for (const record of auditHistory.auditRecords) {
if (record.action === 'updated') {
await syncHotel(record.contentID);
}
}

3. Use Pagination Tokens Correctly

// ✅ Good - Use tokens correctly
let token = null;
do {
const result = await getHotels({
query: { maxSize: 1000 },
token, // Use token from previous response
});
token = result.hotels.token;
} while (token);

// ❌ Bad - Don't reuse tokens with different queries
const result1 = await getHotels({ query: { maxSize: 100 } });
const result2 = await getHotels({
query: { maxSize: 200 }, // Different query
token: result1.hotels.token, // Wrong token
});

Error Handling

1. Implement Retry Logic

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

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

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

// Retry server errors (5xx)
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
} catch (error) {
if (attempt === maxRetries - 1) throw error;

// Exponential backoff
await sleep(1000 * Math.pow(2, attempt));
}
}
}

2. Handle Warnings

Warnings don't prevent success but indicate data quality issues:

if (result.warnings?.length > 0) {
result.warnings.forEach(warning => {
if (warning.code === 'UNMAPPED_HOTEL') {
// Hotel exists but content not fully mapped
// Still usable, but may have limited information
logWarning('Unmapped hotel', warning);
}
});
}

3. Monitor Tracing Data

Use tracing to optimize supplier selection:

result.tracing.accessSpans.forEach(span => {
if (span.status === 'ERROR') {
logSupplierError(span.access, span.errorCode);
}

if (span.processTime > 5000) {
logSlowSupplier(span.access, span.processTime);
}
});

Performance

1. Reduce API Calls

// ✅ Good - Batch requests
const hotels = await getHotels({
query: {
hotelCodes: ['12345', '67890', '11111'],
maxSize: 100,
},
});

// ❌ Bad - Multiple requests
const hotel1 = await getHotel('12345');
const hotel2 = await getHotel('67890');
const hotel3 = await getHotel('11111');

2. Use Webhooks Instead of Polling

// ✅ Good - Use webhooks
app.post('/webhooks/bookings', async (req, res) => {
const { event, data } = req.body;
await handleBookingEvent(event, data);
res.status(200).send('OK');
});

// ❌ Bad - Polling
setInterval(async () => {
const bookings = await listBookings();
// Check for updates
}, 5000);

3. Monitor Rate Limits

const response = await fetch(url, options);

const remaining = parseInt(response.headers.get('X-RateLimit-Remaining-Minute') || '0');
const limit = parseInt(response.headers.get('X-RateLimit-Limit-Minute') || '0');

if (remaining < limit * 0.1) {
console.warn(`Rate limit warning: ${remaining} requests remaining`);
// Slow down requests
}

Security

1. Never Expose API Keys

// ✅ Good - Server-side only
const apiKey = process.env.BUNDLEPORT_API_KEY;

// ❌ Bad - Client-side
const apiKey = 'pk_live_xxx'; // Never do this!

2. Rotate Keys Regularly

// Implement key rotation
const apiKeys = [
process.env.BUNDLEPORT_API_KEY_PRIMARY,
process.env.BUNDLEPORT_API_KEY_SECONDARY,
];

let currentKeyIndex = 0;

async function makeRequest(url, options) {
const key = apiKeys[currentKeyIndex];
try {
return await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `ApiKey ${key}`,
},
});
} catch (error) {
// Rotate to backup key
if (currentKeyIndex === 0 && apiKeys.length > 1) {
currentKeyIndex = 1;
return makeRequest(url, options);
}
throw error;
}
}

Next Steps