MCP Connector

Search and Compare Flights with Google Flights

MCP server, CLI and Python library for searching Google Flights: detailed flight search and cheapest-date search with rich filters.

Works with google flights

84
Spark score
out of 100
Updated last month
Source checked Oct 14, 2026
Version fli-js-v0.0.4
Models
universal

Add to Favorites

Why it matters

Leverage real-time data from Google Flights to search for flights, compare prices, and identify the best travel routes. This asset provides a Python library and server for seamless integration with flight data.

Outcomes

What it gets done

01

Search for flights using Google Flights data.

02

Compare flight prices across different options.

03

Identify optimal flight routes.

04

Access real-time flight information.

Source

Get it from source

Spark does not host a copy of it.

Open source

Reports

Agent outcome reports

No reports yet

Overview

Fli is a Python library, command line tool and MCP server for searching Google Flights. Through MCP it gives an assistant two tools: search_flights for a specific date with detailed filters, and search_dates to find the cheapest dates across a flexible range. Results come from Google's public flight search page: Fli issues a GET with an encoded search and reads the page's inline data blob. Use it to ask an assistant for flights between airports, compare cabins, airlines or alliances, or find the cheapest days in a window. It cannot do multi-city searches or booking options in its current form, and searches with children or infants may return few or no results.

What it does

Fli provides programmatic access to Google Flights data through a Python library with a CLI, an MCP server and a TypeScript port. Through MCP an assistant gets two tools: search_flights for a specific date with detailed filters and search_dates for the cheapest travel dates across a flexible date range. It supports one-way and round-trip searches, multi-airline filtering, cabin class selection (economy, premium economy, business, first), stop preferences, flexible departure times and sorting by price, duration, departure time or arrival time. It has built-in rate limiting, automatic retries, error handling and input validation.

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 a GET to Google's flights page with a protobuf-encoded search and reads the results out of the page's inline AF_initDataCallback blob keyed ds:1. The limitations below follow from that transport.

When to use - and when NOT to

Use it when you want an assistant to search flights in natural language, for example flights from JFK to LAX on December 25th, the cheapest dates to fly from NYC to London in January, or business class from SFO to NRT with no stops. Origin and destination take IATA airport codes, comma-separated for several.

Limitations of the page-based transport:

  • Three filters are not supported: emissions, bags and exclude_basic_economy have no tfs field, so they are dropped with a warning. Stops, cabin, passengers, alliances and layover bounds ride in the request; airline include and exclude, price cap, maximum duration and departure windows are applied to the results after fetching.
  • Multi-city search raises SearchUnsupportedError, because Google loads those results client-side through the gated RPC; search each leg separately.
  • get_booking_options is unavailable and currently raises SearchRejectedError. The per-flight tfs booking deep links are built offline and still work.
  • Expect roughly 20 to 45 itineraries per search, fewer than the old RPC returned, and a client-side filter cannot back-fill the list the way Google's server-side one did.
  • Children and infants thin the results, sometimes to nothing. Google prices those parties client-side, so the page inlines fewer itineraries for them, and in premium cabins often none. Extra adults cost nothing. An empty result for such a search does not mean the route has no flights, and search() only warns when the fetched page itself came back with zero rows, not when your own airline, price, duration or window filter removed rows Google returned. An adults-only search shows the schedule.
  • Date searches cost one page fetch per date. One SearchDates.search covers at most 93 dates and a wider range raises ValueError; 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. Only pages served without results count towards that; a timeout or dropped connection never abandons a sweep. The bound on page fetches is (5 + worker count) x 3, so raising configure_concurrency raises it proportionally. The breaker disarms for good the moment any page loads, even an empty one, so SearchDates.search also raises when 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), returns normally but logs one warning naming the counts.
  • Roughly one request in sixty returns HTTP 200 with no ds:1 blob; the client retries up to twice (0.5s then 1.5s) before raising SearchParseError. A healthy search never pays for it.
  • Round trips cost 1 + top_n page fetches: one outbound search plus one per outbound candidate expanded into return flights. top_n applies to round trips only, defaults to 5 and is capped at 10; the CLI --top-n is rejected if set on a one-way search. The default sort only expands the cheapest top_n outbounds, which are often the same carrier, so if all round-trip results come from one airline, raise top_n or sort differently.

