<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sunny Badgujar</title>
    <description>The latest articles on DEV Community by Sunny Badgujar (@sunny_badgujar_13).</description>
    <link>https://dev.to/sunny_badgujar_13</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4095939%2F8e04938a-4ee6-42cb-b918-10ef64dd8523.jpg</url>
      <title>DEV Community: Sunny Badgujar</title>
      <link>https://dev.to/sunny_badgujar_13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sunny_badgujar_13"/>
    <language>en</language>
    <item>
      <title>How to Integrate the Sabre API into a Travel Portal</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Mon, 12 Oct 2026 16:19:55 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/how-to-integrate-the-sabre-api-into-a-travel-portal-mn3</link>
      <guid>https://dev.to/sunny_badgujar_13/how-to-integrate-the-sabre-api-into-a-travel-portal-mn3</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sunnybadgujar.com/blog-integrate-sabre-api-travel-portal.html" rel="noopener noreferrer"&gt;sunnybadgujar.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If you've already read my guide to &lt;a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html" rel="noopener noreferrer"&gt;integrating the Amadeus API into a B2B travel portal&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;If you're still choosing a GDS, start with &lt;a href="https://sunnybadgujar.com/blog-sabre-vs-amadeus-vs-travelport-gds.html" rel="noopener noreferrer"&gt;Sabre vs Amadeus vs Travelport&lt;/a&gt;. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before any code: credentials, the PCC and the contract
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev Studio test credentials.&lt;/strong&gt; Sign up on Sabre Dev Studio and you get temporary credentials for the &lt;em&gt;certification&lt;/em&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live credentials.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three pieces make up a Sabre credential, and you'll see them everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EPR&lt;/strong&gt; (Employee Profile Record): the user ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PCC&lt;/strong&gt; (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 &lt;em&gt;group&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password&lt;/strong&gt;, plus a &lt;strong&gt;domain&lt;/strong&gt;, which is &lt;code&gt;AA&lt;/code&gt; for travel agencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The contract sits with the agency, not with you.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  SOAP or REST? Plan for both
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: a REST token and a SOAP session pool
&lt;/h2&gt;

&lt;h3&gt;
  
  
  REST: one token per PCC, refreshed before it expires
&lt;/h3&gt;

&lt;p&gt;The REST token request has an unusual credential format. You build a user string of &lt;code&gt;V1:EPR:PCC:AA&lt;/code&gt;, 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// C#: build the Basic credential Sabre expects&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;SabreCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;epr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pcc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AA"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;B64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;B64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"V1:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;epr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pcc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// always "V1"&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;B64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;B64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;POST&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//api.cert.platform.sabre.com/v2/auth/token&lt;/span&gt;
&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Basic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;SabreCredentials&lt;/span&gt;&lt;span class="p"&gt;(...)}&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;urlencoded&lt;/span&gt;

&lt;span class="n"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client_credentials&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;em&gt;before&lt;/em&gt; 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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two environments, two hosts.&lt;/strong&gt; Certification (&lt;code&gt;api.cert.platform.sabre.com&lt;/code&gt;) is for development and testing; production (&lt;code&gt;api.platform.sabre.com&lt;/code&gt;) 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  SOAP: a managed pool of sessions
&lt;/h3&gt;

&lt;p&gt;SOAP calls run inside a session. You open one with &lt;code&gt;SessionCreateRQ&lt;/code&gt;, 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 &lt;strong&gt;pool&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a small pool at start-up and hand sessions out to requests, returning them when done.&lt;/li&gt;
&lt;li&gt;Keep idle sessions alive with &lt;code&gt;SessionValidateRQ&lt;/code&gt; before they time out. It refreshes the session's timestamp without touching its content.&lt;/li&gt;
&lt;li&gt;Always close sessions you no longer need with &lt;code&gt;SessionCloseRQ&lt;/code&gt;. Sabre is explicit that applications which skip it risk running out of resources.&lt;/li&gt;
&lt;li&gt;Expect Sabre's scheduled maintenance windows to invalidate sessions, and recreate the pool afterwards rather than letting the first customer request discover it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core flow: search, revalidate, book, ticket
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bargain Finder Max (BFM)&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revalidate Itinerary&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Booking&lt;/strong&gt;: create the PNR with the confirmed itinerary and traveller details. For new builds, use the Booking Management API's Create Booking (&lt;code&gt;/trip/orders/createBooking&lt;/code&gt;), 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ticketing&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get the order of operations right around money.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  NDC content changes the booking request
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An NDC booking carries the airline's &lt;strong&gt;offer ID&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  PCCs in a B2B portal
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The design rule is simple: &lt;strong&gt;the PCC is configuration attached to a tenant, market or route, never a constant.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html" rel="noopener noreferrer"&gt;Amadeus B2B guide&lt;/a&gt;, and none of it changes for Sabre. That's the point of the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Sabre behind an adapter
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html" rel="noopener noreferrer"&gt;how to integrate multiple GDS providers into one booking engine&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning: Sabre retires APIs on a schedule
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pin the version of every Sabre API you call in configuration, so an upgrade is a deliberate change you can test in certification first.&lt;/li&gt;
&lt;li&gt;Check the retirement schedule as a recurring task, not when something breaks.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pitfalls I'd flag before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't build on Dev Studio test credentials.&lt;/strong&gt; They're temporary and limited to public REST APIs. Get the client's real certification credentials as early as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never skip Revalidate Itinerary.&lt;/strong&gt; Search fares are a snapshot. Confirm live before you charge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage SOAP sessions as a pool.&lt;/strong&gt; Keep them alive, close them when done, and rebuild after maintenance windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the PCC as data.&lt;/strong&gt; Resolve it per tenant or route, and store it on every booking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate booking from ticketing.&lt;/strong&gt; Track the ticketing deadline and alert a human before it passes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carry NDC offer IDs end to end.&lt;/strong&gt; Design the offer model for both content types from day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every request and response.&lt;/strong&gt; With credentials masked, and searchable by PNR. Support will need it daily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the retirement schedule.&lt;/strong&gt; An API version you never upgrade is a production outage with a date on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Sunny Badgujar, a full-stack .NET developer who runs a live travel platform integrated with multiple GDS suppliers. I build &lt;a href="https://sunnybadgujar.com/travel-portal-development.html" rel="noopener noreferrer"&gt;travel portals&lt;/a&gt; and &lt;a href="https://sunnybadgujar.com/booking-engine-development.html" rel="noopener noreferrer"&gt;booking engines&lt;/a&gt;. The full version of this article, with an FAQ, is on &lt;a href="https://sunnybadgujar.com/blog-integrate-sabre-api-travel-portal.html" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>dotnet</category>
      <category>architecture</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Developer Left Mid-Project? How to Take Over an Unfinished App</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Tue, 06 Oct 2026 18:31:27 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/developer-left-mid-project-how-to-take-over-an-unfinished-app-pmd</link>
      <guid>https://dev.to/sunny_badgujar_13/developer-left-mid-project-how-to-take-over-an-unfinished-app-pmd</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sunnybadgujar.com/blog-take-over-unfinished-software-project.html" rel="noopener noreferrer"&gt;sunnybadgujar.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The messages got slower, then stopped. The app is "about 80% done," you've paid most of the budget, and nobody is answering. It's one of the most common reasons people contact me, and it's more recoverable than it feels in the moment. Here's what to do in the first 48 hours, what a new developer should check before writing a single line, and how to decide whether to finish what's there or start again.&lt;/p&gt;

