Skip to main content
@voyant-travel/flights is the flights vertical for OTA, tour-operator, and DMC deployments. Flights are modeled as supplier integrations and sourced inventory: Voyant is not an airline or a flight-operator system, and the module never tries to be one. Flights are unlike every other vertical. There is no slow-moving inventory to pre-project, no marketing copy to overlay on “LHR to JFK on BA177”, and no semantic-search use case (customers specify route, dates, and passengers, then expect live offers). The module reflects that: it adopts only the catalog-plane surfaces that add real value (booking snapshots, provenance, webhooks, source disconnection) and opts out of the rest (search-index projection, editorial overlays, embeddings, drift detection). Live multi-connection fan-out replaces the index. The package is opt-in. Templates that need flights (a tour-package vertical, a corporate-travel storefront, a cruise reseller offering coordinated flights) register it; the rest skip it entirely.

Key concepts

These terms come from the shared glossary and the flight contract.
  • The flight lifecycle: search, price, book, order. A flight provider integrates by implementing five core methods on the FlightConnectorAdapter: searchFlights (returns offers), priceOffer (revalidates an offer), bookFlight (creates an order, held or ticketed), getOrder, and cancelOrder. If any of the five cannot be implemented, the connection is not a flight connection.
  • Slice-based search. A search request carries slices, where each slice is { origin, destination, departureDate }. One slice is a one-way, two is a round-trip, three or more is a multi-city or open-jaw. Slices generalize cleanly where flat origin / destination / return-date shapes cannot.
  • Intent-driven booking. The book request carries a paymentIntent discriminated union. { type: "hold" } returns a confirmed order to ticket later, { type: "card", ... } returns a ticketed order, and { type: "ticket_on_credit" } covers the GDS agency model. The default is hold. Consumers declare intent rather than branching on capability discovery.
  • Capability-gated methods. Alongside the five core methods sit optional ones declared per connection: holds, seat maps, seat selection, ancillaries, check-in, exchange, refund, void, special service requests, and branded fares. An adapter that does not declare a capability stubs the method with CAPABILITY_NOT_SUPPORTED.
  • Itinerary fingerprint. A deterministic key derived from a FlightOffer’s segments (carrier codes, flight numbers, airports, times, cabin). Two providers selling the same flight produce identical fingerprints, which is how fan-out deduplicates.
  • Booking snapshot. At book time the module captures the frozen FlightOffer plus FlightOrder, the segments, the PNR, the fare breakdown, and the source pointer into a booking_catalog_snapshot row. This is the one catalog-plane surface flights fully adopt, because refunds, exchanges, and audits months later need the frozen offer and order.

How it relates to the catalog and Connect

The flight contract shapes (FlightConnectorAdapter, FlightOffer, FlightOrder, MergedFlightOffer, capability ids, paymentIntent) mirror voyant-cloud’s connect-flight-contract so adapters stay portable between Voyant Cloud and a Voyant Catalog flight integration. Connect flights provide the connector ecosystem (Hisky, Amadeus, Duffel, Sabre, Travelport NDC, and operator-built GDS adapters), each implementing the same contract this module orchestrates.