Capabilities

search_flights takes origin, destination and departure_date (YYYY-MM-DD), with optional return_date, cabin_class, max_stops (ANY, NON_STOP, ONE_STOP, TWO_PLUS_STOPS), departure_window in HH-HH form such as 6-20, airlines and exclude_airlines (IATA codes), alliance and exclude_alliance (ONEWORLD, SKYTEAM, STAR_ALLIANCE), min_layover and max_layover in minutes (multi-stop only), top_n, currency (ISO 4217, passed as curr=), language (BCP-47, hl=), country (ISO 3166-1 alpha-2, gl=), sort_by (CHEAPEST, DURATION, DEPARTURE_TIME, ARRIVAL_TIME) and passenger counts: passengers for adults, children aged 2-11, infants_in_seat and infants_on_lap (under 2, no seat). Total travelers must be between 1 and 9, and infants_on_lap cannot exceed passengers.

search_dates takes start_date and end_date, an optional trip_duration in days and is_round_trip, the same filters and locale options, the same passenger limits, and sort_by_price.

The CLI mirrors this. fli flights JFK LHR 2026-10-25 searches a date, with options for return date, time window, airlines, alliances, layovers, cabin class, stops, sorting, currency, language, country, passengers and --top-n. fli dates takes --from, --to, --duration and --round, with day-of-week filters such as --monday --friday. fli multi takes legs as --leg SEA,HKG,2026-12-26. --format json is experimental and its schema may change.

In Python, build FlightSearchFilters from PassengerInfo and FlightSegment objects and call SearchFlights().search(filters); results carry price, duration, stops and legs with airline, flight number, airports and datetimes, and round-trip results come back as outbound and return pairs. SearchDates takes DateSearchFilters and returns dates with prices. Filters also accept airlines, a maximum duration and layover restrictions. A 1:1 TypeScript port is on npm as fli-js, installed with bun add fli-js or npm install fli-js, with the same models, filter encoding and search-page transport. Its consequences match: the three filters are dropped with a warning, multi-city throws SearchUnsupportedError, getBookingOptions throws SearchRejectedError, date searches cost one page fetch per date capped at 93, and FLI_SOCS_COOKIE controls the consent cookie.

Two environment settings matter. EU and EEA IPs are redirected to Google's consent interstitial, which serves no ds:1 blob. The client sends a pre-accepted SOCS cookie by default; set FLI_SOCS_COOKIE to change the value, or to an empty string to send none. Behind a TLS-intercepting corporate proxy, set FLI_CA_BUNDLE, CURL_CA_BUNDLE or REQUESTS_CA_BUNDLE (checked in that order) to a PEM CA bundle path. A bad or missing path, or a certificate the bundle does not cover, raises SearchCertificateError naming the variable and is not retried. The variable is read once per worker thread on its first request; changing it does not affect a thread's already-created session.

How to install

Install with pipx, then run the MCP server on STDIO or over streamable HTTP:

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/

For Claude Desktop, add an mcpServers entry to claude_desktop_config.json (on macOS at ~/Library/Application Support/Claude/claude_desktop_config.json) whose command is fli-mcp, or its full path (find it with which fli-mcp), then restart Claude Desktop. The library also installs with pip install flights, and the CLI is available as fli, with fli --help listing its commands. MIT licensed.

Source README

🛫 Fli - Flight Search MCP Server and Library

Ask DeepWiki

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? 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

MCP Server

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/

MCP Demo