&lt;p&gt;Projects get abandoned for ordinary reasons more often than dramatic ones. A freelancer takes a full-time job. A small agency loses its one developer who knew your project. Someone underquoted, ran out of money halfway, and quietly stopped. Occasionally it's worse than that, but usually the code isn't sabotaged. It's just unfinished, undocumented, and sitting somewhere you may or may not have access to.&lt;/p&gt;

&lt;p&gt;This guide is different from &lt;a href="https://sunnybadgujar.com/blog-legacy-dotnet-application-rescue.html" rel="noopener noreferrer"&gt;rescuing a legacy application&lt;/a&gt;. A legacy app is old but working; the problem is that it's fragile. An unfinished project is the opposite: it may be built on perfectly modern technology, but it has never actually worked end to end, and the person who knew how close it really was has gone. That changes what you do first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first 48 hours: secure what you own
&lt;/h2&gt;

&lt;p&gt;Before you think about code quality or deadlines, make sure you control the things the project runs on. Every one of these is far easier to sort out now, while the old developer is merely slow to reply, than later if the relationship turns sour.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The source code.&lt;/strong&gt; Get the full repository, with its history, into a GitHub, GitLab or Bitbucket account &lt;em&gt;you&lt;/em&gt; own. A zip file of the latest code is better than nothing, but the history shows what changed recently and what was abandoned halfway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting and cloud accounts.&lt;/strong&gt; Azure, AWS, a VPS, Netlify, whatever runs the app. If it's in the developer's personal account, ask for it to be transferred, or at least for you to be added as an owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The domain and DNS.&lt;/strong&gt; Whoever controls the DNS controls where your users end up. Make sure the registrar account is yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The database.&lt;/strong&gt; Take a backup now, and store it somewhere you control. If there's real customer data in it, this is the most valuable thing in the whole project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party services.&lt;/strong&gt; Payment gateway, email provider, SMS, maps, any supplier APIs, app store and Shopify Partner accounts. Make a list of every service the app talks to and who owns each login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have access, &lt;strong&gt;change the passwords and rotate the API keys&lt;/strong&gt;, especially for payments and email. That isn't an accusation. It's simply good practice whenever someone with production access leaves, and it closes the door on any accidental damage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ask for it in writing.&lt;/strong&gt; Keep requests for code and credentials polite, specific and in email. If you later need to rely on your contract, a clear written record of what you asked for and when is worth more than a dozen phone calls.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Don't start by adding features
&lt;/h2&gt;

&lt;p&gt;The natural instinct is to find a new developer and say "finish the last 20%." Resist it. Nobody, including the previous developer, actually knows whether it's 20%. "Done" in an abandoned project usually means "a screen exists," not "it works, it's tested, and it's deployed." Asking someone to add features to code they don't understand yet is how the second developer ends up abandoning it too.&lt;/p&gt;

&lt;p&gt;The first piece of work should be a short, fixed-scope &lt;strong&gt;audit&lt;/strong&gt;: a few days to a week in which a new developer reads the code, gets it running, and tells you plainly what state it's in. It's the cheapest decision you'll make in the whole recovery, because everything else is estimated from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a takeover audit actually checks
&lt;/h2&gt;

&lt;p&gt;When I take over someone else's half-finished project, these are the questions I need answered before I'll estimate anything. You can use the list to judge whether whoever you hire is doing a real audit or just skimming.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Does it build, and does it run?
&lt;/h3&gt;

