google-travel-website-api
v0.5.0
Published
Google Travel hotel, flight, and vacation rental operations powered by website-api
Downloads
2,275
Readme
Google Travel website-api integration
Application code imports the public package name, never its internal source paths. Network inventory,
batchexecute decoding/grouping, catalog comparison, date and JSON helpers, option validation, and filter
normalization all come from that shared API — as does the suite model itself: every operation below is a
defineOperation the framework selects, documents, and validates:
import { executeOperation } from "google-travel-website-api";
const hotels = await executeOperation({
operation: "search",
query: "Boston hotels",
sort: "highest-rating",
guestRating: 4.5,
hotelClass: [4, 5],
limit: 10,
});
const candidateResult = await executeOperation({
operation: "hotel-candidates",
query: "Boston Harbor Hotel",
});
// Inspect candidateResult.candidates and match both name and address; never select the first row blindly.
const selectedEntityToken = "ENTITY_TOKEN_FROM_MATCHED_PROPERTY";
const calendar = await executeOperation({
operation: "hotel-calendar",
entityId: selectedEntityToken,
calendarStart: "2026-08-10",
calendarEnd: "2026-09-30",
nights: 1,
currency: "USD",
});Install, build, and run:
pnpm install --filter google-travel-website-api
pnpm build
pnpm start --operation catalog
pnpm start --operation hotel-json --query "Boston hotels" --limit 10
pnpm start --operation hotel-candidates --query "Boston Harbor Hotel"
pnpm start --operation hotel-filter-options --query "Boston hotels"
pnpm start --operation hotel-filters --query "Boston hotels" --sort highest-rating --guest-rating 4.5 --hotel-class 4,5
pnpm start --operation autocomplete --query "Boston Harbor Hotel"
pnpm start --operation hotel-map --query "Boston hotels" --limit 100
pnpm start --operation hotel-calendar --entity-id "ENTITY_TOKEN_FROM_MATCHED_PROPERTY" --calendar-start 2026-08-10 --calendar-end 2026-09-30 --nights 1 --currency USD
pnpm start --operation detail --entity-id "ENTITY_TOKEN_FROM_MATCHED_PROPERTY"
pnpm start --operation hotel-all --url "https://www.google.com/travel/search?ts=..."
pnpm start --operation hotel-payloads --query "Boston hotels" --save-raw --out-dir ./captures
pnpm start --operation vacation-rental-catalog
pnpm start --operation vacation-rental-filter-options --query "Boston vacation rentals"
pnpm start --operation vacation-rental-search --query "Boston vacation rentals" --rental-property-types apartments,houses --bedrooms 2 --bathrooms 1 --amenities kitchen,pool
pnpm start --operation vacation-rental-all --url "https://www.google.com/travel/hotels/entity/..."
pnpm start --operation vacation-rental-payloads --query "Boston vacation rentals" --save-raw --out-dir ./captures
pnpm start --operation scan --query "Boston hotels"
pnpm start --operation flight-catalog
pnpm start --operation flight-filter-options
pnpm start --operation flight-autocomplete --query NYC
pnpm start --operation flight-search --origin JFK --destination LAX --departure 2026-08-20 --return 2026-08-27 --limit 10
pnpm start --operation flight-search --origin JFK --destination LAX --departure 2026-08-20 --trip-type one-way --cabin-class business --passengers 2 --max-stops non-stop --departure-window 6-20 --airlines DL --sort cheapest --currency USD
pnpm start --operation flight-search --origin JFK --destination LAX --departure 2026-08-20 --return 2026-08-27 --select 1
pnpm start --operation flight-dates --origin FOC --destination /m/02_286 --departure 2026-10-15 --calendar-start 2026-10-01 --calendar-end 2026-10-31 --trip-type one-way
pnpm start --operation flight-booking --origin JFK --destination LAX --departure 2026-08-20 --select 1
pnpm start --operation flight-booking --origin JFK --destination LAX --departure 2026-08-20 --return 2026-08-27 --select 1 --select-return 2
pnpm start --operation flight-scan --origin JFK --destination LAX --departure 2026-08-20 --return 2026-08-27 --endpoints-onlySelecting an operation
Every operation is declared with its own inputs, and the framework does the selecting. Name it as the first
argument or with --operation; both spellings work on the CLI, through runSite, and as
POST /v1/sites/google-travel/<operation>:
npx website-api google-travel hotel-candidates --query "Boston Harbor Hotel"
npx website-api google-travel --operation hotel-candidates --query "Boston Harbor Hotel"There is no default operation: running the site without naming one lists every operation rather than quietly running the catalog. Help is per operation, because the flags are:
npx website-api google-travel --help # every operation, grouped by surface
npx website-api google-travel flight-dates --help # only this operation's inputsA flag belonging to a different operation now says so — Option --hotel-class belongs to
"hotel-filter-options"; this is "flight-search" — instead of being accepted and ignored. Two defaults follow
the operation that owns them rather than the whole site: --nights is 1 on hotel-calendar and 7 on
flight-dates, and --select is 0 on flight-search (return the outbound list) but 1 on flight-booking,
which requires a selection.
For the published extension, register and install it before the first operation in a fresh environment. Registry
addition is idempotent; --refresh avoids retaining a stale registry or installed bundle:
npx website-api ext registry add https://unpkg.com/google-travel-website-api@latest/registry
npx website-api ext install google-travel --refresh -y
npx website-api ext list
npx website-api google-travel --operation hotel-json --query "Kyoto hotels"Do not call ext run before this bootstrap: an unconfigured registry reports Site "google-travel" not found in
any registry. For one-shot execution after bootstrap, put the trust option before the extension id. ext run
passes arguments after the id through to the extension, so ext run google-travel -y does not set the runner's
trust option:
npx website-api ext run -y google-travel -- --operation hotel-json --query "Kyoto hotels"Tests:
pnpm test
# Opt-in live HTTP matrix (Hotels + Flights)
GOOGLE_TRAVEL_LIVE=1 node --test --test-concurrency=1 dist/test/live.test.js
# Rerun a single live case
GOOGLE_TRAVEL_LIVE=1 \
GOOGLE_TRAVEL_LIVE_SCOPE="complete HTTP flight scan" \
node --test --test-concurrency=1 dist/test/live.test.jsRelease from a clean working tree with an exact version:
pnpm release 0.3.0The release command verifies npm authentication and version availability, builds and tests the package, commits the generated registry, creates the version tag, publishes the exact tested files, pushes the branch and tag, and verifies the published npm version.
A response-contract change — a renamed or removed field, or a field that moves behind --raw — breaks
existing callers even on 0.x, so release it as a minor bump rather than a patch. The current contract
(endpoints replacing dataEndpoints/allEndpoints, url replacing finalUrl, endpointCatalog for
published inventory, and --raw gating Google's payloads) is one such change: pnpm release 0.3.0.
Module layout
src/
index.ts public API surface
cli.ts CLI entry
execute-operation.ts programmatic entry
extension-site.ts registry/extension wrapper
google-travel.ts the two site contracts
parameters.ts the shared input vocabulary, as defineParameters groups
core/ primitives with no website-api equivalent
json-utils.ts network.ts protobuf.ts rate-limit.ts
google/ Google transport, error envelopes, RPC catalog, shared operation plumbing
catalog.ts catalog-operations.ts envelope.ts http.ts runtime.ts
hotels/ autocomplete, entity tokens, filters, payload decoding, operations
autocomplete.ts entity-token.ts filters.ts json.ts markers.ts operations.ts
flights/ filters, location entities, RPC codec, deep links, operations
filters.ts links.ts locations.ts operations.ts rpc.tsgoogle-travel.ts holds only what the whole site shares; each operation is a defineOperation beside the
code that performs it, in hotels/operations.ts, flights/operations.ts, and google/catalog-operations.ts,
over the shared google/runtime.ts. Neither domain imports the other. There is no dispatch table and no
published operation list to keep in sync: website-api selects the operation and generates help, the OpenAPI
document, and the registry entry from the same declarations.
Date, JSON, batchexecute, network-inventory, catalog, filter, and option helpers come from website-api
directly rather than through a local re-export. core/ keeps only what the shared API has no equivalent for:
the token-bucket pacing Google's quiet quota requires, the protobuf codec behind tfs and entity tokens, the
Google-origin URL default, and the Google-specific endpoint inventory built on the shared primitive.
Response contract
Every operation answers with a JSON object built the same way:
| Field | Present | Meaning |
| --- | --- | --- |
| operation | always | The operation that produced this response, echoed back. |
| endpoints | always | The HTTP requests this run actually made, one entry per method/path/RPC. Empty for an operation that answers from published data. |
| url | when applicable | The Google URL the operation worked from or an equivalent public deep link. |
| rpcIds, rpcGroups | lodging batchexecute operations | Which multiplexed RPCs answered — with their catalog name, purpose, source paths, and counts. Named flight-service operations report rpc instead. |
| count, totalCount | when applicable | The returned row count and, where available, the total matched count. |
| endpointCatalog | the four catalog operations | The known endpoint inventory, which is a different record than an observed request. |
| google, googleJson, requests | normally --raw only | Google's original positional payloads and f.req envelopes. The two *-payloads operations expose google by default because transport inspection is their purpose. |
endpoints is the single endpoint field — the previous dataEndpoints and allEndpoints are gone — and it
always means the same thing: requests this run made, as EndpointHit records with key, method, origin,
path, queryKeys, rpcIds, and counts. Published inventory is a different kind of record
(EndpointCatalogEntry: id, category, description, implemented) and lives under endpointCatalog, so
one field name never carries two shapes. catalog, hotel-catalog, vacation-rental-catalog,
flight-catalog, and flight-filter-options issue no request, so their endpoints is [].
The top-level transport views are opt-in for normal search, detail, calendar, autocomplete, scan, and flight
operations. They repeat the same data as several positional representations and dominate the response: a
three-result hotel-json is ~250 KB by default and ~4.7 MB with --raw. Pass --raw when inspecting a normal
operation. Use hotel-payloads / vacation-rental-payloads when the transport is the subject; those
operations intentionally keep google in the default response. One current exception is scan: its
top-level google, requests, and googleJson fields are gated, but its parsed entities and reviews retain
their nested raw / rawSources fields even without --raw.
--endpoints-only reduces any response to its identity and endpoint inventory.
The package-owned pnpm start CLI reports success and failure the same way, so one parse handles both:
pnpm start --operation hotel-prices{
"ok": false,
"operation": "hotel-prices",
"error": { "name": "Error", "message": "Unknown operation: hotel-prices. Supported: catalog, ..." }
}Success prints { "ok": true, ... } on stdout; a failure prints { "ok": false, ... } on stderr and exits 1.
A refusal from a Google RPC service carries error.service, error.status, and error.retryable; --raw
adds error.stack. ok belongs to the CLI: the library throws instead, and errorEnvelope is exported for
callers that want to render the same shape.
The separate npx website-api google-travel and ext run runners print the operation result directly, with
no ok field. Their status and failure diagnostics use stderr; parse stdout without merging stderr into it.
test/contract.test.ts holds this contract to every one of the 29 operations, running the network ones
against a stubbed transport: each names itself, reports endpoints of the documented shape, gates the
top-level transport views as described above, and serializes without losing a value.
Hotel endpoint inventory
Google Hotels is a server-rendered/stateful frontend, not a stable REST API. The integration uses anonymous
HTTP documents and direct batchexecute calls; it never launches Chrome or executes Google's JavaScript.
It decodes raw f.req positional JSON and each wrb.fr response while retaining Google's arrays. MQghPc
bootstraps destination, filter, record, and map state, AtySUc enriches a selected property, and canonical
properties use /travel/hotels/entity/:entityId documents.
Private data calls are multiplexed through:
POST /_/TravelFrontendUi/data/batchexecute?rpcids=<RPC_ID>&...Hotel flows observed through 2026-08-10 used these RPC IDs:
MQghPc— anonymous server-side destination, search, filter metadata, records, and map bootstrapmejVKc— destination/property autocompleteFCE32b— map geometry, bounds, and search stateAtySUc— map prices plus the selected property's full record: identity, coordinates, address, phone, nearby places, price, taxes, providers, rooms, reviews, topics, amenities, and photosM0CRd— shared Travel hotel page datayY52ce— per-hotel daily price calendar for a date range, length of stay, and currencybdmBfe,ocp93e,zM1L7d— shared hotel entity/detail datapSDzMb— entity photo/location-associated data
The typed entities, mapMarkers, supplementalReviews, and reviewPageTokens fields are derived only from
Google's own payloads. --raw returns those payloads as well: google.requests[].calls[].payload and
google.responses[].payload in Google's original JSON-array format, plus the f.req strings and full
transport envelopes. --save-raw writes the decoded request and response JSON to an ignored artifact. A real
Google Travel URL passed with --url is fetched directly. Hotel filter values are normalized without relying
on the frontend's opaque ts, qs, or ap UI serialization; the enforcement boundary is described below.
rpcGroups names the RPCs that answered without carrying their payloads: one entry per RPC with its stable
name, purpose, applicable surface and sections, source paths, and request/response counts. That makes
AtySUc, M0CRd, VxbZPe, and the other multiplexed calls identifiable in every response. With --raw,
google.byRpc adds the request payloads, response payloads, and summaries for the same grouping.
Several hotel operations project that one capture differently rather than returning it whole:
search,hotel-json,hotel-filters— the full search response: markers, property records, reviews, offershotel-map— markers only, plus the bounding box they occupy (derived from the markers, not a Google viewport)hotel-insights— ratings, review distributions, review topics and summaries, price bands, and neighborhood context per property, without offers, photos, or raw recordsdetail,hotel-all— one property, optionally with every section and its price calendarautocomplete— properties, places, and query suggestionshotel-destinations— the places only, for turning a typed place name into a search query
Typed hotel search filters
search, hotel-json, and hotel-filters parse and echo every group in the published filter contract. The
current result filter applies only minimum/maximum displayed price, guest rating, hotel class, and the
lowest-price, highest-rating, and most-reviewed sorts to decoded map markers. relevance preserves
Google's order. The remaining accepted fields are validated and echoed but do not currently remove or
reorder results, so callers must not treat them as enforced constraints:
--sort:relevance,lowest-price,highest-rating, ormost-reviewed--min-price,--max-price: numeric amounts parsed from displayed nightly prices--property-type:hotelsorvacation-rentals--free-cancellation,--special-offers--guest-rating:any,3.5,4.0, or4.5--hotel-class: any combination of2,3,4, and5--amenities: repeatable names/slugs such asfree-wifi,pool, orfitness-center--eco-certified--brands: repeatable Google chain or subchain labels such asHilton Honors
Call hotel-filter-options to retrieve the published filter vocabulary and a price ceiling derived from the
current destination's returned markers. available.price.displayedMaximum is quoted verbatim from the
highest-priced returned marker, in whatever currency Google returned it; --currency reaches the entity and
calendar RPCs, not this search bootstrap. Sort, rating, class, property-type, room, offer, and amenity choices
come from static package constants rather than destination discovery. In the current implementation,
available.amenities is the combined global list: restrict it with
predefined.amenitiesByPropertyType[propertyType] before submitting a value. available.brands is a short,
hardcoded set of major loyalty programs, not an exhaustive list of chains available at the destination.
Stable choices are also exported as HOTEL_SEARCH_FILTER_OPTIONS for schema and tool generation.
const choices = await executeOperation({
operation: "hotel-filter-options",
query: "Boston hotels",
});
// For amenities, intersect available.amenities with
// choices.predefined.amenitiesByPropertyType[choices.propertyType].Repeat --hotel-class, --amenities, and --brands, or provide comma-separated values. Hotel-only offers,
class, sustainability, and brand filters cannot be combined with property-type=vacation-rentals.
Vacation-rental endpoints and filters
Vacation rentals deliberately have separate discoverable operations while reusing the same lodging capture, validation, filtering, and RPC grouping code:
vacation-rental-catalog— document, section, transport, support endpoints, and labeled RPC inventoryvacation-rental-filter-options— the complete predefined contract plus destination-specific price capvacation-rental-search— filtered search and grouped RPC responsesvacation-rental-detail/vacation-rental-all— overview or all prices/reviews/photos/about/location sectionsvacation-rental-payloads— decoded payloads and per-RPC summaries
The shared sort, price, and guest-rating constraints are enforced on decoded markers. Rental property type, bedroom, bathroom, and amenity values are validated and echoed, but they do not currently filter the returned markers. Their accepted values are predefined:
--rental-property-types:apartments,houses,cottages,villas,houseboats,other--bedrooms:0,1,2,3,4, or5(Google displays these as 0+ through 5+)--bathrooms:0,1,2,3,4, or5- rental amenities:
free-wifi,fitness-center,air-conditioned,kid-friendly,pool,pet-friendly,hot-tub,kitchen,patio-or-deck,outdoor-grill,crib,fireplace
The observed rental search RPCs are FCE32b, AtySUc, and M0CRd; entity sections use ocp93e,
zM1L7d, yY52ce, and rental-specific photo RPC VxbZPe. Autocomplete uses mejVKc.
Property identity
Google Travel identifies one hotel by an entity token — the Ch… value in
/travel/hotels/entity/<token>. It is a base64url protobuf pairing a numeric property id with that
property's Knowledge Graph id:
{ 1: { 1: <propertyId>, 3: <knowledgeGraphId> }, 2: 1 }autocomplete returns both members verbatim, so properties[].entityToken is composed rather than guessed,
and parseHotelEntityToken decodes any token Google mints back into its two parts. Every hotel record found
in a payload is recognised by decoding the token at its positional slot, which is why nested and duplicated
copies of the same property collapse to one entry.
Property collections differ by operation; callers must not assume one shared key:
| Operation | Property collection | Entity-token path |
|---|---|---|
| autocomplete | properties[] | properties[].entityToken |
| hotel-json, search, detail | entities[] | entities[].entityToken |
| hotel-candidates | candidates[] | candidates[].entityToken |
| hotel-map | mapMarkers[] | mapMarkers[].entityToken |
In JavaScript, check the applicable value with Array.isArray before mapping it. In particular,
hotel-json has no properties field.
The other identifiers in a response address different things and are not interchangeable with the token:
entities[].entityId and properties[].propertyId are numeric property ids, placeId is a Maps place,
and /m/… or /g/… are Knowledge Graph ids. The yY52ce calendar accepts only the entity token and
answers anything else with zero days, so hotel-calendar rejects the other forms with a message naming the
right one instead of returning an empty result.
For reliable automation, call hotel-candidates --query <hotel name>, inspect its compact candidates[]
response, confirm both the exact name and address, and pass that candidate's entityToken to
hotel-calendar --entity-id. Do not redirect the candidate response to a temporary file, run a detached
jq command, automatically select the first result, or reuse a token from documentation. The older
hotel-calendar --query resolver remains for compatibility but is not an agent-safe workflow because Google
autocomplete may return only destination or query suggestions.
hotel-calendar calls the raw yY52ce RPC using an anonymous HTTP POST. It does not launch Chrome, read
cookies, fetch the hotel HTML, or require Google to establish a browser session. It returns
one priceCalendar.days[] entry per check-in date with check-out date, length of stay, displayed nightly
price, tax-inclusive price, numeric base amount, taxes, fixed fees, and total. Pass --raw to include each
entry's original Google row. Rows Google returns with a date but no price are omitted.
When Google has no daily prices for the property and date range, the operation still succeeds with
availability.available: false, priceCalendar.available: false, and an empty priceCalendar.days array.
hotel-all includes this same daily calendar alongside all entity sections.
yY52ce answers with prices only — no property name — so the operation pairs it with a token-only AtySUc
lookup and reports property (entity token plus any resolved name, address, and hotel class) beside the
prices. Those descriptive fields are nullable when Google does not return the entity record. The compatibility
--query path first tries mejVKc, but automation should use hotel-candidates followed by --entity-id.
Both googleHotelCalendarHttpSite and the combined default googleTravelSite declare transport: "http".
No operation calls ctx.browser() or ctx.network.capture().
When given an entity URL or --entity-id, detail fetches the entity document over HTTP, derives its title,
resolves the matching result through MQghPc, and enriches it with AtySUc. A /travel/search URL instead
uses its query and selects the lead result. hotel-all additionally fetches each entity-section document and
the anonymous yY52ce price calendar when it can resolve an entity token.
The complete observed inventory, including shell, OneGoogle, Maps, and telemetry routes, is in
src/google/catalog.ts. Telemetry is cataloged but is deliberately not replayed as a product API. scan
deduplicates the raw requests by method, origin, path, and RPC ID and reports IDs missing from the catalog.
flight-scan is its flight counterpart, but flights answer from named services rather than multiplexed RPC
IDs, so it reports services and newServices against FLIGHT_SERVICE_CATALOG alongside rpcIds.
Flights endpoint inventory
Flights uses its own FlightsFrontendUi application. Initial and selected-leg searches read structured
ds:1 shopping data from public Flights documents; date-grid and booking call named
FlightsFrontendService endpoints; autocomplete uses the multiplexed H028ib batchexecute RPC:
POST /_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetShoppingResults
POST /_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetCalendarGrid
POST /_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetBookingResults
POST /_/FlightsFrontendUi/data/batchexecute?rpcids=H028ibThe three named service endpoints do not use the batchexecute envelope. Their body is a bare
[null, "<json>"] pair in the same f.req form field, and the response is )]}' followed by one or more
length-prefixed wrb.fr chunks. Those length headers count UTF-8 bytes, so decodeGoogleRpcStream reads
the encoded buffer — airline and airport names contain non-ASCII text, and character offsets would
desynchronise the stream. Autocomplete uses the normal batchexecute request and response envelope instead.
City-wide flight searches use Google's geographic entity, not a guessed metropolitan IATA code. Agents must
run flight-autocomplete --query <city or metro>, match the intended candidate by name, city, and
description, and pass that candidates[].value as --origin or --destination. For example, an NYC lookup
returns New York with value: "/m/02_286" alongside the individual JFK, LGA, and EWR airport
candidates. Each candidate includes { kind, name, city, description, code, entityId, value }. A direct,
unambiguous airport IATA code needs no lookup.
Because the request carries the filters, Google applies them: stops, cabin, airlines and alliances (include and exclude), layover bounds, price cap, duration cap, emissions, bag counts, and sort order all come back applied, and the result set is the one the UI would show. One-way searches return real one-way fares — the previous document-scraping implementation could not price them at all and substituted round-trip totals.
A round trip lists outbound options first, exactly as the UI does; --select <n> encodes the chosen legs into
Google's second public search document and returns its complete-itinerary return prices. flight-booking repeats that selection
and then calls GetBookingResults, because Google identifies an itinerary by a booking token and by the
chosen legs on each segment — a token alone returns no vendors. On a round trip it takes --select for the
outbound and --select-return for the return leg; both default to the first option.
flight-dates prices a whole range through GetCalendarGrid, Google's date grid, and is the flight
counterpart to the hotel price calendar. Google sweeps at most 61 days per request, so --calendar-start and
--calendar-end are validated against that. The response carries dates[] of
{ departure, returnDate, price, currency } plus the cheapest entry. Round-trip grids require --return to
establish a round-trip request, while --nights sets the stay length priced for every departure in the range.
Flight operations build requests from structured inputs. They do not accept the site's lodging-oriented --url
parameter or a caller-supplied --tfs; the extension generates that state and fetches the corresponding public
shopping document itself.
Pacing and refused calls
A refusal is not an empty result set. Google answers HTTP 200 with a wrb.fr row carrying no payload and a
gRPC status instead — [["wrb.fr",null,null,null,null,[13]],["di",45],…], where 13 is INTERNAL. In the
documented measurements Google did not use HTTP 429 for these refusals, although the retry policy also handles
429. Decoded naively, the status response can be mistaken for "no flights match" unless its status is checked.
Measured against the live service on 2026-08-11, in one session from one client and IP:
- Short bursts are fine. A rested client answered 14 back-to-back requests in 2.6s (≈5.4 req/s), and a 150-request run at ≈2.5 req/s was answered 143 times, with refusals scattered rather than a cliff.
- Within that window, refusal likelihood tracked request rate: roughly 1 in 1–7 at ≈4 req/s, 1 in 20 at ≈0.7 req/s, and each refusal was momentary — the next request ~2s later always succeeded.
- After several hundred requests across the session, refusals became persistent rather than momentary, on every flight service at once, and outlasted the retry budget. So there does appear to be a longer-horizon limit; this measurement did not establish its size or its reset period, and the short-window numbers above should not be read as a licence to sustain that rate.
Treat the pacing defaults as a floor, not a ceiling — a datacenter IP or parallel callers will need slower settings, and heavy use should expect to back off for minutes rather than seconds.
postGoogleService raises GoogleRpcServiceError carrying the status. Google now gates anonymous flight
services with a per-page BotGuard proof and reports a bare HTTP call as status 13 (INTERNAL). Initial
flight searches therefore consume the structured ds:1 result rendered in the public Flights document;
date-grid and other gated follow-ups fall back to a clean browser context so Google's own JavaScript creates
the proof. Requests are still paced through a token bucket shared process-wide per frontend.
The bucket defaults to 2 requests/second with a burst of 8. Override with GOOGLE_TRAVEL_RPC_PER_SECOND and
GOOGLE_TRAVEL_RPC_BURST. A persistent refusal is not something retry or pacing can fix from inside a single
run — it needs a pause between runs.
Typed flight search filters
Call flight-filter-options to retrieve the machine-readable contract before constructing a search. Flight
search supports round-trip and one-way dates, metro-area or airport origin/destination choices, all four
cabins, structured passenger counts, stops, departure/arrival windows, airline and alliance inclusion or
exclusion, layover duration, maximum price and duration, emissions, carry-on and checked bags, locale,
currency, and deterministic sorting. Airline values are IATA codes published by the filter-options operation.
Cabin and passenger controls are encoded into Google's public tfs state. Stops, time windows, airline codes,
layover bounds, duration, price, emissions, and sorting are applied to Google's complete structured initial
result rows. --departure-window and --arrival-window constrain the outbound leg only.
--currency defaults to USD and is always sent. Google reads the currency off the request and infers it from
the caller's IP when the request omits one, so an unset currency does not mean a default — it means whatever the
host's egress looks like, and the same search answered USD from one machine and RUB from another. The
default is applied when the filters are parsed rather than only declared as a flag, so a library caller through
runSite/executeOperation and an HTTP-gateway caller get it too; both are handed the options they passed, and
only the CLI fills declared defaults in. Every response reports the currency in effect under
filters.currency.
The TypeScript API accepts the same uppercase/underscore aliases used by Fli, while returning normalized kebab-case values:
const flights = await executeOperation({
operation: "flight-search",
origin: "JFK",
destination: "LAX",
departure: "2026-08-20",
tripType: "one-way",
cabinClass: "business",
adults: 2,
children: 1,
maxStops: "ONE_STOP",
departureWindow: "6-20",
airlines: ["DL"],
excludeAlliances: ["STAR_ALLIANCE"],
minLayover: 60,
maxLayover: 360,
sort: "duration",
currency: "USD",
});filters echoes the normalized request and url is the equivalent public Google Flights deep link. The
initial flight-search operation fetches that document and reads its structured ds:1 data; it does not scrape
rendered result text or CSS selectors.
Each itinerary is fully decoded: price and currency (the currency is a protobuf field inside Google's
price token), durationMinutes, stops, selfTransfer, mixedCabin, per-leg airline, flightNumber,
operatingAirline, airports and their full names, departure/arrival timestamps, aircraft, legroom,
co2Grams, and amenities (wifi, power, on-demand video, legroom rating). Layovers carry airport, city, and
wait time. emissions reports this itinerary's grams against the typical figure for the route.
A row whose price head is empty is Google declining to publish a list price — common for premium-cabin round
trips — not a malformed row. Those itineraries are returned with price: null and a usable bookingToken, so
flight-booking can still resolve real fares for them.
Coverage boundary
There is no finite, published Google Travel endpoint list. RPC IDs, build labels, opaque page-state tokens, experiments, locale-specific calls, signed-in features, and telemetry can change without notice. The catalog therefore distinguishes the routes observed in the exercised hotel and flight flows from dynamic scan output. Run both HTTP scanners for the locale and request variants you need before relying on the inventory. “Complete” here means every route observed across those exercised flows; no client can guarantee undiscoverable experiment or account-only routes that Google did not serve.