Connecting to Claude Desktop
{
  "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 running which fli-mcp in your terminal.

MCP Tools Available

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, and infants_on_lap cannot exceed passengers.

A round trip costs 1 + top_n page fetches (one outbound search, plus one per outbound candidate expanded into return flights), so top_n is capped at 10. Round-trip results all from one airline? Raise top_n, or sort differently - the default sort only ever expands the cheapest top_n outbounds, 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.

Quick Start

pip install flights
# Install using pipx (recommended for CLI)
pipx install flights

# Get started with CLI
fli --help

CLI Demo

Features

  • 🔍 Powerful Search

    • One-way flight searches
    • Multi-city flight searches
    • Flexible departure times
    • Multi-airline support
    • Cabin class selection
    • Stop preferences
    • Custom result sorting
  • 💺 Cabin Classes

    • Economy
    • Premium Economy
    • Business
    • First
  • 🎯 Smart Sorting

    • Price
    • Duration
    • Departure Time
    • Arrival Time
  • 🛡️ Built-in Protection

    • Rate limiting
    • Automatic retries
    • Comprehensive error handling
    • Input validation

Search transport

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:

  • Three filters are not supported. 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.
  • Multi-city raises 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.
  • Fewer rows per search. Expect roughly 20-45 itineraries, fewer than the old RPC returned - and a client-side filter cannot back-fill the list the way Google's server-side one did.
  • Children and infants thin the results - sometimes to nothing. Google prices those parties client-side, so the page inlines fewer itineraries for them, and in premium cabins often none (measured 2026-09: JFK→LHR economy 23 rows for one adult, 16 with an infant; SFO→NRT business 9 rows for one or two adults, 0 with a child). Extra adults cost nothing. An empty result for such a search does not mean the route has no flights - and neither does it mean this is why: 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.
  • Date searches cost one page fetch per date. The page has no calendar grid, so a range is priced date by date; one 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.
  • A page occasionally arrives without results. Roughly one request in sixty returns HTTP 200 with no 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 consent interstitial, which serves no ds:1 blob. The client sends a pre-accepted SOCS consent cookie by default; set FLI_SOCS_COOKIE to change the value, or to an empty string to send none.
  • FLI_CA_BUNDLE / CURL_CA_BUNDLE / REQUESTS_CA_BUNDLE. Behind a TLS-intercepting corporate proxy, set one of these (checked in that order) to a PEM CA bundle path. A bad or missing path, or a certificate the bundle doesn't cover, raises SearchCertificateError naming the variable - the client does not retry it, since a fixed bundle either verifies or it doesn't. Read once per worker thread on its first request; changing the value doesn't affect a thread's already-created session.

CLI Usage

Search for Flights
# Basic flight search
fli flights JFK LHR 2026-10-25

# Advanced search with filters
fli flights JFK LHR 2026-10-25 \
    --time 6-20 \             # Departure time window (6 AM - 8 PM)
    --airlines BA,KL \        # Airlines (British Airways, KLM)
    --class BUSINESS \        # Cabin class
    --stops NON_STOP \        # Non-stop flights only
    --sort DURATION           # Sort by duration

# Alliance + exclude + locale (May-2026 filter additions)
fli flights JFK LHR 2026-10-25 \
    --alliance ONEWORLD \
    --exclude-airlines AA \
    --min-layover 90 \
    --max-layover 360 \
    --currency EUR --language en-GB --country GB

# Round trip: raise --top-n to see more airlines (default 5, max 10, costs 1 + top_n
# page fetches per search)
fli flights JFK LHR 2026-10-25 --return 2026-11-01 --top-n 8

# Family mix: 2 adults, 1 child, 1 lap infant (total must be 1-9)
fli flights JFK LHR 2026-10-25 --passengers 2 --children 1 --infants-on-lap 1

⚠️ Experimental --format json is experimental. The JSON schema may change while the machine-readable CLI contract settles.

# Return machine-readable flight results
fli flights JFK LHR 2026-10-25 --format json
Find Cheapest Dates
# Basic date search
fli dates JFK LHR

# Advanced search with date range
fli dates JFK LHR \
    --from 2026-01-01 \
    --to 2026-02-01 \
    --monday --friday      # Only Mondays and Fridays

⚠️ Experimental --format json is experimental for date searches as well.

# Return machine-readable date search results
fli dates JFK LHR --from 2026-01-01 --to 2026-02-01 --format json
Multi-city Search
# Two-leg multi-city trip
fli multi --leg SEA,HKG,2026-12-26 --leg PEK,SEA,2027-01-02

# Three-leg multi-city trip with filters
fli multi \
    -l SEA,NRT,2026-12-26 \
    -l NRT,HKG,2026-12-30 \
    -l HKG,SEA,2027-01-05 \
    --class BUSINESS \
    --stops 0
CLI Options
Flights Command (fli flights)
Option Description Example
--return, -r Return date 2026-10-30
--time, -t Departure time window 6-20
--airlines, -a Airline IATA codes BA,KL
--exclude-airlines, -A Airline IATA codes to exclude DL,B6
--alliance Restrict to alliance(s) ONEWORLD, SKYTEAM
--exclude-alliance Alliance(s) to exclude STAR_ALLIANCE
--min-layover Minimum layover (minutes) 90
--max-layover Maximum layover (minutes) 360
--top-n Round-trip only: outbound options expanded into return flights (default 5, 1-10) 8
--currency ISO 4217 currency code EUR, JPY
--language BCP-47 language code (Google hl=) en-GB
--country ISO 3166-1 alpha-2 country (gl=) GB
--class, -c Cabin class ECONOMY, BUSINESS
--stops, -s Maximum stops NON_STOP, ONE_STOP
--sort, -o Sort results by CHEAPEST, DURATION
--passengers, -p Number of adult passengers 2
--children Number of children (ages 2-11) 1
--infants-in-seat Number of infants occupying their own seat 1
--infants-on-lap Number of lap infants (must be ≤ adults) 1
--format Output format text, json

Total passengers (adults + children + infants) must be between 1 and 9.

--top-n only applies to round trips (it is rejected if set on a one-way search) and costs 1 + top_n page fetches. Round-trip results all from one airline? Raise --top-n (up to 10), or --sort differently - the default sort only expands the cheapest top_n outbounds, which are often the same carrier.

Dates Command (fli dates)
Option Description Example
--from Start date 2026-01-01
--to End date 2026-02-01
--duration, -d Trip duration in days 3
--round, -R Round-trip search (flag)
--airlines, -a Airline IATA codes BA,KL
--exclude-airlines, -A Airline IATA codes to exclude DL,B6
--alliance Restrict to alliance(s) ONEWORLD
--exclude-alliance Alliance(s) to exclude STAR_ALLIANCE
--min-layover Minimum layover (minutes) 90
--max-layover Maximum layover (minutes) 360
--currency ISO 4217 currency code EUR, JPY
--language BCP-47 language code en-GB
--country ISO 3166-1 alpha-2 country GB
--class, -c Cabin class ECONOMY, BUSINESS
--stops, -s Maximum stops NON_STOP, ONE_STOP
--time Departure time window 6-20
--sort Sort by price (flag)
--[day] Day filters --monday, --friday
--passengers, -p Number of adult passengers 2
--children Number of children (ages 2-11) 1
--infants-in-seat Number of infants occupying their own seat 1
--infants-on-lap Number of lap infants (must be ≤ adults) 1
--format Output format text, json
Multi Command (fli multi)
Option Description Example
--leg, -l Flight leg (ORIGIN,DEST,DATE format) SEA,HKG,2026-12-26
--time, -t Departure time window 6-20
--airlines, -a Airline IATA codes DL CX
--class, -c Cabin class ECONOMY, BUSINESS
--stops, -s Maximum stops NON_STOP, ONE_STOP
--sort, -o Sort results by CHEAPEST, DURATION
--passengers, -p Number of adult passengers 2
--children Number of children (ages 2-11) 1
--infants-in-seat Number of infants occupying their own seat 1
--infants-on-lap Number of lap infants (must be ≤ adults) 1

MCP Server Integration

Fli includes a Model Context Protocol (MCP) server that allows AI assistants like Claude to search for flights directly. This enables natural language flight search through conversation.

Running the MCP Server
# Run the MCP server on STDIO
fli-mcp

# Or with uv (for development)
uv run fli-mcp

# Or with make (for development)
make mcp

# Run the MCP server over HTTP (streamable)
fli-mcp-http  # serves at http://127.0.0.1:8000/mcp/
Claude Desktop Configuration

To use the flight search capabilities in Claude Desktop, add this configuration to your claude_desktop_config.json:

Location: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)