&lt;p&gt;The most basic test, and the one that fails surprisingly often. Can a fresh machine pull the repository, restore its dependencies and start the app? Missing configuration files, secrets that only lived on the old developer's laptop, and packages pinned to versions that no longer download are all common. Until it runs locally, nothing else can be trusted.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Can it be deployed, and by whom?
&lt;/h3&gt;

&lt;p&gt;Is there a repeatable deployment, such as a pipeline or at least a written process, or did the old developer copy files to a server by hand? If only one person ever knew how to release it, that knowledge has to be rebuilt before anything new ships.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Is "done" really done?
&lt;/h3&gt;

&lt;p&gt;This is where the gap between what you were told and what exists shows up. I go through every feature that was reported as finished and check it end to end:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you were told&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Payments are done"&lt;/td&gt;
&lt;td&gt;Does it work with live keys, handle failed and refunded payments, and record the result in the database, or only the happy path in test mode?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Login works"&lt;/td&gt;
&lt;td&gt;Password reset, email verification, roles and permissions, and whether passwords are stored properly hashed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The admin panel is built"&lt;/td&gt;
&lt;td&gt;Do the screens save real data, or are they layouts wired to sample values?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"It's integrated with the API"&lt;/td&gt;
&lt;td&gt;Is it a real connection with error handling and retries, or one successful call made against a sandbox?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"It's basically finished"&lt;/td&gt;
&lt;td&gt;Is there a production environment at all, with backups, logging and HTTPS, or has it only ever run on a laptop?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Where are the secrets and the risks?
&lt;/h3&gt;

&lt;p&gt;Passwords and API keys committed into the code are common in rushed projects, and once they're in the repository history they have to be treated as exposed and replaced. I also look for the obvious security gaps that unfinished projects tend to have: no input validation, admin pages without proper authorisation checks, and outdated packages with known vulnerabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Is the data model sound?
&lt;/h3&gt;

&lt;p&gt;Code can be refactored a piece at a time. A database designed around the wrong idea of the business is much harder to fix once real data lives in it. If orders can't have more than one product, or a customer can only ever have one address, it's far better to find out now than after launch. For most projects this is fine. When it isn't, it's usually the deciding factor in the next question.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Is the stack something people can maintain?
&lt;/h3&gt;

&lt;p&gt;Mainstream frameworks such as ASP.NET Core, React, Node or Laravel can be picked up by plenty of developers. A home-made framework, an abandoned library at the core, or an unusual combination chosen because the old developer liked it means you'll be dependent on a small pool of people forever. That's a cost worth knowing before you invest more in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finish, fix or rebuild?
&lt;/h2&gt;

&lt;p&gt;The audit should end with a clear recommendation. In my experience it lands in one of three places:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;When it's the right call&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Finish it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It builds and runs, the structure is reasonable, and the gaps are missing features rather than broken foundations. The most common outcome.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fix, then finish&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The foundations are mostly right but some parts are unsafe or unreliable, such as payments, security or deployment. Those get repaired before new work goes on top.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rebuild&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It won't build or run, the data model is wrong for the business, or the stack is genuinely unmaintainable. Even then, the designs, the requirements and any working pieces are usually reusable.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Be cautious of a new developer who recommends rebuilding before they've read the code. Sometimes it's honest. Sometimes it's because writing new code is more pleasant than understanding someone else's. A rebuild restarts the clock on a project that's already late and throws away business rules someone already spent your money working out. It should be a conclusion from the audit, never the opening position.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It's 80% done" is a guess. An audit replaces the guess with a list, and a list is something you can actually budget.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What it realistically costs
&lt;/h2&gt;

&lt;p&gt;There are two separate costs, and they should be quoted separately. The &lt;strong&gt;audit&lt;/strong&gt; is a fixed piece of work, usually around a week. I quote rescue work this way, with the audit first, at my &lt;a href="https://sunnybadgujar.com/pricing.html" rel="noopener noreferrer"&gt;published day rate&lt;/a&gt;. Finishing the project is then estimated from what the audit found, feature by feature, rather than from what anyone remembers being promised.&lt;/p&gt;

&lt;p&gt;Expect the finishing estimate to be larger than "the remaining 20%" suggested. That isn't the new developer padding the quote. It's the hidden work every abandoned project carries: getting it deployable, fixing what was reported as done but isn't, and writing the documentation that should have existed. If someone offers a fixed price to finish your project without looking at the code first, they're either guessing or planning to cut the same corners that caused the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to make sure it never happens again
&lt;/h2&gt;

