Skip to content

Search API Reference

The main search functionality for finding specific flights.

SearchFlights

fli.search.flights.SearchFlights()

Flight search implementation using Google Flights' API.

This class handles searching for specific flights with detailed filters, parsing the results into structured data models.

Initialize the search client for flight searches.

Source code in fli/search/flights.py
def __init__(self):
    """Initialize the search client for flight searches."""
    self.client = get_client()

BASE_URL = 'https://www.google.com/_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetShoppingResults' class-attribute instance-attribute

DEFAULT_HEADERS = {'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'} class-attribute instance-attribute

client = get_client() instance-attribute

search(filters: FlightSearchFilters, top_n: int = 5) -> list[FlightResult | tuple[FlightResult, FlightResult]] | None

Search for flights using the given FlightSearchFilters.

PARAMETER DESCRIPTION
filters

Full flight search object including airports, dates, and preferences

TYPE: FlightSearchFilters

top_n

Number of flights to limit the return flight search to

TYPE: int DEFAULT: 5

RETURNS DESCRIPTION
list[FlightResult | tuple[FlightResult, FlightResult]] | None

List of FlightResult objects containing flight details, or None if no results

RAISES DESCRIPTION
Exception

If the search fails or returns invalid data

Source code in fli/search/flights.py
def search(
    self, filters: FlightSearchFilters, top_n: int = 5
) -> list[FlightResult | tuple[FlightResult, FlightResult]] | None:
    """Search for flights using the given FlightSearchFilters.

    Args:
        filters: Full flight search object including airports, dates, and preferences
        top_n: Number of flights to limit the return flight search to

    Returns:
        List of FlightResult objects containing flight details, or None if no results

    Raises:
        Exception: If the search fails or returns invalid data

    """
    encoded_filters = filters.encode()

    try:
        response = self.client.post(
            url=self.BASE_URL,
            data=f"f.req={encoded_filters}",
            impersonate="chrome",
            allow_redirects=True,
        )
        response.raise_for_status()

        parsed = json.loads(response.text.lstrip(")]}'"))[0][2]
        if not parsed:
            return None

        encoded_filters = json.loads(parsed)
        flights_data = [
            item
            for i in [2, 3]
            if isinstance(encoded_filters[i], list)
            for item in encoded_filters[i][0]
        ]
        flights = [self._parse_flights_data(flight) for flight in flights_data]

        if (
            filters.trip_type == TripType.ONE_WAY
            or filters.flight_segments[0].selected_flight is not None
        ):
            return flights

        # Get the return flights if round-trip
        flight_pairs = []
        # Call the search again with the return flight data
        for selected_flight in flights[:top_n]:
            selected_flight_filters = deepcopy(filters)
            selected_flight_filters.flight_segments[0].selected_flight = selected_flight
            return_flights = self.search(selected_flight_filters, top_n=top_n)
            if return_flights is not None:
                flight_pairs.extend(
                    (selected_flight, return_flight) for return_flight in return_flights
                )

        return flight_pairs

    except Exception as e:
        raise Exception(f"Search failed: {str(e)}") from e

FlightSearchFilters

A simplified interface for flight search parameters.

fli.models.google_flights.FlightSearchFilters

Bases: BaseModel

Complete set of filters for flight search.

This model matches required Google Flights' API structure.

airlines: list[Airline] | None = None class-attribute instance-attribute

flight_segments: list[FlightSegment] instance-attribute

layover_restrictions: LayoverRestrictions | None = None class-attribute instance-attribute

max_duration: PositiveInt | None = None class-attribute instance-attribute

passenger_info: PassengerInfo instance-attribute

price_limit: PriceLimit | None = None class-attribute instance-attribute

seat_type: SeatType = SeatType.ECONOMY class-attribute instance-attribute

sort_by: SortBy = SortBy.NONE class-attribute instance-attribute

stops: MaxStops = MaxStops.ANY class-attribute instance-attribute

trip_type: TripType = TripType.ONE_WAY class-attribute instance-attribute

encode() -> str

URL encode the formatted filters for API request.

Source code in fli/models/google_flights/flights.py
def encode(self) -> str:
    """URL encode the formatted filters for API request."""
    formatted_filters = self.format()
    # First convert the formatted filters to a JSON string
    formatted_json = json.dumps(formatted_filters, separators=(",", ":"))
    # Then wrap it in a list with null
    wrapped_filters = [None, formatted_json]
    # Finally, encode the whole thing
    return urllib.parse.quote(json.dumps(wrapped_filters, separators=(",", ":")))