{
  "mcpServers": {
    "flight-search": {
      "command": "fli-mcp",
      "args": []
    }
  }
}

After adding this configuration:

  1. Restart Claude Desktop
  2. You can now ask Claude to search for flights naturally:
    • "Find flights from JFK to LAX on December 25th"
    • "What are the cheapest dates to fly from NYC to London in January?"
    • "Search for business class flights from SFO to NRT with no stops"

Python API Usage

Basic Search Example
from datetime import datetime, timedelta
from fli.models import (
    Airport,
    PassengerInfo,
    SeatType,
    MaxStops,
    SortBy,
    FlightSearchFilters,
    FlightSegment
)
from fli.search import SearchFlights

# Create search filters
filters = FlightSearchFilters(
    passenger_info=PassengerInfo(adults=1),
    flight_segments=[
        FlightSegment(
            departure_airport=[[Airport.JFK, 0]],
            arrival_airport=[[Airport.LAX, 0]],
            travel_date=(datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d"),
        )
    ],
    seat_type=SeatType.ECONOMY,
    stops=MaxStops.NON_STOP,
    sort_by=SortBy.CHEAPEST,
)

# Search flights
search = SearchFlights()
flights = search.search(filters)

# Process results
for flight in flights:
    print(f"💰 Price: ${flight.price}")
    print(f"⏱️ Duration: {flight.duration} minutes")
    print(f"✈️ Stops: {flight.stops}")

    for leg in flight.legs:
        print(f"\n🛫 Flight: {leg.airline.value} {leg.flight_number}")
        print(f"📍 From: {leg.departure_airport.value} at {leg.departure_datetime}")
        print(f"📍 To: {leg.arrival_airport.value} at {leg.arrival_datetime}")
Running Examples

Runnable examples live in examples/python/:

# Run examples with uv (recommended)
uv run python examples/python/basic_one_way_search.py
uv run python examples/python/round_trip_search.py
uv run python examples/python/date_range_search.py

# Or install dependencies first, then run directly
pip install pydantic curl_cffi httpx
python examples/python/basic_one_way_search.py

Available Examples:

  • basic_one_way_search.py - Simple one-way flight search
  • round_trip_search.py - Round-trip flight booking
  • date_range_search.py - Find cheapest dates
  • multi_city_search.py - Multi-city itinerary across several legs
  • advanced_filters_search.py - Alliances, airline exclusions, layovers, locale
  • complex_flight_search.py - Advanced filtering and multi-passenger
  • time_restrictions_search.py - Time-based filtering
  • date_search_with_preferences.py - Weekend filtering
  • price_tracking.py - Price monitoring over time
  • error_handling_with_retries.py - Robust error handling
  • result_processing.py - Data analysis with pandas
  • complex_round_trip_validation.py - Advanced round-trip with validation
  • advanced_date_search_validation.py - Complex date search with filtering

Examples

Examples are organized by language, with parallel scripts so you can compare the two APIs:

# Python
uv run python examples/python/complex_flight_search.py

# TypeScript (from examples/typescript, after `bun install`)
bun run multi_city_search.ts

Example Categories:

  • Basic Usage: One-way, round-trip, date searches
  • Advanced Filtering: Time restrictions, airlines, alliances, seat classes, locale
  • Data Analysis: Price tracking, result processing with pandas
  • Error Handling: Retry logic, robust error management
  • Complex Scenarios: Multi-city, multi-passenger, validation, business rules

Each example is self-contained - change the airports, dates, and filters at the top of the script to fit your search.

TypeScript / JavaScript

Fli is also available as a 1:1 TypeScript port, published to npm as fli-js. Same models, same filter encoding, same direct-API approach.

bun add fli-js   # or: npm install fli-js
import { Airport, FlightSearchFilters, FlightSegment, SearchFlights, SeatType } from "fli-js";

const inDays = (n: number) => new Date(Date.now() + n * 86_400_000).toISOString().slice(0, 10);

const filters = new FlightSearchFilters({
  passenger_info: { adults: 1, children: 0, infants_in_seat: 0, infants_on_lap: 0 },
  flight_segments: [
    new FlightSegment({
      departure_airport: [[[Airport.JFK, 0]]],
      arrival_airport: [[[Airport.LAX, 0]]],
      travel_date: inDays(30),
    }),
  ],
  seat_type: SeatType.ECONOMY,
});

const results = await new SearchFlights().search(filters, { currency: "USD" });

fli-js uses the same search-page transport as the Python package (see Search transport above), with the same consequences: emissions / bags / exclude_basic_economy are dropped with a warning, multi-city throws SearchUnsupportedError, getBookingOptions throws SearchRejectedError, date searches cost one page fetch per date and are capped at 93, and FLI_SOCS_COOKIE controls the consent cookie.

The TypeScript source lives in fli-js/; see the TypeScript Quick Start for the full guide.

Development

# Clone the repository
git clone https://github.com/punitarani/fli.git
cd fli

# Install dependencies with uv
uv sync --all-extras

# Run tests (offline only — no network access required)
uv run pytest

# Run linting
uv run ruff check .
uv run ruff format .

# Build documentation
uv run mkdocs serve

# Or use the Makefile for common tasks
make install-all  # Install all dependencies
make test         # Run tests (offline only)
make test-live    # Run the small, stable live tests (real network)
make lint         # Check code style
make format       # Format code

Tests that call the real Google Flights API are marked live and skipped by default (--fuzz, --live and --all are independent, skip-only gates - --all does not imply --live, and --live alone does not imply --fuzz). Run the small, stable live set with make test-live (or pytest -m live --live); the noisier 100-case fuzz-gated live test is a separate opt-in, make test-live-fuzz (pytest --all -m live --live tests/search/test_search_flights_fuzz.py - it needs --all/--fuzz together with --live, since it carries both markers).

Docker Development
# Build the devcontainer
docker build -t fli-dev -f .devcontainer/Dockerfile .

# Run CI inside the container
docker run --rm fli-dev make lint test-all

# Or run lint and tests separately
docker run --rm fli-dev make lint
docker run --rm fli-dev make test-all
Running CI Locally with act

To run GitHub Actions locally, install act:

brew install act

# Run CI locally (lint + tests on Python 3.10-3.13)
make ci

# Or run CI inside Docker (no local act installation needed)
make ci-docker

examples/python/basic_one_way_search.py

#!/usr/bin/env python3
"""Basic one-way flight search example.

This example demonstrates how to search for one-way flights between two airports
on a specific date using the most basic configuration.
"""

from datetime import datetime, timedelta

from fli.models import (
    Airport,
    FlightSearchFilters,
    FlightSegment,
    MaxStops,
    PassengerInfo,
    SeatType,
    SortBy,
)
from fli.search import SearchFlights


def main():
    # Create search filters
    filters = FlightSearchFilters(
        passenger_info=PassengerInfo(adults=1),
        flight_segments=[
            FlightSegment(
                departure_airport=[[Airport.JFK, 0]],
                arrival_airport=[[Airport.LAX, 0]],
                travel_date=(datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d"),
            )
        ],
        seat_type=SeatType.ECONOMY,
        stops=MaxStops.NON_STOP,
        sort_by=SortBy.CHEAPEST,
    )

    # Search flights
    search = SearchFlights()
    flights = search.search(filters)

    # Process results
    for flight in flights:
        print(f"💰 Price: ${flight.price}")
        print(f"⏱️ Duration: {flight.duration} minutes")
        print(f"✈️ Stops: {flight.stops}")

        for leg in flight.legs:
            print(f"\n🛫 Flight: {leg.airline.value} {leg.flight_number}")
            print(f"📍 From: {leg.departure_airport.value} at {leg.departure_datetime}")
            print(f"📍 To: {leg.arrival_airport.value} at {leg.arrival_datetime}")


if __name__ == "__main__":
    main()

examples/python/round_trip_search.py

#!/usr/bin/env python3
"""Round trip flight search example.

This example demonstrates how to search for round trip flights with
outbound and return segments.
"""

from fli.models import (
    Airport,
    FlightSearchFilters,
    FlightSegment,
    PassengerInfo,
    TripType,
)
from fli.search import SearchFlights


def main():
    # Create flight segments for round trip
    from datetime import datetime, timedelta

    outbound_date = (datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d")
    return_date = (datetime.now() + timedelta(days=37)).strftime("%Y-%m-%d")

    flight_segments = [
        FlightSegment(
            departure_airport=[[Airport.JFK, 0]],
            arrival_airport=[[Airport.LAX, 0]],
            travel_date=outbound_date,
        ),
        FlightSegment(
            departure_airport=[[Airport.LAX, 0]],
            arrival_airport=[[Airport.JFK, 0]],
            travel_date=return_date,
        ),
    ]

    # Create filters
    filters = FlightSearchFilters(
        trip_type=TripType.ROUND_TRIP,
        passenger_info=PassengerInfo(adults=1),
        flight_segments=flight_segments,
    )

    # Search flights
    search = SearchFlights()
    results = search.search(filters)

    # Process results
    for outbound, return_flight in results:
        print("\nOutbound Flight:")
        for leg in outbound.legs:
            print(f"Flight: {leg.airline.value} {leg.flight_number}")
            print(f"Departure: {leg.departure_datetime}")
            print(f"Arrival: {leg.arrival_datetime}")

        print("\nReturn Flight:")
        for leg in return_flight.legs:
            print(f"Flight: {leg.airline.value} {leg.flight_number}")
            print(f"Departure: {leg.departure_datetime}")
            print(f"Arrival: {leg.arrival_datetime}")

        print(f"\nTotal Price: ${outbound.price}")


if __name__ == "__main__":
    main()

examples/python/date_range_search.py

#!/usr/bin/env python3
"""Date range search example.

This example demonstrates how to search for the cheapest flights
across a range of dates.
"""

from fli.models import Airport, DateSearchFilters, FlightSegment, PassengerInfo
from fli.search import SearchDates


def main():
    from datetime import datetime, timedelta

    # Create future dates
    base_date = datetime.now() + timedelta(days=30)
    travel_date = base_date.strftime("%Y-%m-%d")
    from_date = base_date.strftime("%Y-%m-%d")
    to_date = (base_date + timedelta(days=30)).strftime("%Y-%m-%d")

    # Create filters
    filters = DateSearchFilters(
        passenger_info=PassengerInfo(adults=1),
        flight_segments=[
            FlightSegment(
                departure_airport=[[Airport.JFK, 0]],
                arrival_airport=[[Airport.LAX, 0]],
                travel_date=travel_date,
            )
        ],
        from_date=from_date,
        to_date=to_date,
    )

    # Search dates
    search = SearchDates()
    results = search.search(filters)

    # Process results
    for date_price in results:
        print(f"Date: {date_price.date}, Price: ${date_price.price}")


if __name__ == "__main__":
    main()

examples/python/complex_flight_search.py

#!/usr/bin/env python3
"""Complex flight search with multiple filters.

This example demonstrates how to search for flights with detailed filters
including airlines, duration limits, and layover restrictions.
"""

from fli.models import (
    Airline,
    Airport,
    FlightSearchFilters,
    FlightSegment,
    LayoverRestrictions,
    MaxStops,
    PassengerInfo,
    SeatType,
    TripType,
)
from fli.search import SearchFlights


def main():
    from datetime import datetime, timedelta

    # Create detailed filters
    filters = FlightSearchFilters(
        trip_type=TripType.ONE_WAY,
        passenger_info=PassengerInfo(adults=2, children=1, infants_on_lap=1),
        flight_segments=[
            FlightSegment(
                departure_airport=[[Airport.JFK, 0]],
                arrival_airport=[[Airport.LHR, 0]],
                travel_date=(datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d"),
            )
        ],
        seat_type=SeatType.BUSINESS,
        stops=MaxStops.ONE_STOP_OR_FEWER,
        airlines=[Airline.BA, Airline.VS],  # British Airways and Virgin Atlantic
        max_duration=720,  # 12 hours in minutes
        layover_restrictions=LayoverRestrictions(
            airports=[Airport.BOS, Airport.ORD],  # Prefer these layover airports
            max_duration=180,  # Maximum 3-hour layover
        ),
    )

    search = SearchFlights()
    results = search.search(filters)

    print(f"Found {len(results)} flights:")
    for i, flight in enumerate(results, 1):
        print(f"\n--- Flight {i} ---")
        print(f"Price: ${flight.price}")
        print(f"Duration: {flight.duration} minutes")
        print(f"Stops: {flight.stops}")

        for j, leg in enumerate(flight.legs, 1):
            print(f"\nLeg {j}: {leg.airline.value} {leg.flight_number}")
            print(f"  From: {leg.departure_airport.value} at {leg.departure_datetime}")
            print(f"  To: {leg.arrival_airport.value} at {leg.arrival_datetime}")
            print(f"  Duration: {leg.duration} minutes")


if __name__ == "__main__":
    main()

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.