&lt;p&gt;Most of the pain in an abandoned project comes from a few setup decisions made on day one. On your next project, or the rest of this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Own every account.&lt;/strong&gt; The repository, the cloud account, the domain and every third-party service should be in your name, with the developer invited as a collaborator. When they leave, you remove their access, and nothing moves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay for working software, not progress reports.&lt;/strong&gt; Tie payments to features deployed to an environment you can click through yourself, and split the project into phases so no single payment covers more than a few weeks of work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insist on a handover note at each milestone.&lt;/strong&gt; One page covering how to run it, how to deploy it, and what's unfinished. It takes an hour to write and saves weeks if someone new has to pick it up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for silence early.&lt;/strong&gt; Slow replies and vague updates in week three are the warning sign. Ask to see the work running, not described.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how I set up every project I take on: the code lives in the client's repository and deploys to the client's own cloud account from the first commit, so nothing ever depends on a login only I have. It isn't about expecting to disappear. It means the client is never in the position this article is about.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Sunny Badgujar, a full-stack .NET, React, SQL Server and Shopify developer. I take over stalled and unfinished applications, especially &lt;a href="https://sunnybadgujar.com/legacy-dotnet-modernization.html" rel="noopener noreferrer"&gt;.NET backends&lt;/a&gt;, &lt;a href="https://sunnybadgujar.com/shopify-development.html" rel="noopener noreferrer"&gt;Shopify apps&lt;/a&gt; and &lt;a href="https://sunnybadgujar.com/booking-engine-development.html" rel="noopener noreferrer"&gt;booking systems&lt;/a&gt;. The full version of this article, with an FAQ, is on &lt;a href="https://sunnybadgujar.com/blog-take-over-unfinished-software-project.html" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How Long Does It Take to Build a Shopify Store?</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Thu, 17 Sep 2026 17:09:31 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/how-long-does-it-take-to-build-a-shopify-store-p1n</link>
      <guid>https://dev.to/sunny_badgujar_13/how-long-does-it-take-to-build-a-shopify-store-p1n</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sunnybadgujar.com/blog-how-long-to-build-a-shopify-store.html" rel="noopener noreferrer"&gt;sunnybadgujar.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"How long will my Shopify store take?" is usually the second question after "what will it cost?" — and it has the same honest answer: anywhere from a few days to a few months, depending on choices you've probably not made yet. But unlike cost, the timeline has one dominant variable most people don't expect. It isn't the code. It's how ready &lt;em&gt;you&lt;/em&gt; are. Here's a realistic breakdown of where the time actually goes.&lt;/p&gt;

&lt;p&gt;The reason "how long does a Shopify store take" gets such a vague answer is the same reason the cost question does: "a store" describes two very different jobs. Setting up a good theme, loading a tidy set of products and launching is genuinely a matter of days. Designing a bespoke storefront, migrating years of data from another platform and wiring the store into your warehouse and accounting is a matter of months. Both are "building a Shopify store," and the gap between them is enormous.&lt;/p&gt;

&lt;p&gt;So rather than promise a number that would be wrong for most people reading it, let's walk through what sets the timeline — the build type, the phases a real project moves through, and the levers that stretch it. If you're weighing time against budget, it's worth reading this alongside &lt;a href="https://sunnybadgujar.com/blog-cost-to-build-a-shopify-store.html" rel="noopener noreferrer"&gt;what a Shopify store costs to build&lt;/a&gt;, because the same choices drive both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest factor: theme, customised theme, or fully custom
&lt;/h2&gt;

&lt;p&gt;Just like cost, the timeline is decided first by how much of the store is bespoke. Here's the honest range for each level — assuming your content is ready, which, as you'll see, is a big assumption:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Build type&lt;/th&gt;
&lt;th&gt;What's involved&lt;/th&gt;
&lt;th&gt;Realistic timeline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Theme, set up well&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A quality theme configured to your brand, a small–medium catalog loaded, launched&lt;/td&gt;
&lt;td&gt;A few days to ~2 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customised theme&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A theme reshaped to your brand — custom sections, a few custom features, a product migration&lt;/td&gt;
&lt;td&gt;~3 to 8 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fully custom / headless&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A bespoke storefront or headless build with complex features and integrations&lt;/td&gt;
&lt;td&gt;A few months+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The most common case:&lt;/strong&gt; a good theme, customised where it matters, landing in the three-to-eight-week band. Most stores don't need — and shouldn't wait for — a fully custom build. If someone's promising you a bespoke, integrated store in a week, something in that promise isn't real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the weeks actually go: the phases of a build
&lt;/h2&gt;

&lt;p&gt;A store doesn't get built in one block of "development." It moves through phases, and each one takes real time — including time that's on your side of the table, not the developer's. For a typical customised-theme store, here's roughly how the weeks break down.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Discovery and planning — a few days to a week
&lt;/h3&gt;

&lt;p&gt;Before anything is designed, the build needs a clear brief: what you sell, how you sell it, which features are must-have, what it integrates with, and what's explicitly out of scope. This phase feels like it isn't "building," but skipping it is the single most reliable way to blow the timeline later — every decision deferred here comes back as rework halfway through. The faster you can answer these questions, the sooner the clock starts on real work.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Design — one to two weeks
&lt;/h3&gt;

&lt;p&gt;If you're customising a theme, this is choosing it and shaping the look — homepage, product page, brand feel, key sections. A pure theme setup compresses this to almost nothing; a genuinely custom design stretches it out, because every screen is drawn rather than adapted. Design also includes your feedback rounds, which is where client-side delay first shows up: a design that could be signed off in two days often takes two weeks because approvals sit in an inbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Build and configuration — one to three weeks
&lt;/h3&gt;

&lt;p&gt;This is the part people picture as "the whole project," and it's genuinely a fraction of it: implementing the design, configuring the theme, setting up collections, navigation, shipping, taxes and payments, and building any custom sections or features. Well-scoped custom features are predictable here; vague ones ("we'll figure out the bundle logic later") are where builds quietly slip.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Content and products — the phase everyone underestimates
&lt;/h3&gt;

&lt;p&gt;Loading your catalog — products, variants, images, descriptions, collections — takes real time, and it's usually the step that stalls, because the content often isn't ready. A store's shell can be finished and sitting there waiting weeks for product photography, or copy, or final pricing. This is the honest heart of the timeline: &lt;strong&gt;the store is rarely late because of development; it's late because the content to fill it isn't done.&lt;/strong&gt; More on this below, because it's the lever you control most.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Testing and launch — a few days
&lt;/h3&gt;