format() -> list

Format filters into Google Flights API structure.

This method converts the FlightSearchFilters model into the specific nested list/dict structure required by Google Flights' API.

The output format matches Google Flights' internal API structure, with careful handling of nested arrays and proper serialization of enums and model objects.

RETURNS DESCRIPTION
list

A formatted list structure ready for the Google Flights API request

TYPE: list

Source code in fli/models/google_flights/flights.py
def format(self) -> list:
    """Format filters into Google Flights API structure.

    This method converts the FlightSearchFilters model into the specific nested list/dict
    structure required by Google Flights' API.

    The output format matches Google Flights' internal API structure, with careful handling
    of nested arrays and proper serialization of enums and model objects.

    Returns:
        list: A formatted list structure ready for the Google Flights API request

    """

    def serialize(obj):
        if isinstance(obj, Airport) or isinstance(obj, Airline):
            return obj.name
        if isinstance(obj, Enum):
            return obj.value
        if isinstance(obj, list):
            return [serialize(item) for item in obj]
        if isinstance(obj, dict):
            return {key: serialize(value) for key, value in obj.items()}
        if isinstance(obj, BaseModel):
            return serialize(obj.dict(exclude_none=True))
        return obj

    # Format flight segments
    formatted_segments = []
    for segment in self.flight_segments:
        # Format airport codes with correct nesting
        segment_filters = [
            [
                [
                    [serialize(airport[0]), serialize(airport[1])]
                    for airport in segment.departure_airport
                ]
            ],
            [
                [
                    [serialize(airport[0]), serialize(airport[1])]
                    for airport in segment.arrival_airport
                ]
            ],
        ]

        # Time restrictions
        if segment.time_restrictions:
            time_filters = [
                segment.time_restrictions.earliest_departure,
                segment.time_restrictions.latest_departure,
                segment.time_restrictions.earliest_arrival,
                segment.time_restrictions.latest_arrival,
            ]
        else:
            time_filters = None

        # Airlines
        airlines_filters = None
        if self.airlines:
            sorted_airlines = sorted(self.airlines, key=lambda x: x.value)
            airlines_filters = [serialize(airline) for airline in sorted_airlines]

        # Layover restrictions
        layover_airports = (
            [serialize(a) for a in self.layover_restrictions.airports]
            if self.layover_restrictions and self.layover_restrictions.airports
            else None
        )
        layover_duration = (
            self.layover_restrictions.max_duration if self.layover_restrictions else None
        )

        # Selected flight (to fetch return flights)
        selected_flights = None
        if self.trip_type == TripType.ROUND_TRIP and segment.selected_flight is not None:
            selected_flights = [
                [
                    serialize(leg.departure_airport.name),
                    serialize(leg.departure_datetime.strftime("%Y-%m-%d")),
                    serialize(leg.arrival_airport.name),
                    None,
                    serialize(leg.airline.name),
                    serialize(leg.flight_number),
                ]
                for leg in segment.selected_flight.legs
            ]

        segment_formatted = [
            segment_filters[0],  # departure airport
            segment_filters[1],  # arrival airport
            time_filters,  # time restrictions
            serialize(self.stops.value),  # stops
            airlines_filters,  # airlines
            None,  # placeholder
            segment.travel_date,  # travel date
            [self.max_duration] if self.max_duration else None,  # max duration
            selected_flights,  # selected flight (to fetch return flights)
            layover_airports,  # layover airports
            None,  # placeholder
            None,  # placeholder
            layover_duration,  # layover duration
            None,  # emissions
            3,  # constant value
        ]
        formatted_segments.append(segment_formatted)

    # Create the main filters structure
    filters = [
        [],  # empty array at start
        [
            None,  # placeholder
            None,  # placeholder
            serialize(self.trip_type.value),
            None,  # placeholder
            [],  # empty array
            serialize(self.seat_type.value),
            [
                self.passenger_info.adults,
                self.passenger_info.children,
                self.passenger_info.infants_on_lap,
                self.passenger_info.infants_in_seat,
            ],
            [None, self.price_limit.max_price] if self.price_limit else None,
            None,  # placeholder
            None,  # placeholder
            None,  # placeholder
            None,  # placeholder
            None,  # placeholder
            formatted_segments,
            None,  # placeholder
            None,  # placeholder
            None,  # placeholder
            1,  # placeholder (hardcoded to 1)
        ],
        serialize(self.sort_by.value),
        0,  # constant
        0,  # constant
        2,  # constant
    ]

    return filters

