DEV Community

Nikita Iakovlev
Nikita Iakovlev

Posted on

The Same Seat, Three Prices: Reading Google Flights Booking Options in Python

Here is a Jetstar flight from Bali to Singapore on 19 November, as Google Flights sees it:

Jetstar — 50 USD
       50 USD  (870000 IDR)         Jetstar [airline]
      119 USD  (2085182 IDR)        Qantas [airline]
      128 USD  (2251997 IDR)        Jettzy [agency]
Enter fullscreen mode Exit fullscreen mode

Same aircraft, same seat, same day. Fifty dollars if you buy from Jetstar, a hundred and
twenty-eight if you buy from an agency that resells it. Google knows this and shows it
behind a click most people never make.

If you are building anything that compares fares — a price alert, a travel dashboard, an
agent that answers "when should I fly" — that seller table is the interesting part, and
it is the part most flight scrapers drop.

Why there is no easy API for this

Google shut down QPX Express in April 2018 and never replaced it. There is no public
Google Flights API. What exists is the consumer interface, which is a heavily obfuscated
single-page app, and the endpoint behind it, which returns a nested array of arrays with
no field names at all.

You can reverse that yourself — people do — or you can call something that already has.
I maintain a Google Flights Scraper
Actor on Apify that reads Google's own endpoint and returns flat rows. The three scripts
below use it, and everything printed here is a real run, not an illustration.

pip install apify-client
export APIFY_TOKEN=...   # console.apify.com/settings/integrations
Enter fullscreen mode Exit fullscreen mode

Reading the seller table

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run = client.actor("lergassy/google-flights-scraper").call(
    run_input={
        "origin": "DPS",
        "destination": "SIN",
        "departureDate": "2026-11-19",
        "maxResults": 5,
        "resolveBookingOptions": True,   # the part that costs an extra request per flight
        "maxBookingResolutions": 3,
        "currency": "USD",
    }
)

rows = client.dataset(run.default_dataset_id).list_items().items

for flight in (r for r in rows if r.get("type") == "flight" and r.get("bookingOptions")):
    print(f"\n{flight['airline']}{flight['price']} {flight['currency']}")
    for option in flight["bookingOptions"]:
        tag = "airline" if option["isAirline"] else "agency"
        print(f"   {option['price']:>6} USD  {option['seller']} [{tag}]")
Enter fullscreen mode Exit fullscreen mode

One detail worth knowing: apify-client 3.x returns a Run object, not a dictionary.
Most tutorials online still show run["defaultDatasetId"], which now raises
TypeError: 'Run' object is not subscriptable. It is run.default_dataset_id.

Each booking option also carries localPrice and localCurrency — what the seller
charges in the currency of the point of sale — plus a direct booking URL. The local price
is not a conversion of the USD figure; it is the number that seller actually bills, and
the two drift apart.

The other two questions worth asking

Which day is cheapest? Google Flights has a price calendar in its UI. Ask for it as
rows and you can diff today's answer against yesterday's, which is what a fare alert
actually needs:

run = client.actor("lergassy/google-flights-scraper").call(
    run_input={
        "origin": "DPS", "destination": "SIN",
        "departureDate": "2026-11-10",
        "calendarDays": 45, "calendarOnly": True, "currency": "USD",
    }
)
rows = client.dataset(run.default_dataset_id).list_items().items
days = sorted((r for r in rows if r["type"] == "calendar_day" and r.get("price")),
              key=lambda r: r["price"])
print(days[0]["date"], days[0]["price"])   # 2026-11-19 50
Enter fullscreen mode Exit fullscreen mode

Where can I go cheaply from here? The Explore map, as a sortable table:

     63 USD  Komodo                 Indonesia        Wings Abadi Airlines nonstop    1.5h
     91 USD  Yogyakarta             Indonesia        Lion                 nonstop    1.5h
    107 USD  Perth                  Australia        Jetstar              nonstop    3.6h
    190 USD  Brisbane               Australia        Batik Air            nonstop    5.8h
Enter fullscreen mode Exit fullscreen mode

Set exploreAnywhere: True with an origin and a date, and each destination comes back
with city, country, coordinates, price, airline, stops and flight time.

Two things that will bite you

Point of sale changes the fare. A route priced from the US and the same route priced
from Indonesia are genuinely different numbers, not a currency conversion. If you are
comparing prices over time, pin market to a two-letter country code, or you will record
noise as signal.

A past date returns nothing, silently. Google sells future dates only. Any scheduled
job with a hardcoded departure date will quietly start returning empty results the day
that date passes — worth a guard in your own code regardless of which tool you use.

The scripts

All three, runnable, are on GitHub:
lergassy/google-flights-api-examples.

The Actor itself, with the full field list — layovers, aircraft, CO₂ per itinerary,
Google's own price level and the historical band behind it, multi-city, batch routes —
is at apify.com/lergassy/google-flights-scraper.

If you end up building the fare alert, the seller table is where the money is. A hundred
and twenty-eight dollars for a fifty dollar seat is not an edge case; it is Tuesday.

Top comments (0)