<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sunnybadgujar]]></title><description><![CDATA[Sunnybadgujar]]></description><link>https://sunnybadgujar.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Sunnybadgujar</title><link>https://sunnybadgujar.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 08:57:44 GMT</lastBuildDate><atom:link href="https://sunnybadgujar.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Integrate the Sabre API into a Travel Portal]]></title><description><![CDATA[Originally published at sunnybadgujar.com.
Sabre is the GDS behind a large share of North American air bookings, and its APIs can power a serious travel portal. But it doesn't work like Amadeus Self-S]]></description><link>https://sunnybadgujar.hashnode.dev/how-to-integrate-the-sabre-api-into-a-travel-portal</link><guid isPermaLink="true">https://sunnybadgujar.hashnode.dev/how-to-integrate-the-sabre-api-into-a-travel-portal</guid><category><![CDATA[api]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Travel]]></category><dc:creator><![CDATA[Sunny Badgujar]]></dc:creator><pubDate>Mon, 12 Oct 2026 16:32:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a8efa5e31f5bb5fe0228fa6/3efb83ec-a34e-47a9-976d-b814e4a90741.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Originally published at <a href="https://sunnybadgujar.com/blog-integrate-sabre-api-travel-portal.html">sunnybadgujar.com</a>.</em></p>
<p>Sabre is the GDS behind a large share of North American air bookings, and its APIs can power a serious travel portal. But it doesn't work like Amadeus Self-Service: there's no sign-up-and-go tier, the catalogue mixes SOAP and REST, and the agency configuration matters as much as your code. Here's the integration path, in the order the decisions actually come up.</p>
<p>If you've already read my guide to <a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html">integrating the Amadeus API into a B2B travel portal</a>, a lot of this will feel familiar. Search is indicative, price must be confirmed live, and the booking is a separate step. The concepts carry over. What doesn't carry over is everything around them: how you get access, how you authenticate, which flavour of API you call, and how much of Sabre's behaviour is decided by agency settings you never see in a code sample.</p>
<p>If you're still choosing a GDS, start with <a href="https://sunnybadgujar.com/blog-sabre-vs-amadeus-vs-travelport-gds.html">Sabre vs Amadeus vs Travelport</a>. The short version: Sabre is the natural pick when your market is the US and Canada, or when the airlines you care about distribute most strongly through it. This post assumes you've made that call and now have to build it.</p>
<h2>Before any code: credentials, the PCC and the contract</h2>
<p>Sabre has no equivalent of Amadeus Self-Service, where you sign up and call a production API the same week. Access works in two tiers:</p>
<ul>
<li><strong>Dev Studio test credentials.</strong> Sign up on Sabre Dev Studio and you get temporary credentials for the <em>certification</em> environment. They're for learning: they only reach the publicly available REST APIs, and they aren't permanent. Good for a proof of concept; not something to build a business on.</li>
<li><strong>Live credentials.</strong> These come from a Sabre account manager, as part of an agency subscription or partner agreement. They're tied to your agency's configuration and unlock the full catalogue your contract covers, in both certification and production.</li>
</ul>
<p>Three pieces make up a Sabre credential, and you'll see them everywhere:</p>
<ul>
<li><strong>EPR</strong> (Employee Profile Record): the user ID.</li>
<li><strong>PCC</strong> (Pseudo City Code): the agency location the request runs under. It decides which fares, content, settings and ticketing arrangements apply. Sabre's docs also call it the <em>group</em>.</li>
<li><strong>Password</strong>, plus a <strong>domain</strong>, which is <code>AA</code> for travel agencies.</li>
</ul>
<blockquote>
<p><strong>The contract sits with the agency, not with you.</strong> If you're building for a client, the client (or the consolidator they use) owns the Sabre relationship and hands you credentials. Start that commercial conversation on day one. Agreeing terms, getting production access and having the PCC configured often takes longer than writing the first working booking flow.</p>
</blockquote>
<p>One more thing that surprises developers: some API behaviour depends on the agency's Sabre configuration, held in records like the TJR (Travel Journal Record). For example, Sabre's Create Booking API requires a specific TJR setting to store passenger types in the PNR. When a call fails in a way the documentation doesn't explain, the fix is sometimes a configuration change on the Sabre side, not a code change. Keep the client's Sabre support contact handy.</p>
<h2>SOAP or REST? Plan for both</h2>
<p>Sabre has been moving its catalogue to REST with JSON for years, and newer services such as the Booking Management API are REST-first. That's the right default for anything new. But a lot of established Sabre functionality and many real-world workflows still run on SOAP web services with stateful sessions, and you'll meet them as soon as you go beyond the basic shop-and-book path.</p>
<p>So the practical answer is: build a REST client and a SOAP client, side by side, behind the same internal interface. Sabre's own guidance is that the same credentials work for both; only the domain and the way you construct the token differ. Decide per operation which one you call, and keep that decision inside your Sabre adapter so the rest of your system never knows.</p>
<h2>Authentication: a REST token and a SOAP session pool</h2>
<h3>REST: one token per PCC, refreshed before it expires</h3>
<p>The REST token request has an unusual credential format. You build a user string of <code>V1:EPR:PCC:AA</code>, Base64-encode it, Base64-encode the password, join the two with a colon and Base64-encode the result again. That final string goes in a Basic authorization header to the token endpoint:</p>
<pre><code class="language-csharp">// C#: build the Basic credential Sabre expects
static string SabreCredentials(string epr, string pcc, string password, string domain = "AA")
{
    static string B64(string s) =&gt; Convert.ToBase64String(Encoding.UTF8.GetBytes(s));
    var user   = B64($"V1:{epr}:{pcc}:{domain}");   // always "V1"
    var secret = B64(password);
    return B64($"{user}:{secret}");
}

