A powerful Python library that provides programmatic access to Google Flights data with an elegant CLI interface. Search flights, find the best deals, and filter results with ease. ๐ What makes fli special?
$ npx -y skills add punitarani/fli --agent claude-code
Repo: punitarani/fli
What's inside
A powerful Python library that provides programmatic access to Google Flights data with an elegant CLI interface. Search flights, find the best deals, and filter results with ease.
๐ What makes
flispecial? Unlike other flight search libraries that rely on web scraping, Fli directly interacts with Google Flights' API through reverse engineering. This means:
- Fast: Direct API access means faster, more reliable results
- Zero Scraping: No HTML parsing, no browser automation, just pure API interaction
- Reliable: Less prone to breaking from UI changes
- Modular: Extensible architecture for easy customization and integration
pipx install flights
# Run the MCP server on STDIO
fli-mcp
# Run the MCP server over HTTP (streamable)
fli-mcp-http # serves at http://127.0.0.1:8000/mcp/

{
"mcpServers": {
"fli": {
"command": "/Users/<user>/.local/bin/fli-mcp"
}
}
}
Note: Replace
<user>with your actual username. You can also find the path to the MCP server by runningwhich fli-mcpin your terminal.
The MCP server provides two main tools:
| Tool | Description |
|---|---|
search_flights | Search for flights on a specific date with detailed filters |
search_dates | Find the cheapest travel dates across a flexible date range |
search_flights Parameters| Parameter | Type | Description |
|---|---|---|
origin | string | Departure airport IATA code(s) โ comma-separated for multi |
destination | string | Arrival airport IATA code(s) โ comma-separated for multi |
departure_date | string | Travel date in YYYY-MM-DD format |
return_date | string | Return date for round trips (optional) |
cabin_class | string | ECONOMY, PREMIUM_ECONOMY, BUSINESS, or FIRST |
max_stops | string | ANY, NON_STOP, ONE_STOP, or TWO_PLUS_STOPS |
departure_window | string | Time window in 'HH-HH' format (e.g., '6-20') |
airlines | list | Filter by airline codes (e.g., ['BA', 'AA']) |
exclude_airlines | list | Airline IATA codes to exclude (e.g., ['DL', 'B6']) |
alliance | list | Restrict to alliances: ONEWORLD, SKYTEAM, STAR_ALLIANCE |
exclude_alliance | list | Alliance names to exclude from results |
min_layover | int | Minimum layover duration in minutes (multi-stop only) |
max_layover | int | Maximum layover duration in minutes (multi-stop only) |
top_n | int | Round-trip only: outbound options expanded into return flights (default 5, 1-10) |
currency | string | ISO 4217 code (e.g. 'EUR', 'JPY') โ flows to curr= param |
language | string | BCP-47 language code (e.g. 'en-GB') โ flows to hl= param |
country | string | ISO 3166-1 alpha-2 country code (e.g. 'GB') for gl= param |
sort_by | string | CHEAPEST, DURATION, DEPARTURE_TIME, or ARRIVAL_TIME |
passengers | int | Number of adult passengers |
children | int | Number of children (ages 2-11) |
infants_in_seat | int | Number of infants (under 2) occupying their own seat |
infants_on_lap | int | Number of lap infants (under 2, no seat) |
Total travelers (
passengers + children + infants_in_seat + infants_on_lap) must be between 1 and 9, andinfants_on_lapcannot exceedpassengers.A round trip costs
1 + top_npage fetches (one outbound search, plus one per outbound candidate expanded into return flights), sotop_nis capped at 10. Round-trip results all from one airline? Raisetop_n, or sort differently โ the default sort only ever expands the cheapesttop_noutbounds, which are often the same carrier.
search_dates Parameters| Parameter | Type | Description |
|---|---|---|
origin | string | Departure airport IATA code(s) โ comma-separated for multi |
destination | string | Arrival airport IATA code(s) โ comma-separated for multi |
start_date | string | Start of date range in YYYY-MM-DD format |
end_date | string | End of date range in YYYY-MM-DD format |
trip_duration | int | Trip duration in days (for round-trips) |
is_round_trip | bool | Whether to search for round-trip flights |
cabin_class | string | ECONOMY, PREMIUM_ECONOMY, BUSINESS, or FIRST |
max_stops | string | ANY, NON_STOP, ONE_STOP, or TWO_PLUS_STOPS |
departure_window | string | Time window in 'HH-HH' format (e.g., '6-20') |
airlines | list | Filter by airline codes (e.g., ['BA', 'AA']) |
exclude_airlines | list | Airline IATA codes to exclude |
alliance | list | Restrict to alliances: ONEWORLD, SKYTEAM, STAR_ALLIANCE |
exclude_alliance | list | Alliance names to exclude |
min_layover | int | Minimum layover duration in minutes |
max_layover | int | Maximum layover duration in minutes |
currency | string | ISO 4217 currency code (e.g. 'EUR', 'JPY') |
language | string | BCP-47 language code (e.g. 'en-GB') |
country | string | ISO 3166-1 alpha-2 country code (e.g. 'GB') |
sort_by_price | bool | Sort results by price (lowest first) |
passengers | int | Number of adult passengers |
children | int | Number of children (ages 2-11) |
infants_in_seat | int | Number of infants (under 2) occupying their own seat |
infants_on_lap | int | Number of lap infants (under 2, no seat) |
Same passenger limits as
search_flights: total 1-9,infants_on_lapโคpassengers.
pip install flights
# Install using pipx (recommended for CLI)
pipx install flights
# Get started with CLI
fli --help

