Skip to content
Skip article header Engineering

Booking Engine Development

Booking engine development for a flights-first product: what happens at search, price, hold, order and ticket, where each state fails and recovers and when to build against a self-service aggregator versus license or white-label instead.

12 min read 17 views
A whiteboard state diagram of boxes and arrows behind a passport and a printed itinerary on an empty engineering desk, the chain of states a booking engine moves a reservation through
Skip key takeaways

In short: a flight booking engine is not a single API call, it is a chain of six distinct states, search, price, hold, order, ticket and service, and most of the failures a production system hits happen at the seam between two of those states rather than inside any one of them. A search result is not a bookable price, a successful order response does not guarantee a retrievable booking and a PNR is not a ticket. Building a direct booking flow for a travel software product, flights first and hotel inventory second, means designing for those seams from day one rather than discovering them after launch.

Search looks like the simplest state in the chain and is the one most teams get wrong first, because they treat the fare a search call returns as a price they can book. It is not. Amadeus's own developer guide is blunt about why a second confirmation step exists: "This is especially true if time passes between the initial search and the decision to book, as fares are limited and there are thousands of bookings occurring every minute." We treat that volatility as a design constraint rather than an edge case: any search result a user can sit with for more than a few seconds needs a re-price step before it reaches a payment form, and we scope that step from the first sprint rather than adding it after a support ticket about a price mismatch.

The tier itself is not one endpoint either. Some tiers of a self-service aggregator's search surface, per Amadeus's own self-service developer guide, are served from a precomputed cache rather than from live availability, which is why coverage on those endpoints is partial rather than universal, and a build that assumes every search response reflects real-time inventory will eventually surface a fare that a live search would never return. The same aggregator route enforces published rate ceilings, a fixed transactions-per-second cap in test that steps up in production, plus a minimum gap between requests, so a search architecture that fans out unthrottled requests against the aggregator hits a wall long before it hits a real capacity limit. We size caching and throttling to those published ceilings as our default pattern, rather than to whatever number a load test happens to find, because the vendor's own published limits are the actual constraint, not our own assumption of average traffic. This is the same distribution economics our travel software development guide covers at the platform level, and it is exactly the live-search dependency our AI travel agent development work runs into once a conversational assistant starts grounding recommendations against a real search call instead of a cached snapshot.

Price

A price confirmation call exists precisely because a search result is a shown fare, not a locked one. Where search returns an indicative price, confirmation returns the number a payment can actually be built against, taxes and fees included, and it is the only step in the flow that guarantees the fare rather than displays it. That confirmed price then carries its own clock: the guarantee it expresses is conditional on booking the same day the search happened, and it holds only until a stated ticketing deadline rather than indefinitely.

Offers built this way also carry a short, explicit shelf life rather than an implicit one. Duffel's own offers documentation states plainly that "an offer is only available to create an order for a limited time by the traveller before it expires, typically within 30 minutes." Once that window closes, the offer cannot be used to create an order at all, and the aggregator treats a booking attempt against an expired offer as a defined error rather than a generic failure, with a prescribed recovery of requesting a fresh offer rather than retrying the stale one. We build that recovery path into the booking form itself, a silent re-search behind a price-changed banner, rather than surfacing the raw error and asking the customer to start over.

Hold and payment

Whether a booking can be held and paid for later is a property of the specific fare, not a product decision your team gets to make unilaterally. Some fares require a payment card to be present at the moment the order is created, and even where a hold is technically possible, the aggregator only permits it when the underlying offer does not require instant payment. A pay-later button on the checkout page is therefore conditional on what the fare allows, and a build that promises it unconditionally will eventually hit a fare that refuses the hold.

An authorization hold is also a clock, not a reservation. Per Stripe's own documentation on holds, card networks typically hold funds for around seven days on an online payment and shorter, around two days, on an in-person terminal payment, and per Stripe's API reference, an uncaptured authorization is canceled by default seven days after it was created unless the merchant configures otherwise. Not every payment method supports splitting authorization from capture in the first place, which narrows which methods a hold-then-charge design can actually offer at checkout. Capturing outside the permitted window fails with an explicit, named error rather than a silent decline, and the window itself is set by the scheme and the acquirer's own configuration rather than being a fixed universal number. Even a capture the gateway reports as successful can still be reversed by the scheme or the issuer afterward, sometimes days later, so a booking engine has to reconcile its own payment state against the gateway's asynchronous notifications rather than trusting the synchronous response alone. This is the same reconciliation discipline our payment solutions development practice builds into every checkout flow that separates a hold from a charge.

Order

A small product team reviewing a booking record on a large screen at a standing desk, checking whether a reservation exists after a slow order-creation call

Order creation is where the slowest and least forgiving failure mode in the whole flow lives, because the call to the airline or accommodation supplier behind it is not guaranteed to be fast. Duffel's own error-handling documentation states the constraint directly: "Airline and Accommodation APIs can occasionally be slow, taking up to 120s. You must set a HTTP client timeout of at least 130s". A client that times out at the common default of 30 or 60 seconds will abandon an order that is still being created upstream, and the retry that follows is now racing against a reservation that may complete anyway.

