If you're building a travel product — flights, hotels, or both — you quickly hit a wall: no single supplier has everything, and none of them agree on how to describe it. One returns fares as XML with segment codes; another gives you JSON with a completely different fare model; a direct hotel feed hands you room rates with its own tax rules. Wiring your app directly to each one is how you end up with a codebase nobody wants to touch.
I've built and run a travel reservation platform that talks to four GDS and supplier integrations end to end. This is the architecture I keep coming back to, and the specific problems that will bite you if you skip a layer.
Start with one rule: your app never speaks GDS
The single most important decision is that nothing above the integration layer knows which provider a result came from. Your search page, your booking flow, your admin panel — they all work with one internal model. Amadeus, Sabre and Travelport live behind an adapter, and the rest of the system never imports their SDKs.
Concretely, you define your own normalised types — a FlightOffer, a HotelOffer, a PriceBreakdown — and every provider adapter is responsible for translating into those types. This is a classic provider abstraction (the adapter pattern), and it's the difference between adding a fifth supplier in a week versus a month.
public interface IFlightProvider
{
string Name { get; }
Task<IReadOnlyList<FlightOffer>> SearchAsync(FlightQuery q, CancellationToken ct);
Task<PricedOffer> PriceAsync(string offerId, CancellationToken ct);
Task<BookingResult> BookAsync(BookRequest r, CancellationToken ct);
}
Every provider implements the same interface. The engine holds a list of them. When you onboard a new supplier, you write one adapter and register it — you don't touch the search or booking code at all.
Search: fan out, but expect stragglers
A search request goes to every relevant provider in parallel, and you merge the results. The mistake people make is waiting for all of them. GDS response times are wildly uneven — one provider answers in 400ms, another takes 6 seconds, and occasionally one just doesn't respond.
So the rule is: fan out, apply a hard per-provider timeout (say 5–8 seconds), and return whatever came back in time. A slow supplier should degrade one row of results, never freeze the page.
Then you normalise and de-duplicate. The same flight often comes back from two GDSes at slightly different prices — you decide the rule (cheapest wins, or preferred supplier wins) and collapse duplicates so the customer sees a clean list.
Caching and rate limits are not optional
GDS calls cost money and are rate-limited. If you hit their live APIs on every keystroke, you'll blow through quotas and your bill. Two things save you:
- Short-lived search cache. Cache search responses for a small window (30–120 seconds) keyed on the route and dates. Repeat searches and pagination hit the cache, not the GDS.
- Rate limiting per provider. Each adapter enforces the supplier's own limit so a burst of traffic doesn't get you throttled or temporarily banned.
The trap here is caching too aggressively. Fares move. Which brings us to the hardest problem in the whole system.
The price-drift problem (and why booking is two steps)
Between the moment a customer sees a fare and the moment they click "book", the price or availability can change. GDS search results are indicative, not a promise. If you book straight off a cached search price, you'll routinely sell fares that no longer exist — and eat the difference.
The fix is a mandatory re-price / confirm step before payment:
- Customer selects an offer.
- You call the provider's price or confirm availability endpoint for that exact offer, live.
- If the price still matches — proceed to payment.
- If it changed — show the new price and make the customer re-confirm before charging them.
Only after that live confirmation do you take payment and issue the booking. This one step prevents the most expensive class of bug in travel software: selling something you can't fulfil at the price you quoted.
Failover: one provider down ≠ platform down
With multiple suppliers you get resilience almost for free — if you design for it. Wrap each provider call in a circuit breaker. If a GDS starts timing out or throwing errors, trip the breaker and stop sending it traffic for a cooldown period. Search simply continues with the remaining providers; the customer never sees an error page, just slightly fewer options.
Log every trip and recovery. When a supplier degrades at 2am, you want the audit trail to show exactly what happened without waking anyone up.
Booking, payment and the refund pipeline
Booking is where money and state meet, so it has to be transactional in spirit even though the GDS itself isn't. The pattern:
- Confirm price live (above).
- Authorise payment.
- Issue the booking with the provider.
- If the provider booking fails after a successful charge, you must automatically void or refund the payment — never leave a customer charged for a booking that didn't happen.
Refunds and cancellations are their own workflow. Each supplier has different rules, penalties and time windows, so cancellation policy lives in the adapter, and the refund itself runs as a background job with retries — GDS refund calls fail transiently and need to be re-tried without a human babysitting them. Automating this end to end is what turns a booking tool into a platform that can actually run 24/7.
The admin panel is half the product
Everything above is invisible to your operations team unless you build for them. An internal admin panel that shows every booking, its provider, its payment and refund state, and a full audit log is not a nice-to-have — it's how support resolves a stuck booking at midnight without reading raw GDS logs. Build it alongside the engine, not after.
Pitfalls I'd flag before you start
- Don't leak provider types upward. The first time a Sabre-specific field shows up in your UI code, the abstraction is broken and it only gets worse.
- Treat every GDS call as unreliable. Timeouts, retries and circuit breakers everywhere — these APIs fail more than internal services do.
- Never book off a search price. Always re-confirm live.
- Make money movements idempotent. A retried booking or refund must not double-charge or double-refund.
- Log for audit from day one. In travel, "what exactly did we send the supplier and what did they say" is a question you will be asked constantly.
Get the abstraction layer and the confirm-before-charge step right, and adding suppliers becomes routine. Get them wrong, and every new integration is a rewrite. The architecture isn't complicated — it's just unforgiving about the details.
Building or fixing a booking engine?
I've built a multi-GDS travel reservation platform end to end — search, booking, refunds and admin. If you're integrating suppliers or your current engine is fragile, let's talk.
Start a project →