← All work
Travel, 2023 – 2026

Sincere Travels — Amadeus GDS booking

Flight search, PNR, e-ticketing and ConnectIPS payments, straight against Amadeus SOAP and NDC web services.

Role
Lead backend engineer, main author
Context
Sincere Travels, Kathmandu
Scale
~250 of 477 commits, ~20 Prisma models, 2 Koa services, 3 years
Live
sinceretravels.com
paidBooking siteReactConsumer Koa APIRules enginecommissions · taxesDashboard APIairlines · logs · rulesMasterPricer searchAir_Sell + PNRPrice + TSTTicket issueper passenger refConnectIPSRSA-signedBullMQ workersPDF + emailAmadeus audit logevery request/response
Sincere Travels: the booking flow against Amadeus, with payment and ticket delivery as separate stages so a failure never double-issues.

Background

Sincere Travels sells flights online in Nepal. The backend talks directly to the Amadeus global distribution system rather than through a reseller API. I own the backend: a consumer API behind the booking site and a dashboard API for the operations team.

The problem

Amadeus web services are SOAP and XML with WS-Security headers, stateful sessions and a booking flow of eight or more calls where any one can fail after money has moved. There is no SDK. Reseller APIs hide this but take a cut on every ticket and lag on availability.

The business also needed its own pricing: airline commissions, per-segment rules and Nepali taxes layered on the Amadeus fare, editable by staff without a deploy. And the whole thing had to take payment through Nepali gateways, not Stripe.

Approach

  1. Implement the classic GDS flow by hand: Fare_MasterPricerTravelBoardSearch for search, Fare_CheckRules, Air_SellFromRecommendation, PNR_AddMultiElements, Fare_PricePNRWithBookingClass, Ticket_CreateTSTFromPricing, then ticket issue per passenger reference. Typed envelopes with xmlbuilder and fast-xml-parser.
  2. Add the Amadeus NDC channel alongside: OfferPrice, OrderCreate, OrderPay, OrderRetrieve. NDC offers are de-duplicated against identical non-NDC fares so the customer sees one price.
  3. Build a rules engine: airline commissions, segment rules, active pricing rules and NQ taxes as data, all editable from the dashboard API.
  4. Payments through ConnectIPS with RSA-signed requests, plus Fonepay and Khalti. Guards against double ticket issue and against price drift between quote and issue.
  5. Ticket delivery as a BullMQ job: Puppeteer renders a Handlebars template to PDF and emails it. Separate queues for test and production, monitored with Bull Board.
  6. Operational safety: an audit log of every Amadeus call, a DB-backed rate limiter, OTP accounts with RSA-signed JWTs, and a hand-written OpenAPI 3 spec served at /api/docs.

Architecture

Consumer Koa APISearch, pricing, booking, ticketing, payments, accounts.
Dashboard Koa APIRules, commissions, airlines, Amadeus logs, transactions, users.
workersBullMQ email and ticket PDF workers, Bull Board.
sincere-travels-web-appReact + Vite booking site. Team-built, I contributed.
sincere-travels-adminMantine dashboard. I built the API behind it.

Outcome

  • Live at sinceretravels.com since 2023, issuing real tickets against Amadeus production.
  • NDC channel added in 2026 without changing the customer-facing search contract.
  • Largest single share of commits on the backend over three years.

Lessons

  • For a stateful vendor flow, log every request and response before anything else. The Amadeus audit log solved more support tickets than any other feature.
  • Put money guards (double issue, price drift) in the service layer, not the UI. They caught cases the UI never saw.

Stack

TypeScriptKoaPrismaKyselyPostgreSQLBullMQRedisAmadeus SOAP/XMLAmadeus NDCPuppeteerConnectIPS