@molecule/api-travel-amadeus
Provider bond · travel · API (Node) · v1.0.2 · Apache-2.0
Amadeus travel trip-planning aggregator (flights + hotels + activities) provider for molecule.dev
npm install @molecule/api-travel-amadeusnpm · Source on GitHub · Implements @molecule/api-travel
How it works
@molecule/api-travel-amadeus is a provider bond on the API (Node) side: it implements the travel core interface (@molecule/api-travel) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider } from '@molecule/api-travel'
import { provider } from '@molecule/api-travel-amadeus'
setProvider(provider)Works with: @molecule/api-secrets, @molecule/api-travel
Secrets: AMADEUS_CLIENT_ID, AMADEUS_CLIENT_SECRET
Reference
Amadeus travel trip-planning provider for molecule.dev.
Implements the TravelProvider interface against the Amadeus
Self-Service flight, hotel and activities APIs. Defaults to the
test sandbox (https://test.api.amadeus.com); set
AMADEUS_USE_PRODUCTION=true to route to production.
Reuses the same AMADEUS_CLIENT_ID / AMADEUS_CLIENT_SECRET
credentials as @molecule/api-flights-amadeus and
@molecule/api-hotels-amadeus — Amadeus issues a single
client-credentials pair per account that grants access to all of
its Self-Service products.
Cars are intentionally returned as an empty array: Amadeus does not expose a public car-rental API as of v22.
Hotels require a check-out date: searchTripOptions prices hotels only
when returnDate is supplied — a one-way search (where includeHotels
defaults to true) resolves with hotels: [] rather than throwing. Hotel
pricing is also best-effort per batch: individual batch failures are
swallowed and partial hotel results returned, so a short/empty hotels
array is not necessarily an upstream outage.
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 } from '@molecule/api-travel'
import { provider } from '@molecule/api-travel-amadeus'
setProvider(provider)
Type
provider
Installation
npm install @molecule/api-travel-amadeus @molecule/api-secrets @molecule/api-travel
API
Interfaces
AmadeusTravelConfig
Configuration options for the Amadeus travel trip-planning provider.
The bond reuses the SAME OAuth2 client-credentials flow as
@molecule/api-flights-amadeus and @molecule/api-hotels-amadeus:
a single AMADEUS_CLIENT_ID / AMADEUS_CLIENT_SECRET pair grants
access to flights, hotels, points-of-interest (used for activities)
and any other Self-Service product on the same account. There is no
cross-bond shared cache — each provider instance mints its own token
— but credentials are interchangeable.
interface AmadeusTravelConfig {
/**
* OAuth2 client ID, sent as the `client_id` form field when minting
* a fresh access token.
*
* If omitted, the provider falls back to the `AMADEUS_CLIENT_ID`
* environment variable. Requests fail with a sanitized error if
* neither is set.
*/
clientId?: string
/**
* OAuth2 client secret, sent as the `client_secret` form field when
* minting a fresh access token.
*
* If omitted, the provider falls back to the `AMADEUS_CLIENT_SECRET`
* environment variable. The secret is NEVER included in any error
* message — token-mint failures redact it before bubbling up.
*/
clientSecret?: string
/**
* When `true`, routes requests to the production endpoint
* (`https://api.amadeus.com`). When `false` or omitted, uses the
* Self-Service test sandbox (`https://test.api.amadeus.com`).
*/
useProduction?: boolean
/**
* Base URL override. Takes precedence over `useProduction`.
*/
baseUrl?: string
/**
* Request timeout in milliseconds for both the token-mint call and
* the data-API calls. Defaults to `15000`.
*/
timeout?: number
/**
* Number of seconds to subtract from the upstream `expires_in`
* value before treating a cached token as stale. Defaults to `30`.
*/
tokenSkewSeconds?: number
}
Classes
AmadeusTravelMissingCredentialsError
Error thrown by the Amadeus travel provider when no client credentials are configured.
Never includes any credential value in its message or properties.
class AmadeusTravelMissingCredentialsError extends Error {
/**
* Stable error code: always `MISSING_CREDENTIALS`.
*/
public readonly code: typeof MISSING_CREDENTIALS = MISSING_CREDENTIALS
/**
* Constructs a new `AmadeusTravelMissingCredentialsError`.
*/
public constructor() {
super(
'Amadeus travel provider is missing client credentials. Set AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET, or pass clientId/clientSecret to createProvider().',
)
this.name = 'AmadeusTravelMissingCredentialsError'
}
}
AmadeusTravelTokenMintError
Error thrown by the Amadeus travel provider when the OAuth2
token-mint call fails. The client_secret NEVER appears in this
error's message or properties.
class AmadeusTravelTokenMintError extends Error {
/**
* Stable error code: always `TOKEN_MINT_FAILED`.
*/
public readonly code: typeof TOKEN_MINT_FAILED = TOKEN_MINT_FAILED
/**
* Upstream HTTP status code, when available.
*/
public readonly status: number | null
/**
* Constructs a new `AmadeusTravelTokenMintError`.
*
* @param message - Human-readable description (never the secret).
* @param status - Upstream HTTP status code, or `null` if unknown.
*/
public constructor(message: string, status: number | null) {
super(message)
this.name = 'AmadeusTravelTokenMintError'
this.status = status
}
}
AmadeusTravelUpstreamError
Error thrown by the Amadeus travel provider for any non-OK upstream data-API response. Never includes the OAuth secret.
class AmadeusTravelUpstreamError extends Error {
/**
* Stable error code: always `UPSTREAM_ERROR`.
*/
public readonly code: typeof UPSTREAM_ERROR = UPSTREAM_ERROR
/**
* Upstream HTTP status code.
*/
public readonly status: number
/**
* Constructs a new `AmadeusTravelUpstreamError`.
*
* @param status - Upstream HTTP status code.
* @param detail - Optional sanitized detail string from the upstream
* error envelope.
*/
public constructor(status: number, detail?: string) {
super(
detail
? `Amadeus API request failed with status ${String(status)}: ${detail}`
: `Amadeus API request failed with status ${String(status)}`,
)
this.name = 'AmadeusTravelUpstreamError'
this.status = status
}
}
Functions
createProvider(config)
Creates an Amadeus travel trip-planning provider.
function createProvider(config?: AmadeusTravelConfig): TravelProvider
config— Provider configuration. Credentials may be supplied here directly or via theAMADEUS_CLIENT_IDandAMADEUS_CLIENT_SECRETenvironment variables.
Returns: A TravelProvider backed by Amadeus.
Constants
provider
The default provider implementation, lazily initialized on first use.
Reads AMADEUS_CLIENT_ID, AMADEUS_CLIENT_SECRET,
AMADEUS_USE_PRODUCTION, and AMADEUS_BASE_URL from environment
variables. Use createProvider directly if you need to supply
configuration programmatically.
const provider: TravelProvider
travelAmadeusSecretDefinitions
Secret definitions required by the Amadeus travel bond.
const travelAmadeusSecretDefinitions: SecretDefinition[]
MISSING_CREDENTIALS
Stable error code emitted by the Amadeus travel provider when neither
clientId nor clientSecret (nor their env-var fallbacks) are
configured.
const MISSING_CREDENTIALS: 'MISSING_CREDENTIALS'
TOKEN_MINT_FAILED
Stable error code emitted by the Amadeus travel provider when the OAuth2 token-mint call fails.
const TOKEN_MINT_FAILED: 'TOKEN_MINT_FAILED'
UPSTREAM_ERROR
Stable error code emitted by the Amadeus travel provider for any non-OK upstream HTTP response (including HTTP 429).
const UPSTREAM_ERROR: 'UPSTREAM_ERROR'
Core Interface
Implements @molecule/api-travel interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-travel'
import { provider } from '@molecule/api-travel-amadeus'
export function setupTravelAmadeus(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-secrets^1.0.1@molecule/api-travel^1.0.1
Environment Variables
AMADEUS_CLIENT_ID(required) — Amadeus API key- Setup: Create an app in Amadeus for Developers (Self-Service) and copy the API Key.
- Get it here: https://developers.amadeus.com/my-apps
AMADEUS_CLIENT_SECRET(required) — Amadeus API secret- Setup: Copy the API Secret from your Amadeus app page.
- Get it here: https://developers.amadeus.com/my-apps
Runtime Dependencies
@molecule/api-secrets@molecule/api-travel
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).