What it owns

  • The flight contract: ./contract/types (offers, orders, segments, requests, capability ids, the paymentIntent union), ./contract/adapter (the FlightConnectorAdapter interface), and ./contract/schemas (Zod runtime schemas for HTTP, queue, RPC, and adapter boundaries).
  • Multi-connection orchestration: ./orchestration/fingerprint (the itinerary fingerprint helper) and ./orchestration/fan-out (parallel search across all of an operator’s flight connections, per-provider timeout and circuit breaker, dedupe by fingerprint, merged result with a cheapest offer plus alternates).
  • Booking-time snapshot capture under ./snapshot, which builds the catalog plane’s capture input from a FlightOffer and FlightOrder pair.
  • The reference-data layer: the ReferenceDataProvider contract under ./reference/contract, plus shipped implementations including ./reference/local-postgres (plain tables in the operator’s own database, no external service) and ./reference/static-bundle.
  • The /v1/{admin,public}/flights/search and /v1/{admin,public}/flights/orders/* routes, mounted by templates that need flights.

Working with it

An agency wired to several providers gets one merged result set without writing fan-out, dedupe, or partial-failure handling. One slow or failing provider does not tank the search; whichever providers respond come back, and the rest are flagged per connection.
import { fanOutFlightSearch } from "@voyant-travel/flights/orchestration/fan-out"

const result = await fanOutFlightSearch({
  adapters: [hiskyAdapter, amadeusAdapter, charterConsolidatorAdapter],
  request: {
    slices: [
      { origin: "LHR", destination: "JFK", departureDate: "2026-10-15" },
      { origin: "JFK", destination: "LHR", departureDate: "2026-10-22" },
    ],
    passengers: { adults: 2 },
    cabin: "economy",
  },
  perConnectionTimeoutMs: 5000,
})

// result.offers: merged across connections, deduped by itinerary fingerprint,
//                 sorted cheapest first.
// result.perConnection: status and latency per connection.

Validate at a wire boundary

Use the shipped Zod schemas rather than re-declaring runtime validators when you expose the contract over HTTP, queues, or RPC.
import { flightBookRequestSchema } from "@voyant-travel/flights/contract/schemas"
import type { FlightBookRequest } from "@voyant-travel/flights/contract/types"

const request: FlightBookRequest = flightBookRequestSchema.parse(await req.json())

Book with an explicit payment intent

const heldOrder = await adapter.bookFlight(ctx, {
  offerId,
  passengers,
  paymentIntent: { type: "hold" }, // returns a confirmed order; call ticketOrder later
})

const ticketedOrder = await adapter.bookFlight(ctx, {
  offerId,
  passengers,
  paymentIntent: { type: "card", /* card details */ }, // returns a ticketed order
})

Hydrate reference data from the operator’s own database

Airlines, airports, and aircraft are global IATA-coded reference data, not flight catalog data. They sit behind a swappable provider. The simplest valid first-party option is plain Postgres tables in the operator’s own database, with no external service and no network call.
import {
  createLocalPostgresReferenceProvider,
  createReferenceDataTables,
} from "@voyant-travel/flights/reference/local-postgres"

const reference = createLocalPostgresReferenceProvider({ db })

const ba = await reference.getAirline("BA")
// → { iataCode: "BA", icaoCode: "BAW", name: "British Airways", country: "GB" }
Voyant Data is the hosted default provider. Static bundles, internal data lakes, third-party services (OAG, Cirium), and GDS-bundled feeds are all first-class alternatives, declared per deployment. No consumer-facing route is aware of which provider is underneath.
  • catalog and bookings. Flights opt into the booking snapshot graph: a booked flight writes a booking_catalog_snapshot row with entity_module: "flights" and the frozen offer, order, segments, fare breakdown, and source pointer.
  • provenance and webhooks. Every flight order carries the standard provenance tuple, and catalog.booking.committed and .cancelled events fire on the standard webhook pipeline.
  • source disconnection. If a provider connection is hard-disconnected, preserved booking snapshots stay queryable for refund and audit while live capability against that source stops.
  • Connect. Flight adapters reach the module through Connect’s connector ecosystem or as operator-built packages. Because the contract is borrowed from voyant-cloud’s connect-flight-contract, the same adapter works on both sides. See the Connect SDK.
What flights deliberately stay out of: search-index projection, editorial overlays, embeddings and semantic search, indexed pricing tiers, and drift detection. Live fan-out is the design, so there is nothing to index, overlay, embed, or drift-check.

React package

@voyant-travel/flights-react is the flights client tier: headless data hooks and a fetch client at the root and ./hooks, ./client, and ./query-keys, plus styled UI and full page compositions under ./ui. The styled subpaths add optional peers (@voyant-travel/ui, @voyant-travel/relationships-react for CRM-backed contact and billing pickers, and the finance checkout UI). FlightsPage renders search, filtering, the per-leg round-trip picker, the offer detail sheet, and the booking handoff. The route owns URL validation and navigation:
import { FlightsPage } from "@voyant-travel/flights-react/ui"

<FlightsPage
  search={search}
  onSearchChange={(next, options) => updateRouteSearch(next, options)}
  onBookOffer={({ outboundOfferId, returnOfferId, passengers, cabin }) =>
    goToBooking({ outboundOfferId, returnOfferId, passengers, cabin })
  }
/>
FlightBookingPage renders the repricing, ancillaries, seat-map, passenger, billing, payment, and confirmation flow. Router behavior, booking submission, and payment capabilities are callbacks or slots, so applications keep deployment-specific ownership.

Next steps

Connect SDK

The flight connector ecosystem and the shared contract.

Modules overview

How verticals compose into a Voyant app.

Data model

How schemas, links, and snapshots are authored.

Glossary

The shared domain language flights speaks.