Worse, a successful-looking response does not always mean the booking is done. The same documentation describes a 200 response that means the request succeeded and a new resource has not yet been created, only that it will become retrievable once the supplier system finishes processing it, sometimes hours later. Retrying the create call on that response risks a duplicate booking; the documented recovery is to wait for a webhook notification or to poll by listing orders until the record appears, never to resubmit the original request. We build that reconciliation path, the timeout above 130 seconds and a listing-based fallback rather than a naive retry, into the order-creation integrations we deliver, because the failure mode is structural to how these APIs work rather than specific to one vendor.

The pattern we use to keep a slow OrderCreate call safe is to treat a timeout as unknown rather than failed. Before the request goes out, we generate an idempotency key and persist it against the booking attempt, so the client has something durable to reconcile against once the call outlives its own timeout. If the response never arrives, we do not resubmit; we reconcile first, retrieving the order by that key, or by passenger and itinerary when the key path is unavailable, and only issue a fresh OrderCreate once that lookup comes back empty. A duplicate booking created by a blind retry is not a cosmetic bug: it can mean a second fare held or ticketed against the same passenger, a refund path that depends on which fare rules apply, and in an agency setting a possible debit memo from the airline.

Ticket

A reservation record is not a ticket, and conflating the two is the single most common scoping mistake in this part of the flow. Amadeus's own glossary draws the line precisely: "Ticket issuance finalizes the booking when the airline receives payment. Until this happens, the reservation is not valid for travel." A PNR created at the order step reserves inventory and nothing more; the passenger has no valid ticket until issuance happens on top of it.

Issuance itself is gated commercially rather than technically. A self-service aggregator tier cannot issue a ticket on its own at all, issuance runs through a consolidator instead, and only a certified travel agent, or a platform holding an IATA or ARC license, is permitted to issue directly. That license sits behind an enterprise contract rather than the self-service tier most teams start on, which means a roadmap that promises direct ticket issuance on day one is promising something the underlying platform does not yet permit. Party size adds a second, smaller constraint worth scoping early: a single PNR is capped at nine passengers, so a group booking beyond that number has to split across records, a detail that changes how a group-checkout flow has to be built rather than a footnote to fix later. The same distribution-standard questions come up whenever a build sits closer to the airline side of this chain, which is the ground our NDC API integration guide covers for teams connecting directly rather than through an aggregator.

Service

Once a ticket is issued, control of the booking does not stay with your own code. Post-issuance servicing, an exchange, a name correction, a schedule-change response, runs through the same consolidator that issued the ticket, not through the platform that created the order. A servicing screen that calls back into your own booking API for a change made after issuance is calling the wrong system; the consolidator's back office is now the system of record.

Margin is constrained the same way inventory is. The fare content reachable through a self-service catalog is published GDS rates only, not the negotiated or private fares a larger travel business might otherwise access, so a margin model built entirely on the aggregator route has a ceiling baked into the product from day one. Teams that outgrow that ceiling move some or all of their volume to a direct airline relationship, and that decision is exactly the build-versus-license tradeoff the next section works through.

State What can fail Recovery
Search Cached tier returns a fare a live search would not Re-price every result before checkout, never book directly off a search response
Price Confirmed offer expires before checkout completes Treat expiry as a modeled error; silently re-request a fresh offer
Hold and payment Authorization expires before capture, or the fare refuses a hold outright Capture inside the authorization window; fall back to instant payment when a hold is not offered
Order A 200 response arrives before the reservation is retrievable Wait for the webhook, or poll by listing orders, never resubmit the create call
Ticket Issuance is attempted without the required accreditation Route issuance through a consolidator, or hold direct issuance for the licensed tier
Service A post-issuance change is sent to the wrong system of record Route servicing requests to the consolidator that issued the ticket

Build, license or white-label

Everything above is a case for owning the state machine yourself, and everything above is also exactly what a self-service aggregator route hands you for free. Production access on that route is gated on registering in an approved market, meeting local legal requirements and working with a consolidator to issue tickets, conditions that exist regardless of which aggregator a team picks. Choosing to build, license a white-label engine or stay on a self-service aggregator is therefore a decision about who owns those gates, not a decision about which vendor has the nicer documentation.