&lt;p&gt;Before going live, the store needs a real check: test orders through checkout, mobile and browser testing, broken-link and redirect checks, tax and shipping verification, and the launch itself — connecting your domain, and if you're migrating, switching over without dropping SEO. It's short, but it's not optional; the stores that launch with embarrassing bugs are the ones that skipped it to hit a date.&lt;/p&gt;

&lt;h2&gt;
  
  
  The levers that stretch the timeline
&lt;/h2&gt;

&lt;p&gt;On top of the build type, a handful of things reliably add weeks. If your project has several of these, plan for the upper end of every range above — or beyond it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A large or messy catalog.&lt;/strong&gt; Ten simple products load in an afternoon. Thousands of products with variants, options and images — especially if the data is inconsistent — is a project in itself, and no amount of developer speed changes how much data there is to get right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A migration from another platform.&lt;/strong&gt; Moving products, customers, orders and URLs off WooCommerce, Magento, Wix or a custom site is its own phase, usually one to a few weeks, and it can't be rushed without risking the SEO you already have. If you're replatforming rather than launching fresh, add this time deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom features and integrations.&lt;/strong&gt; Anything a theme and apps can't do off the shelf — a custom configurator, unusual bundle logic, a members' area, or syncing the store with your ERP, accounting or warehouse — is build time, not setup time. Integrations in particular can take longer than the storefront itself, because you're keeping two systems in agreement. This is the backend-heavy work I focus on, and it's worth scoping early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approval and feedback cycles.&lt;/strong&gt; This is the invisible one. A build's calendar time is often double its actual work time because of waiting — for feedback, for a decision, for a login, for the final logo. Prompt, consolidated feedback is one of the biggest things that keeps a project on schedule, and it costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that's on you: content readiness
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this, take this: the fastest-launching stores are the ones whose owners had their content ready before the build started. Real product photography, finished descriptions, final pricing, your logo and brand assets, your policy and about pages — when all of that is waiting to be loaded, the build flows. When it's being created &lt;em&gt;during&lt;/em&gt; the build, the store sits finished-but-empty, and the launch date drifts by exactly as long as the content does.&lt;/p&gt;

&lt;p&gt;Developers can't fix this from their side, and it's the most common reason a "three-week" store takes two months. So before you're anxious about how fast someone can build, get your products, photos and copy done. It's the single biggest lever you personally control, and it moves the timeline more than any technical choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to launch faster, honestly
&lt;/h2&gt;

&lt;p&gt;There's no trick that safely compresses a real build, but there are reliable ways to avoid losing weeks. Start from a strong theme instead of a fully custom design unless you truly need bespoke. Have your catalog and content ready to load on day one. Agree the scope clearly up front so nothing gets "figured out later" mid-build. Give feedback in prompt, batched rounds. And separate must-have features from nice-to-haves — you can launch a clean, selling store and add the extras later, rather than delaying revenue for polish. Almost every advanced feature can be added to a store that's already live.&lt;/p&gt;

&lt;p&gt;The stores that hit their dates aren't the ones with the fastest developer; they're the ones where the scope was clear, the content was ready, and decisions came back quickly. Get those three right and a customised Shopify store in a month is entirely realistic. Get them wrong and no timeline survives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shopify store timeline FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How long does it take to build a Shopify store?&lt;/strong&gt;&lt;br&gt;
A theme-based store set up well is usually a few days to a couple of weeks. A theme customised to your brand with some custom features and a product migration is typically three to eight weeks. A fully custom or headless store runs a few months. The single biggest variable isn't the code — it's how ready your products, photos, copy and decisions are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a Shopify store be built in a week?&lt;/strong&gt;&lt;br&gt;
Yes, if you keep it to a well-chosen theme, a small and simple catalog, no migration and no custom features — and your product photos and copy are ready to load on day one. The moment you add custom design, a large catalog, a migration or integrations, a week is no longer realistic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes a Shopify build take longer?&lt;/strong&gt;&lt;br&gt;
Custom design beyond a theme, a large or messy catalog, migrating data and URLs from another platform, custom features that themes and apps can't do, and integrations with your other systems all add time. But the most common cause of delay is client-side: product photos, descriptions and content that aren't ready, and slow rounds of feedback and approvals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I make my Shopify store launch faster?&lt;/strong&gt;&lt;br&gt;
Have your content ready before the build starts — product data, real photography and copy — because that's the most common thing that stalls a launch. Start from a strong theme instead of a fully custom design, agree the scope clearly up front, and give feedback in prompt, consolidated rounds rather than trickling changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does migrating to Shopify take?&lt;/strong&gt;&lt;br&gt;
A migration from another platform is its own phase, usually one to a few weeks depending on how many products, customers and orders you have and how clean that data is. It also has to preserve your URLs and SEO with proper redirects, which is careful work — rushing a migration is how stores lose search rankings they spent years building.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build Shopify stores end to end — theme customisation through to custom apps, migrations and the integrations that tie a store into your business. See my &lt;a href="https://sunnybadgujar.com/shopify-development.html" rel="noopener noreferrer"&gt;Shopify development service&lt;/a&gt; or &lt;a href="https://sunnybadgujar.com/contact.html" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>webdev</category>
      <category>business</category>
    </item>
    <item>
      <title>Sabre vs Amadeus vs Travelport: Which GDS Should Your Travel Portal Use?</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Thu, 17 Sep 2026 16:54:53 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/sabre-vs-amadeus-vs-travelport-which-gds-should-your-travel-portal-use-2eie</link>
      <guid>https://dev.to/sunny_badgujar_13/sabre-vs-amadeus-vs-travelport-which-gds-should-your-travel-portal-use-2eie</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sunnybadgujar.com/blog-sabre-vs-amadeus-vs-travelport-gds.html" rel="noopener noreferrer"&gt;sunnybadgujar.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;your&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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."&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three, at a glance
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GDS&lt;/th&gt;
&lt;th&gt;Typically strongest in&lt;/th&gt;
&lt;th&gt;Often chosen for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amadeus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Europe and broad global coverage&lt;/td&gt;
&lt;td&gt;The widest all-round airline and hotel content; strong for globally-minded portals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sabre&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;North America&lt;/td&gt;
&lt;td&gt;Deep US carrier and agency presence; portals focused on the Americas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Travelport&lt;/strong&gt; (Galileo / Apollo / Worldspan)&lt;/td&gt;
&lt;td&gt;A challenger across several regions&lt;/td&gt;
&lt;td&gt;Competitive terms, merchandising and NDC content; strong in select markets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The honest headline:&lt;/strong&gt; for most portals the deciding factor isn't which brand is "biggest," it's which one has the best &lt;em&gt;content and commercial terms for your specific market&lt;/em&gt;. A smaller GDS with a great deal and the right coverage beats the biggest name on paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  The factors that actually decide it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Your target market and routes
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Content: air, hotel, car — and NDC
&lt;/h3&gt;