POST https://api.cert.platform.sabre.com/v2/auth/token
Authorization: Basic {SabreCredentials(...)}
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
</code></pre>
<p>You get back a bearer token that you send on every REST call. Unlike Amadeus's half-hour tokens, Sabre's sessionless tokens are long-lived, typically days rather than minutes. The pattern Sabre recommends is one token per PCC, shared by all requests on that PCC, and recreated <em>before</em> it expires rather than after a call fails. Store it with its expiry, refresh it on a schedule, and never request a new token per search.</p>
<blockquote>
<p><strong>Two environments, two hosts.</strong> Certification (<code>api.cert.platform.sabre.com</code>) is for development and testing; production (<code>api.platform.sabre.com</code>) is live inventory and real bookings. Make the host, EPR, PCC and password configuration per environment and per tenant. Hard-coding any of them is a bug waiting to happen.</p>
</blockquote>
<h3>SOAP: a managed pool of sessions</h3>
<p>SOAP calls run inside a session. You open one with <code>SessionCreateRQ</code>, receive a security token, and send it with each request. A session holds a work area, so multi-step workflows on the same booking must stay on the same session. Sessions are a limited resource, so production systems keep a <strong>pool</strong>:</p>
<ul>
<li>Open a small pool at start-up and hand sessions out to requests, returning them when done.</li>
<li>Keep idle sessions alive with <code>SessionValidateRQ</code> before they time out. It refreshes the session's timestamp without touching its content.</li>
<li>Always close sessions you no longer need with <code>SessionCloseRQ</code>. Sabre is explicit that applications which skip it risk running out of resources.</li>
<li>Expect Sabre's scheduled maintenance windows to invalidate sessions, and recreate the pool afterwards rather than letting the first customer request discover it.</li>
</ul>
<p>Getting the pool wrong produces the worst kind of bug: everything works in testing with one user, then requests start failing under load in production because sessions ran out or quietly expired.</p>
<h2>The core flow: search, revalidate, book, ticket</h2>
<p>The shape is the same one every GDS imposes, and the reason is the same: a search result is a snapshot, not a promise. On Sabre the main services are:</p>
<ol>
<li><strong>Bargain Finder Max (BFM)</strong>: the low-fare search. Send origin, destination, dates, passengers and preferences; get back priced itinerary options. This populates your results page. BFM is powerful and has a lot of request options, so start with a minimal request and add qualifiers only as you need them.</li>
<li><strong>Revalidate Itinerary</strong>: take the exact itinerary the customer picked and check its price and availability live, without holding airline inventory. Sabre is clear that there's no guarantee the fare matches the earlier search, which is exactly why this step exists. If the price moved, show the new price before you take money.</li>
<li><strong>Create Booking</strong>: create the PNR with the confirmed itinerary and traveller details. For new builds, use the Booking Management API's Create Booking (<code>/trip/orders/createBooking</code>), Sabre's evolution of the older Create Passenger Name Record API. It's designed to be stateless, accepts the sessionless REST token, and handles both traditional (ATPCO) and NDC content.</li>
<li><strong>Ticketing</strong>: a booking isn't a ticket. Issuing tickets is a separate step with its own time limits set by the airline, and it's where money actually moves between your agency and the carrier. Decide early whether you ticket immediately after payment or in a controlled queue, and build for the ticketing deadline either way.</li>
</ol>
<blockquote>
<p><strong>Get the order of operations right around money.</strong> Revalidate, then take payment (or debit the agent's wallet), then create the booking. If booking creation fails after you've charged, void or refund automatically. If ticketing fails after booking, you need a retry path and an alert for a human before the ticketing deadline passes. A charged customer with no ticket is the most expensive state your system can be in.</p>
</blockquote>
<h2>NDC content changes the booking request</h2>
<p>Sabre now returns NDC offers (airline content distributed through the IATA New Distribution Capability standard) alongside traditional ATPCO-filed fares. For a portal, that's good news: more fares and richer airline products. But the two kinds of content don't book the same way.</p>
<ul>
<li>An NDC booking carries the airline's <strong>offer ID</strong> and the selected offer items from the pricing step, rather than a rebuilt itinerary. Your data model has to keep those identifiers from search through to booking.</li>
<li>Servicing differs: changes, cancellations and ancillaries don't always behave the same for NDC as for traditional bookings, and some capabilities lag behind. Sabre's own documentation lists current limitations, so check them for your airlines before you promise a feature.</li>
<li>Your results page should treat both as one list, but your internal offer type needs a content-source field so the booking and servicing code knows which path to take.</li>
</ul>
<p>If you design the offer model with that field from the start, adding NDC is an extension. If you don't, it's a painful retrofit across search, booking and your admin panel.</p>
<h2>PCCs in a B2B portal</h2>
<p>A consumer site might run everything through one PCC. A B2B portal usually can't. Different markets, currencies, branches or contracted fares often live under different PCCs, and Sabre lets an agency work across related PCCs it has access to. Create Booking even lets you specify a different target PCC for the booking.</p>
<p>The design rule is simple: <strong>the PCC is configuration attached to a tenant, market or route, never a constant.</strong> Resolve it per request from your own rules (which agency, which point of sale, which fare source), cache a token per PCC, and record on every booking which PCC it was made in. Six months later, when finance asks why a booking priced differently, that field is the answer.</p>
<p>The rest of the B2B layer is the same as on any GDS: a markup engine that turns net fares into sell fares on the server, agent wallets modelled as an immutable ledger, multi-tenant roles, and per-agency rate limits. I've covered those in detail in the <a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html">Amadeus B2B guide</a>, and none of it changes for Sabre. That's the point of the next section.</p>
<h2>Keep Sabre behind an adapter</h2>
<p>Sabre's responses are large, deeply nested and full of Sabre-specific codes. Don't let them leak. Define your own internal types (an offer, a price breakdown, a booking result, a ticketing result) and write a Sabre adapter that translates in both directions. Your search page, booking flow, wallet logic and admin panel should only ever see your types.</p>
<p>This pays off three ways. You can move a single operation from SOAP to REST, or to a newer API version, without touching business code. You can test your booking flow without calling Sabre at all. And when you add a second GDS or a direct airline feed, it's one more adapter rather than a rewrite. I walk through that multi-supplier design in <a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html">how to integrate multiple GDS providers into one booking engine</a>.</p>
<h2>Versioning: Sabre retires APIs on a schedule</h2>
<p>Sabre versions its APIs and publishes a retirement schedule. Older versions of core services, BFM and Revalidate Itinerary included, get blocked in production on announced dates. A portal that integrated an API version three years ago and never revisited it will eventually stop booking.</p>
<ul>
<li>Pin the version of every Sabre API you call in configuration, so an upgrade is a deliberate change you can test in certification first.</li>
<li>Check the retirement schedule as a recurring task, not when something breaks.</li>
<li>Budget for upgrades. A version bump usually means re-testing request and response mapping, which is another reason the adapter matters: the change stays in one place.</li>
</ul>
<h2>Pitfalls I'd flag before you start</h2>
<ul>
<li><strong>Don't build on Dev Studio test credentials.</strong> They're temporary and limited to public REST APIs. Get the client's real certification credentials as early as possible.</li>
<li><strong>Never skip Revalidate Itinerary.</strong> Search fares are a snapshot. Confirm live before you charge.</li>
<li><strong>Manage SOAP sessions as a pool.</strong> Keep them alive, close them when done, and rebuild after maintenance windows.</li>
<li><strong>Treat the PCC as data.</strong> Resolve it per tenant or route, and store it on every booking.</li>
<li><strong>Separate booking from ticketing.</strong> Track the ticketing deadline and alert a human before it passes.</li>
<li><strong>Carry NDC offer IDs end to end.</strong> Design the offer model for both content types from day one.</li>
<li><strong>Log every request and response.</strong> With credentials masked, and searchable by PNR. Support will need it daily.</li>
<li><strong>Watch the retirement schedule.</strong> An API version you never upgrade is a production outage with a date on it.</li>
</ul>
<p>None of this is exotic. It's the same discipline any GDS demands, with Sabre's particular mix of sessions, PCCs and configuration on top. Get the access, authentication and money flow right first, keep Sabre behind your own interface, and it becomes a dependable source of content for a portal you can grow.</p>
<hr />
<p><em>I'm Sunny Badgujar, a full-stack .NET developer who runs a live travel platform integrated with multiple GDS suppliers. I build <a href="https://sunnybadgujar.com/travel-portal-development.html">travel portals</a> and <a href="https://sunnybadgujar.com/booking-engine-development.html">booking engines</a>. The full version of this article, with an FAQ, is on <a href="https://sunnybadgujar.com/blog-integrate-sabre-api-travel-portal.html">my blog</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Sabre vs Amadeus vs Travelport: Which GDS Should Your Travel Portal Use?]]></title><description><![CDATA[Originally published at sunnybadgujar.com.
If you're building a travel portal, one of the first real decisions is which global distribution system to connect to — and the three big names, Amadeus, Sab]]></description><link>https://sunnybadgujar.hashnode.dev/sabre-vs-amadeus-vs-travelport-which-gds-should-your-travel-portal-use</link><guid isPermaLink="true">https://sunnybadgujar.hashnode.dev/sabre-vs-amadeus-vs-travelport-which-gds-should-your-travel-portal-use</guid><dc:creator><![CDATA[Sunny Badgujar]]></dc:creator><pubDate>Thu, 17 Sep 2026 16:59:58 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published at <a href="https://sunnybadgujar.com/blog-sabre-vs-amadeus-vs-travelport-gds.html">sunnybadgujar.com</a>.</em></p>
<p>If you're building a travel portal, one of the first real decisions is which global distribution system to connect to — and the three big names, Amadeus, Sabre and Travelport, get compared as if one is simply "the best." They aren't ranked that way in practice. They're three large, mature systems that overlap heavily and differ in ways that matter enormously for <em>your</em> portal and barely at all in the abstract. Here's how they actually differ, and how to choose — from someone who runs a live platform sitting on top of more than one of them.</p>
<p>First, the thing every comparison should say up front: a GDS is a distribution network. It aggregates flights, hotels, car hire and other travel content from thousands of providers and exposes it through one connection, so your portal can search and book across the industry without integrating each airline and hotel chain yourself. Amadeus, Sabre and Travelport are the three that dominate that role globally. All three do fundamentally the same job. So the question isn't "which one is good" — they're all good — it's "which one fits what I'm building and who I'm selling to."</p>
<p>That's why a blanket "use X" recommendation is worth ignoring. The right GDS depends on your target market, the content you need, the commercial terms you can get, and how much engineering you want to take on. Let's go through each of those, then the three systems, then how to actually decide.</p>
<h2>The three, at a glance</h2>
<p>Here's the honest high-level shape of each. Treat these as tendencies, not laws — all three carry global content, and their exact coverage and terms shift with the deals airlines and agencies strike.</p>
<table>
<thead>
<tr>
<th>GDS</th>
<th>Typically strongest in</th>
<th>Often chosen for</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Amadeus</strong></td>
<td>Europe and broad global coverage</td>
<td>The widest all-round airline and hotel content; strong for globally-minded portals</td>
</tr>
<tr>
<td><strong>Sabre</strong></td>
<td>North America</td>
<td>Deep US carrier and agency presence; portals focused on the Americas</td>
</tr>
<tr>
<td><strong>Travelport</strong> (Galileo / Apollo / Worldspan)</td>
<td>A challenger across several regions</td>
<td>Competitive terms, merchandising and NDC content; strong in select markets</td>
</tr>
</tbody></table>
<p><strong>The honest headline:</strong> for most portals the deciding factor isn't which brand is "biggest," it's which one has the best <em>content and commercial terms for your specific market</em>. A smaller GDS with a great deal and the right coverage beats the biggest name on paper.</p>
<h2>The factors that actually decide it</h2>
<h3>1. Your target market and routes</h3>
<p>This is the first and biggest filter. Where do your customers fly and stay? A portal built for European and long-haul international travel usually finds Amadeus the natural fit; one aimed at North American domestic and cross-border travel often leans to Sabre; Travelport is a serious option across several regions and frequently the one that competes hardest on terms. Match the GDS's regional strength to your actual demand and half the decision is made — because content and fares that are excellent in one region can be thinner in another.</p>
<h3>2. Content: air, hotel, car — and NDC</h3>
<p>"Flights" isn't one thing. You need to check that the GDS carries the airlines your customers actually want, at competitive fares, plus the hotel and car content if you're selling those. A growing piece of this is <strong>NDC</strong> (New Distribution Capability) — the newer standard airlines use to distribute richer fares and ancillaries like seats and bags. All three GDSs are building out NDC content, but the depth varies by carrier and by GDS, and it matters more every year. If a specific airline's full fare range is core to your business, verify how each GDS carries it before you choose.</p>
<h3>3. Commercial terms and cost</h3>
<p>GDS access is a negotiated commercial agreement, not a public price list. Terms vary with your booking volume, market and content needs, and can involve segment fees, minimums and incentives that swing the real cost significantly. This is why the "best" GDS is partly whichever gives <em>you</em> the best deal — a challenger hungry for your segment may offer terms the market leader won't. You negotiate directly with the GDS, or reach one through an aggregator, and the deal is a genuine input to the choice, not an afterthought. I've written more on how these costs feed into a build in <a href="https://sunnybadgujar.com/blog-cost-to-build-a-booking-engine.html">what it costs to build a booking engine</a>.</p>
<h3>4. The API and the engineering reality</h3>
<p>Each GDS exposes its content through its own APIs, and they are not interchangeable — different data models, different booking flows, different quirks and legacy corners. Whichever you pick, the integration is real work: managing sessions and pricing, handling the confirm-price-before-book flow, dealing with time-outs and errors gracefully, and keeping the connection stable in production. The GDS with slightly better content but a far messier API can cost you more in the long run than a marginally smaller one that's cleaner to build on. This is exactly the kind of trade-off worth weighing with an engineer who's integrated one before, not just a salesperson.</p>
<h2>Do you have to pick just one?</h2>
<p>No — and many serious portals don't. Running more than one GDS widens your content and fare coverage, strengthens your hand on commercial terms, and means you're not wholly dependent on a single provider's uptime or pricing. The trade-off is engineering: every GDS you add is another API, another data model and another set of failure modes to handle.</p>
<p>The right way to do it — and the way I've built it — is to put an <strong>abstraction layer</strong> between your portal and the GDSs. The rest of your system talks to one internal interface for "search flights" or "book this fare," and behind that interface you can have one GDS today and three tomorrow without rewriting the portal. It also lets you route a search to whichever provider has the best fare or content for that request. If multi-GDS is where you're heading, that architecture is the whole game; I've written it up in detail in <a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html">how to integrate multiple GDS providers into one booking engine</a>. And if you're leaning toward Amadeus specifically, there's a full walkthrough in <a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html">integrating the Amadeus API into a B2B travel portal</a>.</p>
<h2>How to actually decide</h2>
<p>Strip it back to a short, honest process. Start with your market and routes and shortlist the GDS whose regional strength matches your demand — that usually narrows it to one obvious lead and one challenger. Confirm each carries the airline, hotel and car content your customers need, with real attention to NDC for any airline central to your model. Then talk terms with both, because the commercial deal can flip the decision, and factor in the engineering effort of each one's API. If you have the volume and the ambition, plan for more than one from the start by building behind an abstraction layer — but launch with the single best fit rather than waiting to integrate everything.</p>
<p>What you should <em>not</em> do is pick the biggest brand by reputation and assume it's right. I've seen portals over-pay for the market leader when a challenger had better terms and equal content for their region, and I've seen portals under-serve their customers by choosing on price alone and missing the airlines those customers actually wanted. The decision is specific to you — and it's cheap to get right up front and expensive to change once your bookings depend on it.</p>
<h2>GDS comparison FAQs</h2>
<p><strong>What is the difference between Amadeus, Sabre and Travelport?</strong>
All three are global distribution systems that let a travel portal search and book flights, hotels and cars from thousands of providers through one connection. The practical differences are geographic strength (Amadeus is strongest in Europe and globally, Sabre in North America, Travelport competes across several regions), the exact airline and hotel content each carries, their APIs and NDC support, and the commercial terms you can negotiate. For most portals the deciding factor is which one has the best content and terms for your target market.</p>
<p><strong>Which GDS is best for a travel portal?</strong>
There is no single best GDS — it depends on where your customers travel, what content you need, and the commercial deal you can get. A portal serving European or global routes often starts with Amadeus; one focused on North America often leans to Sabre; Travelport is a strong option and frequently the challenger on price and terms. The right answer is the GDS whose content and pricing fit your market, not the biggest name.</p>
<p><strong>Can a travel portal use more than one GDS?</strong>
Yes, and larger portals often do — to widen fare and content coverage, to negotiate better terms, and to avoid depending on a single provider. The catch is that each GDS has its own API and quirks, so connecting several is far more work than one. The clean way to do it is to build an internal abstraction layer so the rest of your system talks to one interface while multiple GDSs sit behind it.</p>
<p><strong>How much does GDS access cost?</strong>
GDS access is a commercial agreement, not a public sign-up, so there is no list price. Terms vary by provider, your booking volume, your market and what content you need, and can involve segment fees, minimums and incentives. You negotiate directly with the GDS or go through an aggregator, and the deal you can get is itself one of the factors in choosing between them.</p>
<p><strong>Do I need a GDS or can I use direct airline APIs and NDC?</strong>
It depends on your model. A GDS gives you broad multi-airline content through one connection, which is why most portals start there. Direct airline APIs and NDC can give richer content and better fares for specific carriers, but connecting many airlines directly is a lot of integrations to build and maintain. Many modern portals use a mix — a GDS for breadth plus direct or NDC connections for key airlines — behind a single booking layer.</p>
<hr />
<p><em>I build travel portals and booking engines on top of the major GDSs — and I run a live platform that sits on more than one. If you're choosing a GDS or building on top of one, see my <a href="https://sunnybadgujar.com/travel-portal-development.html">travel portal development service</a> or <a href="https://sunnybadgujar.com/contact.html">get in touch</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to Integrate the Amadeus API into a B2B Travel Portal]]></title><description><![CDATA[Originally published at sunnybadgujar.com.

Amadeus is where most travel portals start, and for good reason: it's the largest GDS, the content is deep, and the modern Amadeus for Developers APIs are p]]></description><link>https://sunnybadgujar.hashnode.dev/how-to-integrate-the-amadeus-api-into-a-b2b-travel-portal</link><guid isPermaLink="true">https://sunnybadgujar.hashnode.dev/how-to-integrate-the-amadeus-api-into-a-b2b-travel-portal</guid><dc:creator><![CDATA[Sunny Badgujar]]></dc:creator><pubDate>Fri, 04 Sep 2026 22:36:10 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>Originally published at <a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html">sunnybadgujar.com</a>.</em></p>
</blockquote>
<p>Amadeus is where most travel portals start, and for good reason: it's the largest GDS, the content is deep, and the modern <em>Amadeus for Developers</em> APIs are plain REST with JSON — no arcane messaging formats to learn on day one. But there's a gap between "I can search flights from a code sample" and "I've shipped a B2B travel portal that sub-agents actually book on." This post is about closing that gap.</p>
<p>I run a travel reservation platform that integrates Amadeus alongside other suppliers end to end — search, pricing, booking, refunds, agent wallets and markup. Below is the integration path I'd hand to someone starting today, in the order the decisions actually come up.</p>
<h2>First decision: Self-Service or Enterprise?</h2>
<p>Before you write a line of code, you have to know which Amadeus you're building against, because they are not the same product.</p>
<ul>
<li><strong>Amadeus Self-Service APIs.</strong> Sign up on the developer portal, get an API key in minutes, and you're calling a free test environment the same day. REST/JSON, pay-as-you-go pricing in production, no commercial negotiation. Perfect for MVPs, prototypes, and portals with moderate volume. The catch: the content and functionality are a curated subset of full GDS capability.</li>
<li><strong>Amadeus Enterprise (the full travel platform).</strong> Full GDS content, ticketing, richer fare and ancillary support — but it requires a commercial agreement, an office ID, and a heavier onboarding. This is what large agencies and consolidators run on.</li>
</ul>
<blockquote>
<p><strong>Practical advice:</strong> build your MVP on Self-Service. Its data model and flow mirror the enterprise concepts closely enough that if you keep Amadeus behind an adapter (more on that below), moving up later is an integration change, not a rewrite. Don't block your launch on an enterprise contract you don't need yet.</p>
</blockquote>
<p>Everything that follows uses the Self-Service REST flow, because that's where 90% of new B2B portals begin.</p>
<h2>Authentication: one token, cached, refreshed</h2>
<p>Amadeus uses OAuth2 client credentials. You exchange your API key and secret for an access token, and you send that token as a bearer header on every subsequent call. The token is short-lived — roughly 30 minutes — so you cache it and refresh before it expires rather than requesting a new one per call.</p>
<pre><code class="language-http">POST https://test.api.amadeus.com/v1/security/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&amp;client_id=YOUR_API_KEY
&amp;client_secret=YOUR_API_SECRET

// response
{ "access_token": "abc123...", "expires_in": 1799, "token_type": "Bearer" }
</code></pre>
<p>Wrap this in a small token provider that stores the token in memory with its expiry and hands out a valid one on demand. Requesting a fresh token on every search is a classic early mistake — it adds a round-trip to every request and burns quota for no reason.</p>
<blockquote>
<p><strong>Two environments, two base URLs:</strong> <code>test.api.amadeus.com</code> serves cached, non-live data for development; <code>api.amadeus.com</code> is production with live pricing and real bookings. They use different credentials. Make the base URL and keys configuration, never hard-coded — you <em>will</em> switch between them constantly.</p>
</blockquote>
<h2>The core flight flow is three calls, in order</h2>
<p>This is the single most important thing to internalise about Amadeus, and it maps directly to how travel actually works: <strong>search is indicative, price is live, and only then can you book.</strong> Three endpoints, always in this sequence:</p>
<ol>
<li><strong>Flight Offers Search</strong> — <code>POST /v2/shopping/flight-offers</code>. Send origin, destination, dates, passenger counts, cabin. Get back a list of offers, each with a full fare breakdown and an offer object you carry forward. This is what populates your results page.</li>
<li><strong>Flight Offers Price</strong> — <code>POST /v1/shopping/flight-offers/pricing</code>. Take the exact offer the customer selected and confirm it live. Amadeus revalidates availability and price and returns the current, bookable fare. This is where you catch price drift before it costs you money.</li>
<li><strong>Flight Create Orders</strong> — <code>POST /v1/booking/flight-orders</code>. Send the confirmed offer plus traveller details, and Amadeus creates the booking (a PNR). This is the step that actually reserves seats.</li>
</ol>
<p>Skipping the middle step is the most expensive bug in the category. If you book straight off the search result, you'll routinely try to sell fares that have already changed or sold out — and you eat the difference or fail the booking at the worst possible moment. The price call exists precisely so you re-confirm the fare the instant before you charge the customer.</p>
<blockquote>
<p><strong>Booking is two steps for the same reason.</strong> Confirm price live, <em>then</em> take payment, <em>then</em> create the order. If order creation fails after you've charged, you must automatically void or refund — never leave an agent's wallet debited for a PNR that doesn't exist.</p>
</blockquote>
<h2>Now the part the docs don't cover: this is a B2B portal</h2>
<p>A consumer booking site has one customer type. A B2B portal has <em>agents</em> — sub-agents, sub-sub-agents, corporate clients — each of whom logs in, sees their own fares, and books against their own money. That changes the architecture in four concrete ways.</p>
<h3>1. Multi-tenancy and roles</h3>
<p>Your data model needs a tenant hierarchy from day one: the portal owner at the top, then agencies, then the individual agents who log in. Every booking, every wallet transaction, every markup rule is scoped to a node in that tree. Retrofitting multi-tenancy after launch is painful — bake the agency/agent relationship into your schema and every query before you have live data.</p>
<h3>2. The markup engine</h3>
<p>Amadeus returns <strong>net fares</strong>. Your agents must never see the net — they see the price <em>after</em> your markup. So between the pricing call and the response you send to the browser, you run a markup engine that adds a margin. And it's rarely a flat number: markup varies by agency, by route, by airline, by fare class, sometimes as a percentage and sometimes as a fixed amount per passenger.</p>
<pre><code class="language-csharp">// conceptual: applied server-side, never in the client
sellFare = netFare + resolveMarkup(agencyId, route, airline, fareClass);
// the agent sees sellFare; the net stays server-side only
</code></pre>
<p>Two rules keep this safe. First, markup is applied on the server, always — the net fare must never reach the browser, or an agent will read it in the network tab. Second, you store both the net and the sell price on the booking record, so your reconciliation and commission reports are accurate later.</p>
<h3>3. Agent wallets and credit</h3>
<p>B2B agents don't pay by card per booking. They top up a <strong>wallet</strong> (or get a credit line), and each booking debits the balance. That means your booking flow has an extra gate: check the agent has sufficient balance <em>before</em> you call Flight Create Orders, then debit atomically as part of confirming the booking.</p>
<ul>
<li>Model the wallet as an <strong>immutable ledger</strong> — append credits and debits, never edit a running total. The balance is the sum of entries. This makes disputes auditable and prevents a whole class of race-condition bugs.</li>
<li>Debit and booking must succeed or fail together. If the Amadeus order fails, the debit reverses; if the debit fails (insufficient funds), the order is never attempted.</li>
<li>Cancellations and refunds credit the wallet back, minus any airline penalty — as a background job with retries, because refund calls fail transiently.</li>
</ul>
<h3>4. Credential and quota management</h3>
<p>On Self-Service, the whole portal typically shares one set of production Amadeus credentials, so your rate limiting and quota tracking are <em>portal-wide</em> — one noisy agency hammering search can throttle everyone. Enforce per-agency rate limits inside your own layer so no single tenant can exhaust the shared quota. (On enterprise, you may have per-office-ID credentials, which changes this calculus — another reason to keep Amadeus behind an adapter.)</p>
<h2>Keep Amadeus behind an adapter — even if it's your only supplier</h2>
<p>It's tempting, when Amadeus is your only integration, to call it directly from your booking code. Don't. Define your own internal types — a <code>FlightOffer</code>, a <code>PriceBreakdown</code>, a <code>BookingResult</code> — and make an Amadeus adapter translate into them. Your search page, booking flow and admin panel work only with your model and never import Amadeus specifics.</p>
<p>You get two payoffs. Moving from Self-Service to enterprise later becomes an adapter change instead of a rewrite. And the day you add a second supplier — a low-cost carrier aggregator, a direct hotel feed, another GDS — you write one more adapter and the rest of the system doesn't notice. That multi-supplier architecture is a topic in itself; I've written it up separately in <a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html">how to integrate multiple GDS providers into one booking engine</a>.</p>
<h2>Caching, rate limits and the test-data trap</h2>
<p>GDS calls cost money and are rate-limited, so treat them accordingly:</p>
<ul>
<li><strong>Cache searches briefly.</strong> A 30–120 second cache keyed on route and dates absorbs pagination and repeat searches without re-hitting Amadeus. Never cache long enough to serve a stale fare into a booking — the live price call is your safety net, but don't lean on it to paper over reckless caching.</li>
<li><strong>Rate-limit per agency, inside your layer.</strong> As above, the shared Self-Service quota means one tenant can starve the rest unless you police it yourself.</li>
<li><strong>Remember the test environment is not live.</strong> Test data is cached and limited — some routes return nothing, prices aren't real, and a "successful" test booking isn't a real reservation. Validate business logic in test, but only trust behaviour you've re-checked against production before you go live.</li>
</ul>
<h2>The admin panel is half the product</h2>
<p>None of the above is visible to your operations team unless you build for them. An internal admin panel — showing every booking with its agency, net and sell fare, wallet transaction, PNR and refund state, plus a full audit log of what you sent Amadeus and what came back — is how support resolves a stuck booking at midnight without reading raw API logs. In a B2B portal it's not optional: it's where you manage agents, set markup rules, top up wallets and investigate disputes. Build it alongside the engine, not after.</p>
<h2>Pitfalls I'd flag before you start</h2>
<ul>
<li><strong>Never skip Flight Offers Price.</strong> Search prices are indicative; confirm live before every charge.</li>
<li><strong>Never let the net fare reach the browser.</strong> Apply markup server-side; store both net and sell on the booking.</li>
<li><strong>Make wallet and booking atomic.</strong> A retried booking or refund must never double-charge or double-credit an agent.</li>
<li><strong>Don't hard-code the environment.</strong> Test and production have different URLs and keys — make them configuration.</li>
<li><strong>Don't call Amadeus directly from business code.</strong> The adapter is what lets you grow from Self-Service to enterprise, and from one supplier to many.</li>
<li><strong>Log every request and response for audit.</strong> "What exactly did we send and what did they say" is a question you'll be asked constantly.</li>
</ul>
<p>Get the three-step flow and the B2B plumbing — markup, wallets, multi-tenancy — right, and Amadeus becomes a dependable engine you can build a real agency business on. Rush them, and you'll be reconciling mismatched fares and disputed wallet balances by hand for months. The API is the easy part; the portal around it is the product.</p>
<hr />
<p><em>I've integrated Amadeus and other GDS suppliers into a live travel platform — search, pricing, booking, markup, agent wallets and refunds. If you're building a B2B travel portal, you can read more about my <a href="https://sunnybadgujar.com/travel-portal-development.html">travel portal development work</a> or <a href="https://sunnybadgujar.com/contact.html">get in touch</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why Your SQL Server Database Is Slow (and How to Fix It)]]></title><description><![CDATA["The app is slow" is one of the most common things a business tells me when they get in touch. Pages that loaded in a blink now take five or six seconds. Reports time out. Everything gets worse at the]]></description><link>https://sunnybadgujar.hashnode.dev/why-your-sql-server-database-is-slow-and-how-to-fix-it</link><guid isPermaLink="true">https://sunnybadgujar.hashnode.dev/why-your-sql-server-database-is-slow-and-how-to-fix-it</guid><dc:creator><![CDATA[Sunny Badgujar]]></dc:creator><pubDate>Sat, 29 Aug 2026 23:55:42 GMT</pubDate><content:encoded><![CDATA[<p>"The app is slow" is one of the most common things a business tells me when they get in touch. Pages that loaded in a blink now take five or six seconds. Reports time out. Everything gets worse at the busiest time of day — exactly when you can least afford it. And the database server sits there pinned at 100%, so the assumption is: we've outgrown the hardware, we need a bigger machine.</p>
<p>Sometimes that's true. Usually it isn't. In most systems I've looked at, throwing more CPU and RAM at the problem just buys a few months and a bigger bill, because the real cause is still sitting in the code. Below is the order I actually work through when a SQL Server database has slowed to a crawl — roughly cheapest and highest-impact first.</p>
<h2>1. Missing indexes — the number one culprit</h2>
<p>An index is what lets the database jump straight to the rows it needs instead of reading the entire table. Without the right one, a query for a single customer might scan all two million rows every single time it runs. As the data grows, that same query goes from milliseconds to seconds, and the slowdown creeps up so gradually that no single day feels like the day it broke. Adding a well-chosen index can turn a multi-second query into an instant one, with no application changes at all.</p>
<h2>2. The N+1 query problem</h2>
<p>You load a list of 50 orders with one query, then — often without realising — the code fires off one more query per order to fetch its customer. That's 51 round trips to show a single page. With ORMs like Entity Framework it's especially easy to write by accident. The fix is usually to load the related data up front in one query instead of hundreds.</p>
<h2>3. SELECT * and pulling back more than you need</h2>
<p>When a query grabs every column and every row "just in case," the database has to read, transfer and materialise all of it. Asking only for the columns and rows you'll use — and paging large lists — cuts the work at the source.</p>
<h2>4. Queries that can't use their indexes</h2>
<p>Wrapping a column in a function, doing type conversions in the wrong place, or leading a search with a wildcard all quietly force the database to scan the whole table anyway. You find these by reading the execution plan — SQL Server's own explanation of how it ran the query.</p>
<h2>5. Blocking and locking under load</h2>
<p>If the app is fine when it's quiet and falls apart when everyone's using it, the problem may be queries getting in each other's way. When one operation holds a lock longer than it should, everything else queues up behind it.</p>
<h2>6. Statistics and fragmentation</h2>
<p>SQL Server decides how to run a query based on internal statistics. If those go stale, it starts making bad decisions. On a lot of the systems I inherit, routine maintenance was never set up, so performance degrades month after month for reasons nobody can see.</p>
<h2>7. Only now: is it actually the hardware?</h2>
<p>Sometimes the honest answer is yes. But by then you're making that decision with evidence instead of a guess. I've seen a "we need a bigger server" emergency turn out to be two missing indexes and one N+1 query — fixed in an afternoon, no new hardware.</p>
<blockquote>
<p>A bigger server makes a slow query slow more quickly. It doesn't make it fast. The fix is almost always in the queries, not the hardware.</p>
</blockquote>
<hr />
<p><em>I'm Sunny Badgujar, a freelance full-stack .NET developer. I do <a href="https://sunnybadgujar.com/services.html">SQL Server performance audits and query tuning</a> for .NET applications. If your app is slow and you're not sure why, <a href="https://sunnybadgujar.com/contact.html">tell me what you're seeing</a>.</em></p>
]]></content:encoded></item></channel></rss>