Flights
Search flights with one call and get back normalized, price-sorted results (the same physical flight appears once). Each result carries the site it came…
Search flights with one call and get back normalized, price-sorted results (the same physical flight appears once). Each result carries the site it came from (site) and every site it was found on (sites). Returns { flights, warnings } — warnings names any site that was dropped from the fan-out, so a degraded search cannot read as a complete one.
Also known as: flight, airfare, airline, airlines, fly, plane ticket, air ticket, flight ticket, plane fare
Call it
bowmark.flights.search(query: FlightQuery, options?: CallOptions): Promise<FlightSearchResult>
bowmark.flights.getBookingOptions(flight: FlightResult, options?: CallOptions): Promise<BookingOptionsResult>
bowmark.flights.getFlightStatus(query: FlightStatusQuery, options?: CallOptions): Promise<FlightStatusResult>Functions
| Function | What it does |
|---|---|
search | Searches for flights matching the query and returns { flights, warnings }. flights are normalized FlightResults, price-sorted, deduped so the same physical flight appears once; each… |
getBookingOptions | Every seller on offer for ONE result — pass the whole row from search(), not its id. |
getFlightStatus | A flight's live status, checked directly with the airline that flies it. |
Types
type FlightQuery = {
from: string // IATA ("SFO") — best for cross-provider matching
to: string
depart: string // ISO date "2026-09-01"
return?: string // omit for one-way
cabin?: "economy" | "premium" | "business" | "first"
stops?: "any" | "nonstop" | "1"
}
// One normalized flight. Same shape no matter where it came from.
type FlightResult = {
price: number | null // price for this flight (matches this result's times/label)
currency: string
airlines: string[]
stops: number // 0 = nonstop
date: string // the departure DATE this result is for ("2026-09-01"),
// stamped from the query — lets a date-range sweep tell days apart
depart: string // OUTBOUND departure TIME of day, "7:00 AM". For a round
// trip this describes the outbound leg only; the return-leg
// times are not in this object.
arrive: string // OUTBOUND arrival time, "3:30 PM" ("+1" if next day)
durationMinutes: number | null
tripType: "round trip" | "one way"
url: string // link to view/book this flight on its site
site: string // the site this result comes from
sites: string[] // every site this same flight was found on. A site MISSING
// here had no matching flight — it does NOT tell you the
// site was reached. Read warnings for that
sponsored: boolean // true if this result is a promoted placement
id: string // opaque handle for getBookingOptions()
}
type FlightSearchResult = {
flights: FlightResult[] // deduped and price-sorted; the answer
warnings: string[] // always present, empty when every site answered.
// One line per site DROPPED from the fan-out — read
// it before treating the rows as the whole market.
// A site missing from a result's sites list merely had no
// matching flight; that is NOT the same fact.
}
type CallOptions = {
timeoutMs?: number // per-provider budget in ms, default 30000, clamped to 1000-55000.
// A provider slower than this is DROPPED from the results and
// NAMED in warnings — never silently absent
}
// What getBookingOptions returns, one per fare on offer for a single result.
type BookingOption = {
provider: string // who would sell you this fare ("Frontier", "Expedia")
fareType: string // that seller's own name for it ("Basic Fare")
price: number | null // this fare's price; null where the seller quoted none
currency: string
deepLink: string | null // straight to this fare, when the site exposes one —
// frequently null, so fall back to the result's url
}
type BookingOptionsResult = {
options: BookingOption[] // cheapest first; unpriced fares last
warnings: string[] // always present. Names what this seller list does NOT
// contain — fields the site left unreported, and the
// OTHER sites the same flight was found on, whose
// sellers are not in here. Empty means nothing dropped.
}
// Give EITHER flightNumber OR both origin and destination, plus date and airline.
type FlightStatusQuery = {
airline: string // IATA carrier code, e.g. "AA" — routes to the airline
// that flies it; there is no default to guess
date: string // the flight's ORIGIN date, ISO "2026-08-04"
flightNumber?: string // "100", "2005", or "AA2005"
origin?: string // IATA code — with destination, returns every NONSTOP
destination?: string // that airline flies on the route that day
}
// One leg's status, at one end of the flight.
type FlightStatusAirport = {
airportCode: string
cityName: string | null
gate: string | null // null is UNKNOWN, not "no gate" — normal for a flight
// weeks out
terminal: string | null
state: string | null
country: string | null
baggageClaim: string | null // arrival end only
scheduledTime: string | null // ISO 8601 WITH the airport's own UTC offset
estimatedTime: string | null
actualTime: string | null // what happened, once it has; null before the event
scheduledBoardingTime: string | null // departure end only
estimatedBoardingTime: string | null
}
type FlightStatusLeg = {
flightNumber: string
airlineCode: string
flightStatus: string | null // the airline's own wording, verbatim
flightStatusKey: string | null // a stable key behind the wording — branch on
// this, not the display string
flightStatusColor: string | null // the airline's own severity colour, where
// it publishes one (GREEN, ORANGE, RED, ...)
canceled: boolean
diverted: boolean
inFlight: boolean
landed: boolean
departure: FlightStatusAirport
arrival: FlightStatusAirport
equipment: {
tailNumber: string | null
equipmentCode: string | null
iataName: string | null
displayName: string | null // e.g. "Airbus A321neo"
}
disruptionMessage: string | null // the airline's own passenger-facing prose
codeShare: boolean
operatedBy: string | null
marketingCarrier: string | null
wifiAvailable: boolean | null
powerPortAvailable: boolean | null
}
type FlightStatusResult = {
date: string // echoed back, "YYYY-MM-DD"
flightNumber: string | null // null in route mode
origin: string | null
destination: string | null
flights: FlightStatusLeg[] // EMPTY IS AN ANSWER: no such flight that day
warnings: string[] // always present, same contract as every other
// function on this capability
}Examples
// One call returns every matching flight, normalized and price-sorted.
// Destructure BOTH halves — `warnings` is how you learn the fan-out was thin.
const { flights, warnings } = await bowmark.flights.search({
from: "YVR", to: "SFO", depart: "2026-09-01", return: "2026-09-08",
});
// Read them first: a dropped site means "cheapest" is only the cheapest of what
// answered, and the real floor may be on the site that didn't.
for (const w of warnings) log(w);
const cheapest = flights.filter(f => f.price).sort((a, b) => a.price - b.price)[0];
log(`$${cheapest.price} ${cheapest.airlines[0]} — from ${cheapest.site}`);
return {
cheapest,
sitesChecked: [...new Set(flights.flatMap(f => f.sites))], // which sites RETURNED FLIGHTS
incomplete: warnings.length > 0, // ...and whether any never answered
warnings,
totalFlights: flights.length,
};// Flexible dates: sweep a DATE RANGE, keep the cheapest nonstop per day, sort.
// search() takes ONE date, so compose — fan the range out in parallel, and each
// result carries `date` so you can tell the days apart. A tighter per-site budget
// keeps five parallel searches inside one tool call.
const range = ["2026-09-01", "2026-09-02", "2026-09-03", "2026-09-04", "2026-09-05"];
const perDay = await Promise.all(
range.map(async (depart) => {
// A tighter per-site budget keeps five parallel searches inside one tool call.
const { flights, warnings } = await bowmark.flights.search(
{ from: "YVR", to: "SFO", depart, stops: "nonstop" },
{ timeoutMs: 20000 },
);
const priced = flights.filter(f => f.price != null);
const cheapest = priced.sort((a, b) => a.price - b.price)[0];
// Keep `depart` and `warnings` on EVERY day, including one that produced no
// flight. A day whose whole fan-out dropped also has no `cheapest`, so
// returning null here would discard the warnings from exactly the day that
// most needed them — and the sweep would show a silent hole in the range.
return { date: depart, cheapest: cheapest ?? null, warnings };
}),
);
// Log warnings for the WHOLE range first, unfiltered — before anything is dropped.
for (const d of perDay) for (const w of d.warnings) log(`${d.date}: ${w}`);
// Then rank only the days that actually returned a flight.
const ranked = perDay.filter(d => d.cheapest)
.map(d => ({ date: d.date, price: d.cheapest.price, airline: d.cheapest.airlines[0], site: d.cheapest.site, warnings: d.warnings }))
.sort((a, b) => a.price - b.price);
// A day whose fan-out was thin is not comparable to a day whose wasn't.
const unpriced = perDay.filter(d => !d.cheapest).map(d => d.date);
if (unpriced.length) log(`no flight returned for: ${unpriced.join(", ")}`);
log(`cheapest day in range: ${ranked[0]?.date ?? "none"} at $${ranked[0]?.price ?? "?"}`);
return { ranked, unpriced };Providers behind it
| Provider | |
|---|---|
google_flights | Google Flights |
kayak | Kayak |
momondo | Momondo |
cheapflights | Cheapflights |
aa | American Airlines |