&lt;p&gt;"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 &lt;strong&gt;NDC&lt;/strong&gt; (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.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Commercial terms and cost
&lt;/h3&gt;

&lt;p&gt;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 &lt;em&gt;you&lt;/em&gt; 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 &lt;a href="https://sunnybadgujar.com/blog-cost-to-build-a-booking-engine.html" rel="noopener noreferrer"&gt;what it costs to build a booking engine&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The API and the engineering reality
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you have to pick just one?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The right way to do it — and the way I've built it — is to put an &lt;strong&gt;abstraction layer&lt;/strong&gt; 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 &lt;a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html" rel="noopener noreferrer"&gt;how to integrate multiple GDS providers into one booking engine&lt;/a&gt;. And if you're leaning toward Amadeus specifically, there's a full walkthrough in &lt;a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html" rel="noopener noreferrer"&gt;integrating the Amadeus API into a B2B travel portal&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually decide
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;What you should &lt;em&gt;not&lt;/em&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  GDS comparison FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between Amadeus, Sabre and Travelport?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which GDS is best for a travel portal?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a travel portal use more than one GDS?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does GDS access cost?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a GDS or can I use direct airline APIs and NDC?&lt;/strong&gt;&lt;br&gt;
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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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 &lt;a href="https://sunnybadgujar.com/travel-portal-development.html" rel="noopener noreferrer"&gt;travel portal development service&lt;/a&gt; or &lt;a href="https://sunnybadgujar.com/contact.html" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>travel</category>
      <category>api</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to Integrate the Amadeus API into a B2B Travel Portal</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Fri, 04 Sep 2026 22:32:19 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/how-to-integrate-the-amadeus-api-into-a-b2b-travel-portal-4acj</link>
      <guid>https://dev.to/sunny_badgujar_13/how-to-integrate-the-amadeus-api-into-a-b2b-travel-portal-4acj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sunnybadgujar.com/blog-integrate-amadeus-api-b2b-travel-portal.html" rel="noopener noreferrer"&gt;sunnybadgujar.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Amadeus is where most travel portals start, and for good reason: it's the largest GDS, the content is deep, and the modern &lt;em&gt;Amadeus for Developers&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  First decision: Self-Service or Enterprise?
&lt;/h2&gt;

&lt;p&gt;Before you write a line of code, you have to know which Amadeus you're building against, because they are not the same product.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amadeus Self-Service APIs.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amadeus Enterprise (the full travel platform).&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Practical advice:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything that follows uses the Self-Service REST flow, because that's where 90% of new B2B portals begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: one token, cached, refreshed
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://test.api.amadeus.com/v1/security/oauth2/token
Content-Type: application/x-www-form-urlencoded

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

