@molecule/api-hotels
Core interface · hotels · API (Node) · v1.0.3 · Apache-2.0
Provider-agnostic hotel search/offer/book core interface.
npm install @molecule/api-hotelsHow it works
@molecule/api-hotels is the hotels 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-hotels-amadeus.
import { setProvider, searchHotels, getHotelOffers } from '@molecule/api-hotels'
import { provider as amadeus } from '@molecule/api-hotels-amadeus'
setProvider(amadeus)
const hits = await searchHotels({
cityCode: 'PAR',
checkInDate: '2026-06-01',
checkOutDate: '2026-06-04',
adults: 2,
})
const offers = await getHotelOffers(hits[0].hotelId, {
checkInDate: '2026-06-01',
checkOutDate: '2026-06-04',
adults: 2,
})Providers (1): @molecule/api-hotels-amadeus
Works with: @molecule/api-bond, @molecule/api-i18n
Reference
Provider-agnostic hotels interface for molecule.dev.
Defines the HotelsProvider interface for hotel inventory aggregators —
search, priced offers, and booking confirmation. Bond packages (Amadeus,
Booking.com Affiliate, Expedia Rapid, etc.) implement this interface.
Application code uses the convenience functions (searchHotels,
getHotelOffers, bookHotel) which delegate to the bonded provider.
Prices carry an explicit ISO 4217 currency so multi-market callers can reconcile across providers. Hotel and offer IDs are kept as plain strings so providers can use whatever opaque catalogue identifier they expose.
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, searchHotels, getHotelOffers } from '@molecule/api-hotels'
import { provider as amadeus } from '@molecule/api-hotels-amadeus'
setProvider(amadeus)
const hits = await searchHotels({
cityCode: 'PAR',
checkInDate: '2026-06-01',
checkOutDate: '2026-06-04',
adults: 2,
})
const offers = await getHotelOffers(hits[0].hotelId, {
checkInDate: '2026-06-01',
checkOutDate: '2026-06-04',
adults: 2,
})
Type
core
Installation
npm install @molecule/api-hotels @molecule/api-bond @molecule/api-i18n
API
Interfaces
GeoLocation
Geographic point used for radius-based hotel search.
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.
* Optional — providers MAY apply a sensible default (e.g. 5 km).
*/
radius?: number
}
HotelSearchCriteria
Search criteria for HotelsProvider.searchHotels.
Either cityCode or location MUST be supplied (callers
that supply both leave provider-specific behaviour up to the
implementation, which SHOULD prefer location when both are
present).
interface HotelSearchCriteria {
/**
* IATA / city code to search within. Mutually exclusive with
* `location` in the typical case.
*/
cityCode?: CityCode
/**
* Geographic point + optional radius. Mutually exclusive with
* `cityCode` in the typical case.
*/
location?: GeoLocation
/**
* Check-in date as `YYYY-MM-DD`.
*/
checkInDate: IsoDate
/**
* Check-out date as `YYYY-MM-DD`. MUST be strictly after
* `checkInDate`.
*/
checkOutDate: IsoDate
/**
* Number of adult guests per room. Defaults to `1` when omitted.
* Providers MAY clamp very large values.
*/
adults?: number
/**
* Number of rooms required. Defaults to `1` when omitted.
*/
rooms?: number
/**
* Optional star-rating filter (e.g. `[4, 5]` to only return 4- and
* 5-star properties). Providers without rating data MAY ignore this.
*/
ratings?: number[]
}
HotelOffersCriteria
Filter / refinement parameters when fetching priced offers for a specific hotel.
interface HotelOffersCriteria {
/**
* Check-in date as `YYYY-MM-DD`.
*/
checkInDate: IsoDate
/**
* Check-out date as `YYYY-MM-DD`.
*/
checkOutDate: IsoDate
/**
* Number of adult guests per room. Defaults to `1` when omitted.
*/
adults?: number
}
HotelAddress
Geocoded address block returned with hotel search hits.
interface HotelAddress {
/**
* ISO 3166-1 alpha-2 country code (e.g. `'US'`, `'FR'`), if known.
*/
countryCode?: string
/**
* Free-form city name as the provider returns it (e.g. `'Paris'`).
*/
cityName?: string
/**
* Free-form street / locality line, if the provider exposes one.
*/
line?: string
/**
* Postal / ZIP code, if known.
*/
postalCode?: string
}
HotelSearchResult
A single hotel returned by HotelsProvider.searchHotels.
Search hits are summary records — they identify the property and may
include a representative price snippet, but consumers should call
HotelsProvider.getHotelOffers to enumerate bookable rooms / rates.
interface HotelSearchResult {
/**
* Provider-specific hotel identifier (opaque to callers).
*/
hotelId: HotelId
/**
* Human-readable hotel name (e.g. `'Hotel de Paris'`).
*/
name: string
/**
* IATA / city code the hotel is located in, if known.
*/
cityCode?: CityCode
/**
* Star rating as an integer (1..5), if the provider exposes it.
*/
rating?: number
/**
* Latitude in decimal degrees, if the provider exposes geocoordinates.
*/
latitude?: number
/**
* Longitude in decimal degrees, if the provider exposes geocoordinates.
*/
longitude?: number
/**
* Geocoded address block, if the provider exposes one.
*/
address?: HotelAddress
/**
* Distance from the search anchor (city centre or `GeoLocation`),
* if the provider returns one. Units are provider-defined.
*/
distance?: number
/**
* "From" / starting price as a snippet so list views can render a price
* without a second round-trip. Optional — not every provider returns a
* price in the search response.
*/
fromPrice?: HotelPrice
}
HotelPrice
Monetary price block. Always carries an explicit currency.
interface HotelPrice {
/**
* 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
}
HotelOffer
A single bookable room / rate combination for a specific hotel.
Offers are short-lived: the offerId is provider-issued and
typically expires within minutes. Consumers should re-fetch offers
immediately before initiating a booking flow.
interface HotelOffer {
/**
* Opaque, provider-issued offer identifier. Pass to
* `HotelsProvider.bookHotel` (or its equivalent) to confirm.
*/
offerId: HotelOfferId
/**
* Hotel this offer belongs to.
*/
hotelId: HotelId
/**
* Check-in date as `YYYY-MM-DD`.
*/
checkInDate: IsoDate
/**
* Check-out date as `YYYY-MM-DD`.
*/
checkOutDate: IsoDate
/**
* Total price for the entire stay (all nights).
*/
price: HotelPrice
/**
* Free-form room name / type description (e.g. `'Deluxe King Room'`).
* Optional — not every provider exposes one.
*/
roomDescription?: string
/**
* Number of adults the offer is priced for.
*/
adults?: number
/**
* Cancellation / refundability hint. `true` = explicitly refundable,
* `false` = explicitly non-refundable, `undefined` = unknown.
*/
refundable?: boolean
/**
* Rate code / class identifier (e.g. `'BAR'`, `'AAA'`), if exposed.
*/
rateCode?: string
}
HotelGuestInfo
Guest information supplied to a booking call. Kept intentionally minimal — providers that require additional fields (loyalty number, etc.) can extend via interface merging in their own bond package.
interface HotelGuestInfo {
/**
* Guest first / given name.
*/
firstName: string
/**
* Guest last / family name.
*/
lastName: string
/**
* Contact email for the booking confirmation.
*/
email: string
/**
* Optional contact phone (E.164 format recommended).
*/
phone?: string
}
HotelBooking
Confirmation record returned after a successful booking.
Note: many hotel providers only support "price the offer" + "redirect
the guest to the provider's checkout" flows rather than direct
bookings via API. Implementations that cannot complete a booking
server-side MAY surface that as an error and expose a separate
priceOffer flow; in that case HotelsProvider.bookHotel can
throw with a cause.code === 'BOOKING_NOT_SUPPORTED'.
interface HotelBooking {
/**
* Provider-issued booking / reservation identifier.
*/
bookingId: string
/**
* Hotel that was booked.
*/
hotelId: HotelId
/**
* Offer that was confirmed.
*/
offerId: HotelOfferId
/**
* Final price charged.
*/
price: HotelPrice
/**
* Check-in date as `YYYY-MM-DD`.
*/
checkInDate: IsoDate
/**
* Check-out date as `YYYY-MM-DD`.
*/
checkOutDate: IsoDate
/**
* Lead guest information echoed back from the booking call.
*/
guest: HotelGuestInfo
/**
* Provider-issued confirmation / itinerary number, if distinct from
* `bookingId`.
*/
confirmationNumber?: string
}
HotelsProvider
Hotel inventory aggregator provider interface.
All providers (Amadeus, Booking.com Affiliate, Expedia Rapid, fixtures, etc.) implement this interface. The interface is deliberately minimal so providers with very different upstream APIs can satisfy it identically.
interface HotelsProvider {
/**
* Searches available hotels matching `criteria`.
*
* @param criteria - Search criteria (location + dates + room demand).
* @returns Array of `HotelSearchResult`, possibly empty.
*/
searchHotels(criteria: HotelSearchCriteria): Promise<HotelSearchResult[]>
/**
* Returns priced offers (room / rate combinations) for `hotelId`.
* Offers are short-lived and should be re-fetched immediately before a
* booking flow.
*
* @param hotelId - Provider-issued hotel identifier.
* @param criteria - Date + occupancy filter.
* @returns Array of `HotelOffer`, possibly empty.
*/
getHotelOffers(hotelId: HotelId, criteria: HotelOffersCriteria): Promise<HotelOffer[]>
/**
* Confirms a booking against `offerId` for `guestInfo`.
*
* Providers that only support priced offers + provider-hosted checkout
* (rather than direct API booking) SHOULD throw with `cause.code ===
* 'BOOKING_NOT_SUPPORTED'`; callers can detect that and fall back to
* the provider's redirect flow.
*
* @param offerId - Offer to confirm. Must be fresh (not expired).
* @param guestInfo - Lead guest contact / identity details.
* @returns Confirmed `HotelBooking` record.
*/
bookHotel(offerId: HotelOfferId, guestInfo: HotelGuestInfo): Promise<HotelBooking>
}
Types
HotelId
Provider-specific identifier for a hotel property (e.g. an Amadeus
hotelId such as 'MCLONGHM'). Kept as a plain string so providers
can use whatever opaque catalogue ID they expose.
type HotelId = string
HotelOfferId
Provider-specific identifier for a priced hotel offer (room / rate
combination). Offers are typically short-lived; consumers should treat
IDs as opaque and refresh them via HotelsProvider.getHotelOffers
before booking.
type HotelOfferId = string
CityCode
IATA / city code (e.g. 'PAR', 'NYC', 'LON'). Plain string —
providers differ on which catalogues they expose.
type CityCode = string
CurrencyCode
ISO 4217 three-letter currency code (e.g. 'USD', 'EUR').
type CurrencyCode = string
IsoDate
Calendar date in ISO YYYY-MM-DD form (no time, no timezone). Hotel
inventory is typically priced per night, not per minute, so a plain
date string is the canonical contract.
type IsoDate = string
Functions
setProvider(provider)
Registers a hotels provider as the active singleton. Called by bond
packages (e.g. @molecule/api-hotels-amadeus) during application
startup.
function setProvider(provider: HotelsProvider): void
provider— The hotels provider implementation to bond.
getProvider()
Retrieves the bonded hotels provider, throwing if none is configured.
function getProvider(): HotelsProvider
Returns: The bonded hotels provider.
hasProvider()
Checks whether a hotels provider is currently bonded.
function hasProvider(): boolean
Returns: true if a hotels provider is bonded.
searchHotels(criteria)
Searches available hotels matching criteria using the bonded
provider.
function searchHotels(criteria: HotelSearchCriteria): Promise<HotelSearchResult[]>
criteria— Search criteria (location + dates + room demand).
Returns: Array of HotelSearchResult, possibly empty.
getHotelOffers(hotelId, criteria)
Returns priced offers for hotelId using the bonded provider.
Offers are short-lived and should be re-fetched immediately before a
booking flow.
function getHotelOffers(hotelId: string, criteria: HotelOffersCriteria): Promise<HotelOffer[]>
hotelId— Provider-issued hotel identifier.criteria— Date + occupancy filter.
Returns: Array of HotelOffer, possibly empty.
bookHotel(offerId, guestInfo)
Confirms a hotel booking against offerId using the bonded
provider.
Providers that only support priced offers + provider-hosted checkout
MAY throw with cause.code === 'BOOKING_NOT_SUPPORTED'; callers can
detect that and fall back to a redirect flow.
function bookHotel(offerId: string, guestInfo: HotelGuestInfo): Promise<HotelBooking>
offerId— Offer to confirm. Must be fresh (not expired).guestInfo— Lead guest contact / identity details.
Returns: Confirmed HotelBooking record.
Available Providers
| Provider | Package |
|---|---|
| Hotels (Amadeus) | @molecule/api-hotels-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 -
Not every provider can complete a booking server-side. Many hotel APIs only support search + priced offers and hand the guest to the provider's own checkout. Such bonds throw from
bookHotelwithcause.code === 'BOOKING_NOT_SUPPORTED'— handle that path (link out / deep-link to the provider) instead of assuming an in-app booking form works for every bond. -
Offers are short-lived quotes, not reservations. An
offerIdfromgetHotelOffersexpires; book promptly after selection, and on a booking failure re-fetch offers and re-confirm the price with the user — never retry a stale offer id or present a cached price as bookable. -
Booking is a real-money, PII-bearing call: keep it SERVER-SIDE behind an authenticated endpoint (provider API keys live in the bond's config, never in app code), validate
guestInfoserver-side, and persist the returnedHotelBooking.bookingIdbefore reporting success to the user.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- A search for a real location + check-in/check-out dates + guest count
(
searchHotelswith acityCodeorlocation) returns REALHotelSearchResults rendered in the UI — each with a name, its city /address, and afromPricefor the stay — never an empty list, a stuck spinner, or placeholder cards. Results match the query: the right city, and the dates / occupancy you entered are reflected in the prices shown. - Any exposed filter or sort (price, star
rating, amenities) actually narrows / reorders the rendered list — e.g. a price sort puts the lowestHotelPrice.totalfirst; a 4–5 star filter drops lower-rated properties. - Availability is respected: a sold-out or invalid-date search (e.g.
checkOutDatenot strictly aftercheckInDate) shows a visible "no availability" empty state — never a crash, a blank screen, or fabricated results. - Prices total correctly and every amount shows its currency: a shown
HotelOffer.price.totalequals nights × nightly rate + any fees, in its ISO 4217HotelPrice.currency(no bare "123" with no symbol or code). - If hotel detail / booking is exposed, opening a hotel calls
getHotelOffersand shows its real rooms / rates (roomDescription+price); selecting one records the chosenofferIdin the app. Booking itself goes out-of-band to the vendor (orbookHotelthrowsBOOKING_NOT_SUPPORTED→ a redirect) — verify the app's RECORDED selection, not a fake in-app confirmation. - A provider error (upstream down / rate-limited) surfaces as a graceful, visible message — not a blank page or an unhandled rejection — and the provider API key stays server-side: search / offers / booking all run on the server, and no key appears in network responses or page source.