Search functionality for finding the cheapest dates to fly.

SearchDates

fli.search.dates.SearchDates()

Date-based flight search implementation.

This class provides methods to search for flight prices across a date range, useful for finding the cheapest dates to fly.

Initialize the search client for date-based searches.

Source code in fli/search/dates.py
def __init__(self):
    """Initialize the search client for date-based searches."""
    self.client = get_client()

BASE_URL = 'https://www.google.com/_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetCalendarGraph' class-attribute instance-attribute

DEFAULT_HEADERS = {'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'} class-attribute instance-attribute

client = get_client() instance-attribute

__parse_date(item: list[list] | list | None, trip_type: TripType) -> tuple[datetime] | tuple[datetime, datetime] staticmethod

Parse date data from the API response.

PARAMETER DESCRIPTION
item

Raw date data from the API response

TYPE: list[list] | list | None

trip_type

Trip type (one-way or round-trip)

TYPE: TripType

RETURNS DESCRIPTION
tuple[datetime] | tuple[datetime, datetime]

Tuple of datetime objects

Source code in fli/search/dates.py
@staticmethod
def __parse_date(
    item: list[list] | list | None, trip_type: TripType
) -> tuple[datetime] | tuple[datetime, datetime]:
    """Parse date data from the API response.

    Args:
        item: Raw date data from the API response
        trip_type: Trip type (one-way or round-trip)

    Returns:
        Tuple of datetime objects

    """
    if trip_type == TripType.ONE_WAY:
        return (datetime.strptime(item[0], "%Y-%m-%d"),)
    else:
        return (
            datetime.strptime(item[0], "%Y-%m-%d"),
            datetime.strptime(item[1], "%Y-%m-%d"),
        )

__parse_price(item: list[list] | list | None) -> float | None staticmethod

Parse price data from the API response.

PARAMETER DESCRIPTION
item

Raw price data from the API response

TYPE: list[list] | list | None

RETURNS DESCRIPTION
float | None

Float price value if valid, None if invalid or missing

Source code in fli/search/dates.py
@staticmethod
def __parse_price(item: list[list] | list | None) -> float | None:
    """Parse price data from the API response.

    Args:
        item: Raw price data from the API response

    Returns:
        Float price value if valid, None if invalid or missing

    """
    try:
        if item and isinstance(item, list) and len(item) > 2:
            if isinstance(item[2], list) and len(item[2]) > 0:
                if isinstance(item[2][0], list) and len(item[2][0]) > 1:
                    return float(item[2][0][1])
    except (IndexError, TypeError, ValueError):
        pass

    return None

search(filters: DateSearchFilters) -> list[DatePrice] | None

Search for flight prices across a date range and search parameters.

PARAMETER DESCRIPTION
filters

Search parameters including date range, airports, and preferences

TYPE: DateSearchFilters

RETURNS DESCRIPTION
list[DatePrice] | None

List of DatePrice objects containing date and price pairs, or None if no results

RAISES DESCRIPTION
Exception

If the search fails or returns invalid data

Notes
  • For date ranges larger than 61 days, splits into multiple searches.
  • We can't search more than 305 days in the future.
Source code in fli/search/dates.py
def search(self, filters: DateSearchFilters) -> list[DatePrice] | None:
    """Search for flight prices across a date range and search parameters.

    Args:
        filters: Search parameters including date range, airports, and preferences

    Returns:
        List of DatePrice objects containing date and price pairs, or None if no results

    Raises:
        Exception: If the search fails or returns invalid data

    Notes:
        - For date ranges larger than 61 days, splits into multiple searches.
        - We can't search more than 305 days in the future.

    """
    from_date = datetime.strptime(filters.from_date, "%Y-%m-%d")
    to_date = datetime.strptime(filters.to_date, "%Y-%m-%d")
    date_range = (to_date - from_date).days + 1

    if date_range <= self.MAX_DAYS_PER_SEARCH:
        return self._search_chunk(filters)

    # Split into chunks of MAX_DAYS_PER_SEARCH
    all_results = []
    current_from = from_date
    while current_from <= to_date:
        current_to = min(current_from + timedelta(days=self.MAX_DAYS_PER_SEARCH - 1), to_date)

        # Update the travel date for the flight segments
        if current_from > from_date:
            for segment in filters.flight_segments:
                segment.travel_date = (
                    datetime.strptime(segment.travel_date, "%Y-%m-%d")
                    + timedelta(days=self.MAX_DAYS_PER_SEARCH)
                ).strftime("%Y-%m-%d")

        # Create new filters for this chunk
        chunk_filters = DateSearchFilters(
            trip_type=filters.trip_type,
            passenger_info=filters.passenger_info,
            flight_segments=filters.flight_segments,
            stops=filters.stops,
            seat_type=filters.seat_type,
            airlines=filters.airlines,
            from_date=current_from.strftime("%Y-%m-%d"),
            to_date=current_to.strftime("%Y-%m-%d"),
            duration=filters.duration,
        )

        chunk_results = self._search_chunk(chunk_filters)
        if chunk_results:
            all_results.extend(chunk_results)

        current_from = current_to + timedelta(days=1)

    return all_results if all_results else None