// response
{ "access_token": "abc123...", "expires_in": 1799, "token_type": "Bearer" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two environments, two base URLs:&lt;/strong&gt; &lt;code&gt;test.api.amadeus.com&lt;/code&gt; serves cached, non-live data for development; &lt;code&gt;api.amadeus.com&lt;/code&gt; is production with live pricing and real bookings. They use different credentials. Make the base URL and keys configuration, never hard-coded — you &lt;em&gt;will&lt;/em&gt; switch between them constantly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The core flight flow is three calls, in order
&lt;/h2&gt;

&lt;p&gt;This is the single most important thing to internalise about Amadeus, and it maps directly to how travel actually works: &lt;strong&gt;search is indicative, price is live, and only then can you book.&lt;/strong&gt; Three endpoints, always in this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Flight Offers Search&lt;/strong&gt; — &lt;code&gt;POST /v2/shopping/flight-offers&lt;/code&gt;. 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flight Offers Price&lt;/strong&gt; — &lt;code&gt;POST /v1/shopping/flight-offers/pricing&lt;/code&gt;. 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flight Create Orders&lt;/strong&gt; — &lt;code&gt;POST /v1/booking/flight-orders&lt;/code&gt;. Send the confirmed offer plus traveller details, and Amadeus creates the booking (a PNR). This is the step that actually reserves seats.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Booking is two steps for the same reason.&lt;/strong&gt; Confirm price live, &lt;em&gt;then&lt;/em&gt; take payment, &lt;em&gt;then&lt;/em&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Now the part the docs don't cover: this is a B2B portal
&lt;/h2&gt;

&lt;p&gt;A consumer booking site has one customer type. A B2B portal has &lt;em&gt;agents&lt;/em&gt; — 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Multi-tenancy and roles
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The markup engine
&lt;/h3&gt;

&lt;p&gt;Amadeus returns &lt;strong&gt;net fares&lt;/strong&gt;. Your agents must never see the net — they see the price &lt;em&gt;after&lt;/em&gt; 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// conceptual: applied server-side, never in the client&lt;/span&gt;
&lt;span class="n"&gt;sellFare&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;netFare&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;resolveMarkup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agencyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fareClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// the agent sees sellFare; the net stays server-side only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Agent wallets and credit
&lt;/h3&gt;

&lt;p&gt;B2B agents don't pay by card per booking. They top up a &lt;strong&gt;wallet&lt;/strong&gt; (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 &lt;em&gt;before&lt;/em&gt; you call Flight Create Orders, then debit atomically as part of confirming the booking.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model the wallet as an &lt;strong&gt;immutable ledger&lt;/strong&gt; — 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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Cancellations and refunds credit the wallet back, minus any airline penalty — as a background job with retries, because refund calls fail transiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Credential and quota management
&lt;/h3&gt;

&lt;p&gt;On Self-Service, the whole portal typically shares one set of production Amadeus credentials, so your rate limiting and quota tracking are &lt;em&gt;portal-wide&lt;/em&gt; — 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.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Amadeus behind an adapter — even if it's your only supplier
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;FlightOffer&lt;/code&gt;, a &lt;code&gt;PriceBreakdown&lt;/code&gt;, a &lt;code&gt;BookingResult&lt;/code&gt; — 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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html" rel="noopener noreferrer"&gt;how to integrate multiple GDS providers into one booking engine&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching, rate limits and the test-data trap
&lt;/h2&gt;

&lt;p&gt;GDS calls cost money and are rate-limited, so treat them accordingly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache searches briefly.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limit per agency, inside your layer.&lt;/strong&gt; As above, the shared Self-Service quota means one tenant can starve the rest unless you police it yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember the test environment is not live.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The admin panel is half the product
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls I'd flag before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never skip Flight Offers Price.&lt;/strong&gt; Search prices are indicative; confirm live before every charge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never let the net fare reach the browser.&lt;/strong&gt; Apply markup server-side; store both net and sell on the booking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make wallet and booking atomic.&lt;/strong&gt; A retried booking or refund must never double-charge or double-credit an agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't hard-code the environment.&lt;/strong&gt; Test and production have different URLs and keys — make them configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't call Amadeus directly from business code.&lt;/strong&gt; The adapter is what lets you grow from Self-Service to enterprise, and from one supplier to many.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every request and response for audit.&lt;/strong&gt; "What exactly did we send and what did they say" is a question you'll be asked constantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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 &lt;a href="https://sunnybadgujar.com/travel-portal-development.html" rel="noopener noreferrer"&gt;travel portal development work&lt;/a&gt; or &lt;a href="https://sunnybadgujar.com/contact.html" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>travel</category>
      <category>api</category>
      <category>dotnet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Integrate Multiple GDS Providers Into One Booking Engine</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Sat, 29 Aug 2026 22:23:34 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/how-to-integrate-multiple-gds-providers-into-one-booking-engine-jmk</link>
      <guid>https://dev.to/sunny_badgujar_13/how-to-integrate-multiple-gds-providers-into-one-booking-engine-jmk</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://sunnybadgujar.com/blog-integrate-multiple-gds-providers-booking-engine.html" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Amadeus, Sabre, Travelport and direct supplier feeds all model the same trip differently. Here's the architecture that lets a single booking engine search, price, book and refund across all of them without the whole thing falling over when one provider has a bad day.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I've built and run a &lt;a href="https://sunnybadgujar.com/portfolio.html" rel="noopener noreferrer"&gt;travel reservation platform&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one rule: your app never speaks GDS
&lt;/h2&gt;

&lt;p&gt;The single most important decision is that &lt;strong&gt;nothing above the integration layer knows which provider a result came from.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Concretely, you define your own normalised types — a &lt;code&gt;FlightOffer&lt;/code&gt;, a &lt;code&gt;HotelOffer&lt;/code&gt;, a &lt;code&gt;PriceBreakdown&lt;/code&gt; — and every provider adapter is responsible for translating &lt;em&gt;into&lt;/em&gt; 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IFlightProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FlightOffer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SearchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FlightQuery&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PricedOffer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;PriceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;offerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BookingResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;BookAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BookRequest&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search: fan out, but expect stragglers
&lt;/h2&gt;

&lt;p&gt;A search request goes to every relevant provider &lt;strong&gt;in parallel&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Practical detail:&lt;/strong&gt; return partial results with a flag. The UI can show "still searching Provider X…" and stream late results in, rather than making the user stare at a spinner because one GDS is having a slow morning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching and rate limits are not optional
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short-lived search cache.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting per provider.&lt;/strong&gt; Each adapter enforces the supplier's own limit so a burst of traffic doesn't get you throttled or temporarily banned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trap here is caching too aggressively. Fares move. Which brings us to the hardest problem in the whole system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The price-drift problem (and why booking is two steps)
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The fix is a mandatory &lt;strong&gt;re-price / confirm step&lt;/strong&gt; before payment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Customer selects an offer.&lt;/li&gt;
&lt;li&gt;You call the provider's &lt;em&gt;price&lt;/em&gt; or &lt;em&gt;confirm availability&lt;/em&gt; endpoint for that exact offer, live.&lt;/li&gt;
&lt;li&gt;If the price still matches — proceed to payment.&lt;/li&gt;
&lt;li&gt;If it changed — show the new price and make the customer re-confirm before charging them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failover: one provider down ≠ platform down
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Booking, payment and the refund pipeline
&lt;/h2&gt;

&lt;p&gt;Booking is where money and state meet, so it has to be transactional in spirit even though the GDS itself isn't. The pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm price live (above).&lt;/li&gt;
&lt;li&gt;Authorise payment.&lt;/li&gt;
&lt;li&gt;Issue the booking with the provider.&lt;/li&gt;
&lt;li&gt;If the provider booking fails &lt;em&gt;after&lt;/em&gt; a successful charge, you must automatically void or refund the payment — never leave a customer charged for a booking that didn't happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The admin panel is half the product
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls I'd flag before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't leak provider types upward.&lt;/strong&gt; The first time a Sabre-specific field shows up in your UI code, the abstraction is broken and it only gets worse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat every GDS call as unreliable.&lt;/strong&gt; Timeouts, retries and circuit breakers everywhere — these APIs fail more than internal services do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never book off a search price.&lt;/strong&gt; Always re-confirm live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make money movements idempotent.&lt;/strong&gt; A retried booking or refund must not double-charge or double-refund.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log for audit from day one.&lt;/strong&gt; In travel, "what exactly did we send the supplier and what did they say" is a question you will be asked constantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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, &lt;a href="https://sunnybadgujar.com/contact.html" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>api</category>
      <category>architecture</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Why Your SQL Server Database Is Slow (and How to Fix It)</title>
      <dc:creator>Sunny Badgujar</dc:creator>
      <pubDate>Sat, 29 Aug 2026 22:21:25 +0000</pubDate>
      <link>https://dev.to/sunny_badgujar_13/why-your-sql-server-database-is-slow-and-how-to-fix-it-239d</link>
      <guid>https://dev.to/sunny_badgujar_13/why-your-sql-server-database-is-slow-and-how-to-fix-it-239d</guid>
      <description>&lt;p&gt;"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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Missing indexes — the number one culprit
&lt;/h2&gt;

&lt;p&gt;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. On a small table nobody notices. 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.&lt;/p&gt;

&lt;p&gt;The good news is this is often the single biggest win available, and it's low-risk. Adding a well-chosen index to a column you filter or join on regularly can turn a multi-second query into an instant one, with no application changes at all. SQL Server will even tell you which indexes it wishes it had — the trick is knowing which of those suggestions to trust and which to ignore, because too many indexes slow down writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The N+1 query problem
&lt;/h2&gt;

&lt;p&gt;This one hides inside the application code, and it's everywhere. It looks like this: 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 the database to show a single page. With ORMs like Entity Framework it's especially easy to write by accident, because the extra queries are invisible in the C# — they only show up when you watch what actually hits the database.&lt;/p&gt;

&lt;p&gt;Each individual query is fast, so nothing looks wrong in isolation. But the round trips add up, and under load they multiply. The fix is usually to load the related data up front in one query instead of hundreds.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. SELECT * and pulling back more than you need
&lt;/h2&gt;

&lt;p&gt;When a query grabs every column and every row "just in case," the database has to read, transfer and materialise all of it — including large text and image columns the page never even displays. Multiply that by every user hitting the page and you're moving far more data than the feature actually needs.&lt;/p&gt;

&lt;p&gt;Asking only for the columns and rows you'll use — and paging large lists instead of loading ten thousand records into a dropdown — cuts the work at the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Queries that can't use their indexes
&lt;/h2&gt;

&lt;p&gt;Sometimes the index exists and the query still ignores it. This usually comes down to how the query is written: 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.&lt;/p&gt;

&lt;p&gt;These are subtle, and you only find them by reading the execution plan — SQL Server's own explanation of how it ran the query. Reading plans is most of what practical performance tuning really is; the fixes are often a one-line change once you can see the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Blocking and locking under load
&lt;/h2&gt;

&lt;p&gt;If the app is fine when it's quiet and falls apart when everyone's using it, the problem may not be any single slow query — it 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, waiting. Users experience this as random freezes that are impossible to reproduce on a quiet test environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Statistics and fragmentation the server forgot to maintain
&lt;/h2&gt;

&lt;p&gt;SQL Server decides how to run a query based on internal statistics about your data. If those go stale, it can start making bad decisions, choosing a slow plan because its picture of the data is out of date. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Only now: is it actually the hardware?
&lt;/h2&gt;

&lt;p&gt;Sometimes, after all of the above, the honest answer is yes — the workload has genuinely outgrown the machine. 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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How I approach a slow database
&lt;/h2&gt;

&lt;p&gt;The method is the same every time, and it's deliberately boring: measure first, guess never. Find the queries that actually hurt (SQL Server tracks which run most often and cost the most time), read each one's execution plan to find the real cause, apply one targeted fix, then re-measure to prove it helped — and know when to stop.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Sunny Badgujar, a freelance full-stack .NET developer. I do &lt;a href="https://sunnybadgujar.com/services.html" rel="noopener noreferrer"&gt;SQL Server performance audits and query tuning&lt;/a&gt; for .NET applications — find the real bottleneck, fix it, and prove the difference with numbers. If your app is slow and you're not sure why, &lt;a href="https://sunnybadgujar.com/contact.html" rel="noopener noreferrer"&gt;tell me what you're seeing&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
