Published 2026-05-05 · Last updated 2026-05-24
Building a Scalable NDC API Layer
A practical architecture for separating airline complexity from seller-facing product logic in multi-airline NDC systems.
Why multi-airline integrations become impossible to maintain
Most NDC programs start with a simple promise: connect to one airline, expose search and booking, and move on. That works until the second airline arrives. Then the platform begins to fracture. One airline returns offers in one shape, another uses a different version of the same message, a third has a different order lifecycle, and a fourth fails in ways the frontend cannot explain.
The usual reaction is to patch the cracks. Teams add special cases inside adapters, push airline-specific logic into the API, and let the UI learn too much about each carrier. That creates a system where every new airline makes the platform harder to reason about. The problem is not the number of integrations alone. It is that raw airline complexity leaks into product code, support workflows, analytics, and user experience.
What NDC changes
NDC is IATA’s standard for airline retailing messages. It lets airlines expose richer content than older distribution models, including offers, ancillaries, pricing detail, and post-booking actions. The important detail is that a standard does not mean every airline behaves the same way. Airlines still differ in supported versions, optional fields, offer rules, error formats, and servicing flows.
That is why NDC platforms need translation, normalization, and clear product boundaries. The standard gives you a common language. The architecture decides whether that language stays manageable.
Core idea: separate airline complexity from product complexity
A scalable NDC layer needs three distinct parts: thin airline adapters, a normalized domain model, and a seller-facing API. The adapters translate to and from each airline or provider. The normalized model defines the platform’s own meaning for search, offers, pricing, orders, payment, fulfillment, servicing, refunds, and exchange flows. The seller-facing API exposes that model consistently to web apps, agent desktops, mobile apps, and partners.
This separation matters because it stops every frontend from learning every airline difference. It also prevents the platform from becoming a pile of one-off transformations. If the same business concept appears in five different shapes across five airlines, the normalized model should absorb that variation once. Everything above it should work against a stable contract.
Why adapters stay thin, and why normalization belongs centrally
Adapters should be narrow and explicit. Their job is to translate between the canonical domain and a specific airline implementation. They can handle authentication, message formats, version differences, airline error codes, and certification requirements. They should not contain general retailing policy, ranking logic, screen-specific assumptions, or product decisions that are supposed to be shared across channels.
Normalization belongs centrally because consistency is a platform concern, not an airline concern. If each adapter or frontend normalizes data independently, teams end up with multiple versions of the truth. Search sees one shape, booking sees another, support sees a third, and analytics sees a fourth. A central normalization layer makes required fields, currency handling, status mapping, service grouping, and error classification behave the same way everywhere.
Architecture walkthrough
The cleanest mental model is a translation pipeline with a stable core. Seller applications talk to one contract. The API layer normalizes the request and response. Capability metadata decides whether the workflow is supported. Then the router sends the request to the correct adapter, which handles the airline-specific details.
flowchart LR
Seller[Seller Apps\nWeb, Agent Desktop, Mobile, Partners] --> API[Seller-facing API]
API --> Norm[Normalization Layer]
Norm --> Domain[(Canonical NDC Domain Model)]
Norm --> Cap[Capability & Policy Layer]
Cap --> Router[Airline / Provider Router]
Router --> A1[Airline Adapter A]
Router --> A2[Airline Adapter B]
Router --> A3[Aggregator Adapter]
A1 --> NDC1[(Airline NDC API)]
A2 --> NDC2[(Airline NDC API)]
A3 --> AGG[(Aggregator API)]
A1 --> Obs[Logs, Traces, Metrics]
A2 --> Obs
A3 --> Obs
Norm --> Obs
API --> ObsThe diagram is intentionally simple. The point is not to hide complexity. The point is to keep complexity in the right place. Product code should never need to know which airline uses which request shape or which provider requires which version. That knowledge belongs below the normalization layer.
Design the domain model around the entire journey
A common mistake is to design only for shopping. That can produce a nice search experience, but it fails as soon as the organization needs booking support, order servicing, refunds, or exchanges. The domain model should describe the complete journey from discovery to post-booking service.
- Search - capture trip intent, passenger mix, and commercial filters.
- Offers - represent fare families, segments, ancillaries, rules, and time limits.
- Pricing - confirm the current commercial truth before commitment.
- Orders - persist booking state, passenger data, and purchased services.
- Payment - model authorization, capture, settlement, and payment failures.
- Fulfillment - represent ticketing, reference generation, and final confirmation.
- Servicing - support changes after booking, including ancillaries and passenger updates.
- Refunds / exchanges - make lifecycle transitions explicit instead of treating them as exceptions.
If the model ends at the search results page, the architecture will break down later. Real systems need to carry state across the full lifecycle. That includes durable identifiers, order transitions, and the ability to reconcile what the seller sees with what the airline or payment provider stores.
Lifecycle diagram
flowchart LR
Search[Search] --> Offers[Offers]
Offers --> Pricing[Pricing]
Pricing --> Orders[Orders]
Orders --> Payment[Payment]
Payment --> Fulfillment[Fulfillment]
Fulfillment --> Servicing[Servicing]
Servicing --> Refunds[Refunds / Exchanges]
Servicing --> Pricing
Refunds --> OrdersThe lifecycle is not linear. A traveler can add a bag after booking, move back into pricing, and then continue through order servicing. A refund can feed back into order state and payment reconciliation. The platform should model those transitions explicitly so teams do not encode them as invisible side effects.
Where teams usually fail
- Fat adapters - business rules, ranking logic, and UI assumptions get buried in integration code.
- UI assumptions inside APIs - APIs start mirroring one screen flow instead of exposing a stable product contract.
- Airline-specific contracts leaking - downstream systems learn airline quirks directly and duplicate the translation work.
- Search-only architecture - teams invest in shopping and discover too late that servicing was never modeled.
- Hidden unsupported features - the platform fails at runtime instead of telling sellers what is not available.
These failures all have the same root cause: boundaries are too weak. The platform stops acting like a platform and starts acting like a collection of loosely connected airline hacks.
Scaling concerns
Once the platform is live, integration quality becomes an operational problem. Versioning has to be managed carefully so seller-facing contracts stay stable while adapters evolve underneath. Capability discovery needs to be explicit so product and operations teams know what each airline can do, rather than discovering gaps through failed transactions.
Observability is not optional. Search, price confirmation, booking, payment, servicing, and refund requests should all carry trace identifiers across the seller layer, normalization layer, adapter, and downstream provider. Structured logs should be detailed enough to diagnose issues but careful enough to avoid exposing sensitive data.
Retries and caching need discipline. Some calls are safe to retry and others are not. Offers can expire or change, so cache the context needed to continue the workflow, not a false promise that stale content is still valid. For orders, store durable mappings between seller IDs, provider IDs, airline IDs, and payment references so reconciliation remains possible.
Error normalization deserves the same attention as data normalization. A good API classifies errors into categories such as validation, availability, pricing conflict, unsupported capability, downstream outage, payment failure, and servicing failure. That gives product and support teams a predictable way to respond regardless of which airline produced the original message.
Onboarding a new airline should feel like a controlled exercise, not a platform rewrite. If adding a carrier requires changes in multiple product teams, the architecture is already too coupled.
Concrete example: add baggage after booking
Suppose a seller wants to add a bag to an existing order. The seller app sends a normalized command to the platform, not an airline-specific mutation. The request identifies the order, the passenger, the segment, and the service quantity. The central API validates whether the request is allowed for this order state and this airline’s capability set.
{
"command": "AddService",
"orderId": "ord_123",
"service": {
"type": "BAGGAGE",
"quantity": 1,
"passengerRef": "pax_1",
"segmentRefs": ["seg_1"]
}
}The normalization layer turns that into the canonical workflow for the platform. The adapter then translates it into the airline’s required message, which may be an order-change flow, a service purchase flow, or a reprice-first sequence. The seller never sees those differences directly.
{
"status": "CONFIRMED",
"serviceId": "srv_987",
"totalDelta": {
"amount": "35.00",
"currency": "USD"
},
"orderStatus": "UPDATED"
}If the airline does not support that workflow, the platform should return an explicit unsupported-capability response. That is better than a generic failure because it tells the product team, support team, and seller exactly what is and is not possible.
Key principles
Normalize once. Do the expensive mapping in one place so the rest of the platform can depend on a single contract.
Adapt at the boundaries. Keep airline-specific logic in thin adapters, not in shared product code.
Model workflows, not screens. Build around search, offers, pricing, orders, payment, servicing, and recovery paths.
Make unsupported capabilities explicit. Do not let sellers discover gaps through avoidable runtime failures.
Keep contracts stable. Seller-facing APIs should remain predictable even as airline implementations change underneath.
Closing
A scalable NDC API layer is not built by hiding all airline complexity everywhere. It is built by putting complexity in the right place. Airline-specific behavior belongs in thin adapters. Shared business meaning belongs in a central normalized model. Seller applications should interact with stable contracts that describe the whole journey, not just the shopping page.
That separation is what turns NDC from a sequence of fragile integrations into a platform that can grow across airlines, channels, and workflows without collapsing under its own exceptions.