@molecule/api-travel
Core interface · travel · API (Node) · v1.0.3 · Apache-2.0
Travel trip-planning aggregator (flights + hotels + cars + activities) core interface for molecule.dev
npm install @molecule/api-travelHow it works
@molecule/api-travel is the travel core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/api-travel-amadeus.
import { setProvider, searchTripOptions } from '@molecule/api-travel'
import { provider as amadeus } from '@molecule/api-travel-amadeus'
setProvider(amadeus)
const trip = await searchTripOptions({
origin: 'JFK',
destination: 'PAR',
departureDate: '2026-07-15',
returnDate: '2026-07-22',
travelers: { adults: 2 },
includeFlights: true,
includeHotels: true,
})
console.log(trip.flights.length, trip.hotels.length)Providers (1): @molecule/api-travel-amadeus
Works with: @molecule/api-bond, @molecule/api-i18n
Reference
Provider-agnostic travel trip-planning interface for molecule.dev.
Defines the TravelProvider interface for aggregate trip
planning across flights, hotels, cars and activities. Bond packages
(Amadeus, Travelport, Sabre, etc.) implement this interface.
Application code uses the convenience functions (searchTripOptions,
searchActivities, searchCars) which delegate to the bonded
provider.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, searchTripOptions } from '@molecule/api-travel'
import { provider as amadeus } from '@molecule/api-travel-amadeus'
setProvider(amadeus)
const trip = await searchTripOptions({
origin: 'JFK',
destination: 'PAR',
departureDate: '2026-07-15',
returnDate: '2026-07-22',
travelers: { adults: 2 },
includeFlights: true,
includeHotels: true,
})
console.log(trip.flights.length, trip.hotels.length)
Type
core
Installation
npm install @molecule/api-travel @molecule/api-bond @molecule/api-i18n
API
Interfaces
GeoLocation
Geographic point used for radius-based search of hotels, activities or car-rental locations.
interface GeoLocation {
/**
* Latitude in decimal degrees, WGS-84.
*/
lat: number
/**
* Longitude in decimal degrees, WGS-84.
*/
lon: number
/**
* Search radius around the point. Units are provider-defined but
* SHOULD default to kilometres if not otherwise specified by the
* provider.
*/
radius?: number
}
TravelerCounts
Traveler-count breakdown supplied to TravelProvider.searchTripOptions.
interface TravelerCounts {
/**
* Adult travelers (>=12 years). Defaults to `1` when omitted.
*/
adults?: number
/**
* Child travelers (2-11 years). Defaults to `0` when omitted.
*/
children?: number
/**
* Infant travelers (<2 years). Defaults to `0` when omitted.
*/
infants?: number
}
MoneyAmount
Monetary price block. Always carries an explicit currency.
interface MoneyAmount {
/**
* Total amount in major units of `currency` (e.g. dollars,
* not cents).
*/
total: number
/**
* ISO 4217 currency code the `total` is denominated in.
*/
currency: CurrencyCode
}
FlightSegment
One leg of a flight offer — a single take-off / landing pair on a single operated flight.
interface FlightSegment {
/**
* Departure airport / instant.
*/
departure: FlightSegmentEndpoint
/**
* Arrival airport / instant.
*/
arrival: FlightSegmentEndpoint
/**
* Marketing carrier IATA code (e.g. `'AA'`, `'BA'`).
*/
carrier: string
/**
* Marketing flight number (e.g. `'100'`, `'1234'`).
*/
flightNumber: string
/**
* Block / total time the segment is in the air. `null` when the
* upstream does not supply per-segment duration.
*/
duration?: IsoDuration | null
}
FlightSegmentEndpoint
Departure or arrival point on a FlightSegment.
interface FlightSegmentEndpoint {
/**
* IATA airport code (e.g. `'JFK'`).
*/
airport: LocationCode
/**
* Local-time instant including timezone offset.
*/
at: IsoDateTime
/**
* Terminal designator (e.g. `'4'`, `'2A'`). `null` when not supplied.
*/
terminal?: string | null
}
FlightOffer
Normalized flight offer surfaced inside a TripSearchResult.
This is a minimal, travel-core-local shape — not the same TypeScript
type as @molecule/api-flights's FlightOffer. Providers that wrap
the flights core MAY convert between the two structurally.
interface FlightOffer {
/**
* Provider-specific opaque offer identifier.
*/
id: OfferId
/**
* Total grand-total price for ALL travelers.
*/
price: MoneyAmount
/**
* Flight segments in chronological order. For round-trip itineraries
* outbound segments precede return segments.
*/
segments: FlightSegment[]
/**
* Total elapsed time across all segments (including layovers).
*/
duration: IsoDuration
}
HotelOffer
Normalized hotel offer surfaced inside a TripSearchResult.
interface HotelOffer {
/**
* Provider-specific opaque offer identifier (room / rate
* combination).
*/
id: OfferId
/**
* Provider-specific hotel identifier the offer belongs to.
*/
hotelId: string
/**
* Human-readable hotel name (e.g. `'Hotel de Paris'`).
*/
name: string
/**
* Total price for the entire stay (all nights).
*/
price: MoneyAmount
/**
* Check-in date (ISO 8601 calendar date).
*/
checkInDate: IsoDate
/**
* Check-out date (ISO 8601 calendar date).
*/
checkOutDate: IsoDate
/**
* Star rating as an integer (1..5), if the provider exposes it.
*/
rating?: number
/**
* Free-form room name / type description, if supplied.
*/
roomDescription?: string
/**
* Cancellation / refundability hint. `true` = explicitly refundable,
* `false` = explicitly non-refundable, `undefined` = unknown.
*/
refundable?: boolean
}
CarOffer
Normalized car-rental offer surfaced inside a TripSearchResult.
Most major travel aggregators expose car offers as a separate vertical; the trip facade includes them so itinerary planners can present a unified "ground transport" line item alongside flights and hotels.
interface CarOffer {
/**
* Provider-specific opaque offer identifier.
*/
id: OfferId
/**
* Vendor / supplier name (e.g. `'Hertz'`, `'Avis'`).
*/
vendor: string
/**
* Free-form vehicle / category description (e.g.
* `'Compact SUV or similar'`).
*/
vehicleDescription: string
/**
* Total price for the entire rental.
*/
price: MoneyAmount
/**
* Pickup location (IATA airport / city code or free-form locality).
*/
pickupLocation: LocationCode
/**
* Pickup date / time.
*/
pickupAt: IsoDateTime
/**
* Return date / time.
*/
returnAt: IsoDateTime
/**
* Whether unlimited mileage is included. `undefined` when unknown.
*/
unlimitedMileage?: boolean
}
ActivityOffer
Normalized activity / experience offer (e.g. a museum tour, food
walk, day trip) surfaced inside a TripSearchResult.
interface ActivityOffer {
/**
* Provider-specific opaque offer / activity identifier.
*/
id: OfferId
/**
* Human-readable activity name (e.g. `'Eiffel Tower skip-the-line'`).
*/
name: string
/**
* Free-form short description, if the provider exposes one.
*/
description?: string
/**
* Per-person or per-booking price (provider-defined). Treat as the
* starting "from" price unless the provider documents otherwise.
*/
price: MoneyAmount
/**
* Geographic point the activity takes place at, when known.
*/
location?: GeoLocation
/**
* URL of a representative image, if the provider exposes one.
*/
pictureUrl?: string
/**
* URL of the provider's booking / detail page, if exposed.
*/
bookingUrl?: string
/**
* Provider-supplied minimum duration string (e.g. `'PT2H'`), if any.
*/
minimumDuration?: IsoDuration
}
SearchTripOptions
Search criteria for TravelProvider.searchTripOptions.
The criteria are deliberately broad: travelers typically want to see flights + hotels + cars + activities all at once when planning a trip, so the same date / origin / destination apply to each. Per- vertical filtering (cabin, hotel rating, etc.) is left to follow-up calls against the per-vertical cores.
interface SearchTripOptions {
/**
* Origin IATA airport / city code (e.g. `'JFK'`, `'NYC'`). Used for
* the flight portion of the trip.
*/
origin: LocationCode
/**
* Destination IATA airport / city code. Used for the flight portion
* of the trip and as the catalogue lookup for hotels / activities
* when the provider supports it.
*/
destination: LocationCode
/**
* Outbound departure date (ISO 8601 calendar date). Also serves as
* the hotel check-in date.
*/
departureDate: IsoDate
/**
* Return date for round-trip searches. Also serves as the hotel
* check-out date when supplied. Omit for one-way / open-ended trips.
*/
returnDate?: IsoDate
/**
* Traveler-count breakdown. Defaults to a single adult when omitted.
*/
travelers?: TravelerCounts
/**
* Whether to include flight offers in the result. Defaults to
* `true`.
*/
includeFlights?: boolean
/**
* Whether to include hotel offers in the result. Defaults to
* `true`.
*/
includeHotels?: boolean
/**
* Whether to include car-rental offers in the result. Defaults to
* `false` (most providers do not expose a car-rental API; opt in
* explicitly when you know yours does).
*/
includeCars?: boolean
/**
* Whether to include activity offers in the result. Defaults to
* `false` for the same reason as `includeCars`.
*/
includeActivities?: boolean
/**
* Maximum number of offers per vertical. Implementations MAY clamp
* this to whatever upper bound their upstream API enforces.
*/
maxResultsPerCategory?: number
}
TripSearchResult
Aggregated trip-search result returned by
TravelProvider.searchTripOptions.
Each per-vertical array is empty (NOT undefined) when the caller
did not opt in to that vertical or when the provider returned no
offers — this lets consumers iterate without conditional access
checks.
interface TripSearchResult {
/**
* Flight offers matching the trip search. Empty when
* `SearchTripOptions.includeFlights` is `false` or the
* provider returned none.
*/
flights: FlightOffer[]
/**
* Hotel offers matching the trip search. Empty when
* `SearchTripOptions.includeHotels` is `false` or the
* provider returned none.
*/
hotels: HotelOffer[]
/**
* Car-rental offers matching the trip search. Empty when
* `SearchTripOptions.includeCars` is `false` or the provider
* does not expose a car-rental API.
*/
cars: CarOffer[]
/**
* Activity offers matching the trip search. Empty when
* `SearchTripOptions.includeActivities` is `false` or the
* provider returned none.
*/
activities: ActivityOffer[]
}
SearchActivitiesOptions
Search criteria for TravelProvider.searchActivities.
Activities are typically scoped to a destination + date range and filtered by the provider's own catalogue. Most providers expose latitude / longitude search rather than IATA codes, so callers can supply either form.
interface SearchActivitiesOptions {
/**
* Destination — either an IATA airport / city code or a geographic
* point with optional radius. Providers SHOULD prefer
* `GeoLocation` when both are present.
*/
destination: LocationCode | GeoLocation
/**
* Date range as `[start, end]` ISO calendar dates. Providers MAY
* ignore the range and return their full catalogue if the upstream
* does not support date-filtered availability.
*/
dates?: { start: IsoDate; end: IsoDate }
/**
* Maximum number of offers to return.
*/
maxResults?: number
}
SearchCarsOptions
Search criteria for TravelProvider.searchCars.
interface SearchCarsOptions {
/**
* IATA airport / city code or free-form locality string for the
* pickup location.
*/
pickupLocation: LocationCode
/**
* Date / time the car is collected.
*/
pickupDate: IsoDate | IsoDateTime
/**
* Date / time the car is returned.
*/
returnDate: IsoDate | IsoDateTime
/**
* Optional alternate dropoff location. Defaults to
* `pickupLocation` when omitted.
*/
dropoffLocation?: LocationCode
/**
* Maximum number of offers to return.
*/
maxResults?: number
}
TravelProvider
Travel trip-planning provider interface.
All travel providers (Amadeus, Travelport, Sabre, fixtures, etc.)
implement this interface. The interface is deliberately minimal and
aggregates across the per-vertical cores (@molecule/api-flights,
@molecule/api-hotels) so callers building "search a trip"
itinerary UIs can issue a single call and render mixed results.
Providers that lack one of the vertical APIs (e.g. Amadeus does not expose a public cars API as of v22) MUST return an empty array for that vertical rather than throwing — the absence is data, not an error.
interface TravelProvider {
/**
* Searches for trip options matching the supplied itinerary. Returns
* an aggregated `TripSearchResult` containing flights,
* hotels, cars and activities (each opt-in via the corresponding
* `include*` flag).
*
* @param options - Trip search criteria.
* @returns Aggregated trip search result.
*/
searchTripOptions(options: SearchTripOptions): Promise<TripSearchResult>
/**
* Searches for activity / experience offers at a destination.
*
* @param options - Activity search criteria.
* @returns Array of normalized activity offers, possibly empty.
*/
searchActivities(options: SearchActivitiesOptions): Promise<ActivityOffer[]>
/**
* Searches for car-rental offers.
*
* Providers without a car-rental API MUST return an empty array
* rather than throwing.
*
* @param options - Car-rental search criteria.
* @returns Array of normalized car-rental offers, possibly empty.
*/
searchCars(options: SearchCarsOptions): Promise<CarOffer[]>
}
Types
LocationCode
IATA airport / city / metropolitan code (e.g. 'JFK', 'NYC',
'PAR'). Plain string alias — providers map onto whatever upstream
catalogue they expose.
type LocationCode = string
IsoDate
ISO 8601 calendar date (e.g. '2026-07-15'). Time-of-day MUST NOT be
included — this is a date for searching availability, not a timestamp.
type IsoDate = string
IsoDateTime
ISO 8601 instant including timezone offset
(e.g. '2026-07-15T08:30:00+02:00').
type IsoDateTime = string
IsoDuration
ISO 8601 duration string (e.g. 'PT2H30M', 'PT11H45M'). Plain
string for the same reason as IsoDate — providers differ on
whether they expose seconds-precision, fractional minutes, etc.
type IsoDuration = string
CurrencyCode
ISO 4217 three-letter currency code (e.g. 'USD', 'EUR', 'JPY').
type CurrencyCode = string
OfferId
Provider-specific opaque offer identifier. Identifier scheme is provider-defined and MUST NOT be parsed by consumers.
type OfferId = string
Functions
setProvider(provider)
Registers a travel provider as the active singleton. Called by bond
packages (e.g. @molecule/api-travel-amadeus) during application
startup.
function setProvider(provider: TravelProvider): void
provider— The travel provider implementation to bond.
getProvider()
Retrieves the bonded travel provider, throwing if none is configured.
function getProvider(): TravelProvider
Returns: The bonded travel provider.
hasProvider()
Checks whether a travel provider is currently bonded.
function hasProvider(): boolean
Returns: true if a travel provider is bonded.
searchTripOptions(options)
Searches for trip options (flights + hotels + cars + activities) using the bonded provider.
function searchTripOptions(options: SearchTripOptions): Promise<TripSearchResult>
options— Trip search criteria.
Returns: Aggregated trip search result.
searchActivities(options)
Searches for activity / experience offers at a destination using the bonded provider.
function searchActivities(options: SearchActivitiesOptions): Promise<ActivityOffer[]>
options— Activity search criteria.
Returns: Array of normalized activity offers, possibly empty.
searchCars(options)
Searches for car-rental offers using the bonded provider.
Providers without a car-rental API return an empty array rather than throwing.
function searchCars(options: SearchCarsOptions): Promise<CarOffer[]>
options— Car-rental search criteria.
Returns: Array of normalized car-rental offers, possibly empty.
Available Providers
| Provider | Package |
|---|---|
| Amadeus | @molecule/api-travel-amadeus |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-i18n^1.0.1
Runtime Dependencies
@molecule/api-bond@molecule/api-i18n
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual trip-planning / results screens, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- A real trip search (
searchTripOptionswith a realorigin->destination, adepartureDateweeks out, andreturnDatefor a round trip — e.g. JFK -> PAR) returns REAL results rendered in the UI: each opted-in vertical (includeFlights/includeHotels/includeCars/includeActivities) shows populatedflights/hotels/cars/activities, each an actual offer — never an empty list, a stuck spinner, or a placeholder row presented as a successful search. - Results match the query: each
FlightOffer's firstsegments[].departure.airportis the searchedoriginand its lastarrival.airportthedestination, on the requested dates; eachHotelOfferis at the destination withcheckInDate/checkOutDateequal to the searched departure / return dates — not random routes, cities, or dates. - Each vertical is opt-in and additive: toggling
includeFlights,includeHotels,includeCars, orincludeActivitiesadds ONLY that section, and a vertical not requested (or one the provider can't serve, e.g. cars) comes back as an EMPTY array — rendered as "none", never as a failed or blank search.maxResultsPerCategoryactually caps how many offers each section shows. Any separate activities / cars screen callssearchActivities/searchCarsand renders its ownActivityOffer[]/CarOffer[]. - Every offer's
price.totalshows its ISO 4217price.currency(formatted with it — never a hardcoded$) and is sane: a flight total is the grand total for ALLtravelers, a hotel total covers the whole stay, a car total the whole rental — positive and the right order of magnitude. - An impossible or invalid search — a bad IATA code, a past
departureDate, areturnDatebefore it, or a route with no availability — shows a clear per-section "no results" / "invalid" state, not a crash and not a blank list presented as a successful search. - A provider outage or rate-limit (HTTP 429) surfaces as a graceful, retryable error in the UI — not a hung spinner or a silent empty list.
- This contract is search ONLY (there is no book / reserve call).
Booking goes OUT-OF-BAND to the vendor (an
ActivityOffer.bookingUrlor the app's own checkout) — verify the app RECORDS the SELECTED offer (its priced total + itinerary), since opaqueOfferIds are short-lived and can't be replayed later. - The provider API key stays server-side: every search runs through the API bond (the package is SERVER-ONLY), no key or upstream credential reaches the browser, and the endpoint validates origin / destination / dates instead of forwarding arbitrary caller input upstream (an open proxy would leak quota or enable SSRF).