We walk a client through the same three questions before recommending a route.

  1. Does the roadmap need direct ticket issuance, or is routing through a consolidator acceptable for the volume in play? Direct issuance needs an IATA or ARC license on an enterprise contract, not the self-service tier most teams start on. This question decides the route mostly for a startup that has no consolidator relationship yet.
  2. Does the margin model depend on fare content beyond published GDS rates? If it does, a self-service catalog is a ceiling, not a starting point, and the build-versus-license question resolves toward a direct relationship sooner rather than later. This is usually the deciding question for an agency already sitting on a GDS contract, since that is where negotiated fares would otherwise come from.
  3. How much integration effort can the team actually validate before launch? A sandbox environment routes to airline systems the aggregator itself will not warrant, so a test pass that looks clean in the sandbox is evidence of correctness in the sandbox, not a guarantee of production behavior, and effort estimates should budget for that gap rather than assume it away. This weighs heaviest for a supplier connecting its own inventory, where the aggregator's sandbox guarantees the least and validation has to be built in-house.

None of the three routes is universally correct, and a flights-first build has one more branch worth naming honestly: hotel inventory runs on a different connectivity model entirely, which is why we scope it as a separate thread rather than an extension of the flight booking flow, covered in our hotel channel manager integration guide. What decides the route for the flight side is rarely the technology; it is which of these constraints, the issuance gate, the fare-content ceiling or the testing gap, the business can least afford to inherit from someone else's platform. If a single number ends up deciding the client architecture on a travel software build, it is usually that 130-second timeout on order creation, because every retry policy and every loading state in the booking flow has to be designed around a call that can legitimately still be running when a user expects an answer.

FAQ

Last updated:

Quick answers to common questions about custom software development, pricing, process and technology.

  • Copy link Copies a direct link to this answer to your clipboard.

    A booking engine has to carry a reservation through six distinct states, search, price, hold, order, ticket and service and most production failures happen at the seams between them rather than inside any single call. Treating "book a flight" as one API call rather than a state machine is the most common scoping mistake.

  • Copy link Copies a direct link to this answer to your clipboard.

    Fares are volatile and thousands of bookings happen every minute, so the price a search returns is not guaranteed. A separate price-confirmation step returns the final tax-inclusive price and that confirmed price is only valid for ticketing until a stated deadline, typically if booked the same day as the search.

  • Copy link Copies a direct link to this answer to your clipboard.

    Order creation can occasionally take up to 120 seconds, so the client needs a timeout of at least 130 seconds. A successful-looking response can also mean the reservation exists in the supplier system but is not yet retrievable, which recovers through a webhook or by listing orders rather than by retrying the create call.

  • Copy link Copies a direct link to this answer to your clipboard.

    Only if it is a certified travel agent, or holds an IATA or ARC license, typically only available on an enterprise contract. Self-service aggregator tiers cannot issue tickets themselves; issuance and most post-booking servicing run through a consolidator instead.

  • Copy link Copies a direct link to this answer to your clipboard.

    Typical card authorization windows run around 7 days for online payments and shorter for in-person terminal payments and an uncaptured authorization is commonly canceled after 7 days by default. Not every payment method supports splitting authorization from capture, which constrains a hold-then-charge design.

  • Copy link Copies a direct link to this answer to your clipboard.

    A self-service aggregator route gets you to market faster but gates ticket issuance behind certification or a consolidator, caps party size and returns published fares only. A custom build removes those gates at the cost of owning more of the state machine yourself; many teams start on the aggregator route and move pieces in-house as volume justifies it.

I work with startup founders who need a dedicated software development team but don’t want to gamble on hiring, random outsourcing, or opaque delivery.
Most founders face the same problem sooner or later.
Early technical and team decisions lock the product into tech debt, slow delivery, missed milestones and constant re-hiring. By the time this becomes visible, fixing it is already expensive.

As a CTO and software architect, I help founders design, build and run dedicated development teams that work as a true extension of the startup. Not as a black-box vendor.

My focus is on complex products where mistakes are costly:

  • Web3 and blockchain platforms
  • FinTech and regulated products
  • High-load startup systems
  • MVP → scale transitions

We don’t do body-shopping.
We don’t sell generic outsourcing.

Instead, we help founders:

  • build the right team structure from day one
  • keep technical ownership and transparency
  • scale delivery without losing control
  • avoid vendor lock-in and hidden risks

Teams are aligned with the product roadmap, business goals and long-term architecture. Not just short-term velocity.

Dmytro Nasyrov, Founder and CTO at Pharos Production
Dmytro Nasyrov Founder & CTO Let's work together!

Your business results matter

Achieve them with minimized risk through our bespoke innovation capabilities

Your contact details
Please enter your name
Please enter a valid email address
Please enter your message
* required

We typically reply within 4 hours

What happens next?

  1. Contact us

    Contact us today to discuss your project. We're ready to review your request promptly and guide you on the best next steps for collaboration

    Same day
  2. NDA

    We're committed to keeping your information confidential, so we'll sign a Non-Disclosure Agreement

    1 day
  3. Plan the Goals

    After we chat about your goals and needs, we'll craft a comprehensive proposal detailing the project scope, team, timeline and budget

    3-5 days
  4. Finalize the Details

    Let's connect on Google Meet to go through the proposal and confirm all the details together!

    1-2 days
  5. Sign the Contract

    As soon as the contract is signed, our dedicated team will jump into action on your project!

    Same day