DatePrice

fli.search.dates.DatePrice

Bases: BaseModel

Flight price for a specific date.

date: tuple[datetime] | tuple[datetime, datetime] instance-attribute

price: float instance-attribute

Examples

from fli.search import SearchFlights, SearchFlightsFilters
from fli.models import Airport, SeatType

# Create filters
filters = SearchFlightsFilters(
    departure_airport=Airport.JFK,
    arrival_airport=Airport.LAX,
    departure_date="2024-06-01",
    seat_type=SeatType.ECONOMY
)

# Search flights
search = SearchFlights()
results = search.search(filters)
from fli.search import SearchDates
from fli.models import DateSearchFilters, Airport

# Create filters
filters = DateSearchFilters(
    departure_airport=Airport.JFK,
    arrival_airport=Airport.LAX,
    from_date="2024-06-01",
    to_date="2024-06-30"
)

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

HTTP Client

The underlying HTTP client used for API requests.

Client

fli.search.client.Client()

HTTP client with built-in rate limiting, retry and user agent impersonation functionality.

Initialize a new client session with default headers.

Source code in fli/search/client.py
def __init__(self):
    """Initialize a new client session with default headers."""
    self._client = requests.Session()
    self._client.headers.update(self.DEFAULT_HEADERS)

DEFAULT_HEADERS = {'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'} class-attribute instance-attribute

__del__()

Clean up client session on deletion.

Source code in fli/search/client.py
def __del__(self):
    """Clean up client session on deletion."""
    if hasattr(self, "_client"):
        self._client.close()

get(url: str, **kwargs) -> requests.Response

Make a rate-limited GET request with automatic retries.

PARAMETER DESCRIPTION
url

Target URL for the request

TYPE: str

**kwargs

Additional arguments passed to requests.get()

DEFAULT: {}

RETURNS DESCRIPTION
Response

Response object from the server

RAISES DESCRIPTION
Exception

If request fails after all retries

Source code in fli/search/client.py
@sleep_and_retry
@limits(calls=10, period=1)
@retry(stop=stop_after_attempt(3), wait=wait_exponential(), reraise=True)
def get(self, url: str, **kwargs) -> requests.Response:
    """Make a rate-limited GET request with automatic retries.

    Args:
        url: Target URL for the request
        **kwargs: Additional arguments passed to requests.get()

    Returns:
        Response object from the server

    Raises:
        Exception: If request fails after all retries

    """
    try:
        response = self._client.get(url, **kwargs)
        response.raise_for_status()
        return response
    except Exception as e:
        raise Exception(f"GET request failed: {str(e)}") from e

post(url: str, **kwargs) -> requests.Response

Make a rate-limited POST request with automatic retries.

PARAMETER DESCRIPTION
url

Target URL for the request

TYPE: str

**kwargs

Additional arguments passed to requests.post()

DEFAULT: {}

RETURNS DESCRIPTION
Response

Response object from the server

RAISES DESCRIPTION
Exception

If request fails after all retries

Source code in fli/search/client.py
@sleep_and_retry
@limits(calls=10, period=1)
@retry(stop=stop_after_attempt(3), wait=wait_exponential(), reraise=True)
def post(self, url: str, **kwargs) -> requests.Response:
    """Make a rate-limited POST request with automatic retries.

    Args:
        url: Target URL for the request
        **kwargs: Additional arguments passed to requests.post()

    Returns:
        Response object from the server

    Raises:
        Exception: If request fails after all retries

    """
    try:
        response = self._client.post(url, **kwargs)
        response.raise_for_status()
        return response
    except Exception as e:
        raise Exception(f"POST request failed: {str(e)}") from e