๐ Powerful Search
๐บ Cabin Classes
๐ฏ Smart Sorting
๐ก๏ธ Built-in Protection
Searches are served by Google's public search page rather than the
FlightsFrontendService RPC. Since 2026-08 GetShoppingResults and
GetCalendarGraph require an x-goog-batchexecute-bgr header that only the
page's own JavaScript can produce, so a plain HTTP client gets HTTP 200 with no
payload. Fli issues GET https://www.google.com/travel/flights?tfs=<protobuf>
instead and reads the results out of the page's inline AF_initDataCallback
blob keyed ds:1.
What that means in practice:
emissions, bags and
exclude_basic_economy have no tfs field and cannot be reconstructed from
the decoded rows, so they are dropped with a warning. Stops, cabin,
passengers, alliances and layover bounds ride in the request; airline
include/exclude, price cap, max duration and departure windows are applied to
the results after fetching.SearchUnsupportedError. Google loads those results
client-side through the gated RPC, so the page carries no rows to read.
Search each leg separately.get_booking_options is unavailable. It calls GetBookingResults, which
is gated the same way, and currently raises SearchRejectedError. The
per-flight tfs booking deep links are built offline and still work.search() only warns when the fetched page
itself came back with zero rows, not when the caller's own airline/price/
duration/window filter removed rows Google did return. An adults-only
search shows the schedule.SearchDates.search covers at
most 93 dates and a wider range raises ValueError. Budget for it: 93 dates
across 10 workers is several hundred MB of pages and parsed JSON at peak.
A sweep that never manages to load a single page โ the shape a blocked or
consent-gated client produces โ gives up after a handful of dates rather than
paying the retry budget on all of them. Only pages served without results
count towards that: a timeout or a dropped connection says nothing about the
dates not yet tried, so those never abandon a sweep. Measured with the real backoff: 42
page fetches (bounded at 45, so up to ~135 HTTP requests once the client's
own retries multiply in) and about 4 seconds, the same whether the range is 30
days or 93. Unbroken, a 93-date range would have cost 279 fetches and up to
837 requests. The bound is (5 + worker count) x 3, so raising
configure_concurrency raises it proportionally. That breaker disarms for
good the moment any page loads, even an empty one, so it cannot catch a
sweep that is mostly timeouts around one lucky date โ SearchDates.search
raises that case too, whenever nothing priced and at least half the
attempted dates never loaded. A minority of failures alongside real
results, or alongside a confirmed-empty range (None), still returns
normally but logs one warning naming the counts.ds:1 blob; the client retries that case up to twice
(0.5s then 1.5s) before raising SearchParseError. A healthy search never
pays for it.FLI_SOCS_COOKIE. EU/EEA IPs are redirected to Google's consentFAQ
fli is a Claude Code plugin with 1 hand-picked skill for data work, indexed on Flowy. Install it with the command on its page. It includes fli. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it