Google Flights Scraper & Price Tracker
Pricing
from $0.25 / 1,000 flights
Google Flights Scraper & Price Tracker
Google Flights for any route and dates: every itinerary incl. the ones Google hides, with price, airlines, flight numbers, stops, CO2, aircraft and codeshares; Google's price level and price history; what changed since your last run; the cheapest departure days.
Pricing
from $0.25 / 1,000 flights
Rating
0.0
(0)
Developer
Berkan Kaplan
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Get every flight Google Flights has for a route and dates — including the ones Google hides behind "View more flights" — with price, airlines, flight numbers, times, stops and layovers, CO₂, aircraft, legroom, codeshares and "operated by", plus Google's own price level, typical price range and price history (61–62 days for most searches). Run it on a schedule in monitor mode and get only what changed since the last run: new, cheaper, pricier and gone flights. Or scan a range of days for the cheapest departure day.
No Google account, no API key, no browser. One request to Google per search.
Quick start (API)
curl -X POST "https://api.apify.com/v2/acts/foxlabs~google-flights-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"routes": ["IST-LHR"], "departureDate": "+30", "maxFlightsPerSearch": 20}'
Three modes
| Mode | What you get | Typical use |
|---|---|---|
search (default) | Every flight for each route and dates (one row per itinerary), plus one route row per search with the lowest price, Google's price level, typical range and price history | Fare research, competitor schedules, emissions reporting |
monitor | Only the flights that changed since the previous run of the same search: NEW, CHEAPER, PRICIER, GONE (and PRICE_ADDED / PRICE_REMOVED). CHEAPER and PRICIER rows carry the previous price and the change, GONE and PRICE_REMOVED rows the last price seen. Plus the route row with the change of the lowest price | Daily price tracking and alerts; schedule it |
cheapest-days | One row per departure day with the lowest fare, the cheapest flight, Google's price level, and a flag on the cheapest day | "Cheapest day to fly" content, flexible-date planning |
What you get
Every row has a type: flight, route, day or status.
| Group | Fields (flight rows) |
|---|---|
| Price | price (for all adults together; round trips: Google's "from" price, the lowest round-trip total with this outbound flight), priceFormatted, priceUnavailable (true: Google lists the flight but shows no price; empty on GONE rows, which have no current price) |
| Flights | airlines, airlinesText, flightNumbers, flightNumbersText, itineraryKey (stable ID: every flight number with its departure time), operatedBy, codeshares, aircraft, aircraftText, minLegroomInches |
| Times | departureAirport, departureAirportName, departureTime, arrivalAirport, arrivalAirportName, arrivalTime (local airport times), arrivalDayOffset, durationMinutes, durationText |
| Stops | stops, layovers[] (airport, city, minutes, changeOfAirport), layoversText, changeOfAirport |
| Emissions | emissionsKg, typicalEmissionsKg (Google's typical figure for the route), emissionsVsTypicalPercent |
| Google's ranking | isBest (Google's "best flights"), rank (order in Google's list) |
| Segments | segments[]: flight number, airline, operated by, codeshares, airports, times, duration, aircraft, legroom, CO₂ |
| Monitor | changeType, previousPrice, priceChange, priceChangePercent |
| Link | googleFlightsUrl: one-way → Google's booking page for exactly these flights (it lists who sells the ticket); round trip → the search with this outbound selected (it lists the matching returns) |
| Group | Fields (route rows, one per search) |
|---|---|
| Price | lowestPrice, lowestPriceFormatted, cheapestFlight |
| Google's price insight | priceLevel (low / typical / high), priceLevelCode, priceVsTypicalRange (below / within / above), typicalPrice, typicalPriceLow, typicalPriceHigh, belowTypicalBy, priceHistory[] (date and lowest price; 61–62 days when Google has a history for the search), priceHistoryDays |
| Coverage | flightsFound, hiddenFlightsCount (how many Google hides behind "View more flights"), hiddenFlightsIncluded, flightsDelivered, originCity, destinationCity |
| Monitor | monitorStatus (first-run / compared), previousCheckAt, previousLowestPrice, lowestPriceChange, previousPriceLevel, newCount, cheaperCount, pricierCount, goneCount, unchangedCount |
Day rows (cheapest-days mode) carry departureDate, returnDate, lowestPrice, cheapestFlight, isCheapestDay, Google's price level and typical range (no price history) and flightsFound. Every row also has route, origin, destination, departureDate, returnDate, tripType, cabinClass, adults, currency, market, mode, scrapedAt and error.
About priceLevel. Google sends a price-level code with most searches, and its pages put a word on each code. We read those words on Google's own pages (2026-09-30): code 1 "low" (a booking page), codes 2, 3 and 4 "typical", code 5 "high" (search pages). priceLevel carries that word and priceLevelCode the code. In our platform runs (81 route and day rows) the code followed where the lowest price sat in Google's typical range: 2 in its lower third, 3 in the middle, 4 in the upper third, 5 above the range (24 of 24). priceVsTypicalRange compares the lowest price with Google's typical range (our comparison of Google's numbers). A search with airline and stop filters came back without a price insight; its fields are then empty.
Sample output
A real flight row (trimmed) from a run on 2026-09-30 (UTC): FRA-BKK, 10 November 2026, economy, 1 adult, USD.
{"type": "flight","route": "FRA-BKK","departureDate": "2026-11-10","price": 626,"priceFormatted": "$626","airlinesText": "Lufthansa, Oman Air","flightNumbers": ["LH 998", "WY 172", "WY 815"],"itineraryKey": "LH998@2026-11-10T16:40/WY172@2026-11-10T20:00/WY815@2026-11-11T08:30","departureTime": "2026-11-10T16:40","arrivalTime": "2026-11-11T17:15","arrivalDayOffset": 1,"durationText": "18 hr 35 min","stops": 2,"layoversText": "AMS 2h 5m; MCT 2h 40m","operatedBy": ["Air Dolomiti"],"codeshares": ["WY 5364"],"aircraftText": "Embraer 190 E2, Boeing 787","minLegroomInches": 30,"emissionsKg": 698,"typicalEmissionsKg": 616,"emissionsVsTypicalPercent": 13,"isBest": false,"googleFlightsUrl": "https://www.google.com/travel/flights/booking?tfs=CBwQAhqBARIKMjAyNi0xMS0xMCIfCgNGUkES…"}
The route row of the same search (trimmed):
{"type": "route","route": "FRA-BKK","departureDate": "2026-11-10","lowestPrice": 489,"cheapestFlight": { "price": 489, "airlinesText": "Etihad", "flightNumbersText": "EY 124, EY 406", "stops": 1, "durationMinutes": 865 },"priceLevel": "typical","priceLevelCode": 3,"priceVsTypicalRange": "within","typicalPrice": 429,"typicalPriceLow": 395,"typicalPriceHigh": 560,"priceHistory": [{ "date": "2026-09-28", "price": 398 }, { "date": "2026-09-29", "price": 373 }, { "date": "2026-09-30", "price": 489 }],"priceHistoryDays": 61,"flightsFound": 177,"hiddenFlightsCount": 165,"originCity": "Frankfurt am Main","destinationCity": "Bangkok"}
Input & filters
| Input | What it does | Default |
|---|---|---|
mode | search, monitor or cheapest-days | search |
routes | One route per line as two 3-letter airport codes: IST-LHR. A line can carry its own dates: JFK-LAX 2026-11-05 or JFK-LAX 2026-11-05 2026-11-12 | — |
departureDate | YYYY-MM-DD, or days from today such as +30 (UTC). Monitor mode needs YYYY-MM-DD. In cheapest-days mode, the first day scanned | +30 |
returnDate | Empty = one-way. YYYY-MM-DD, or +N for N days after the departure. In cheapest-days mode +N is the trip length | one-way |
scanDays | Cheapest-days mode: how many departure days to scan (one search per day) | 14 |
cabinClass | economy, premium-economy, business, first | economy |
adults | 1–9. Prices are for all adults together | 1 |
maxStops | any, nonstop, 1, 2 (Google's stop filter) | any |
airlines | 2-character airline codes such as TK, LH | all |
currency, country | Currency of the prices; the searcher's market | USD, us |
maxFlightsPerSearch | Flight rows per search, up to 300 (in monitor mode: change rows; see the FAQ) | 200 (the form starts at 20) |
includeHiddenFlights | Also the flights Google hides behind "View more flights" | on |
monitorName | Monitor mode: the result of each search is kept in the key-value store google-flights-monitor-<name> of your account | default |
minPriceChangePercent | Monitor mode: a price counts as changed from this percentage on, measured against the last reported price | 1 |
includeUnchanged | Monitor mode: also list unchanged flights (UNCHANGED) | off |
proxyConfiguration, autoProxyFallback | Proxy is not needed; the Actor switches to Apify residential proxy by itself if Google rate-limits it | off / on |
Input the form does not allow is rejected by Apify before the run starts (for example adults: 10). These stop the run at once, with the reason in its status message and before any request to Google: a route line that is not two 3-letter codes, a departure date in the past, a departure or return date more than 365 days ahead, a relative departure date (+30) in monitor mode, and a return date instead of +N in cheapest-days mode. A 3-letter code that is not an airport, such as the city code LON, is sent to Google; Google refuses that search, and a free row says so (see Troubleshooting). Days of a cheapest-days scan whose departure or return falls more than 365 days ahead are not searched; the log and the SOURCE_REPORT record say how many.
Each search once per run. Two lines that describe the same search (route, dates and filters) are searched and charged once; the second leaves a free row with error: "skipped: …". In cheapest-days mode, a day that two lines share is searched and charged once; its row appears under the first line, and the second line still counts it when it names its cheapest day in the log.
Example inputs (copy & paste)
Every flight, including the hidden ones, for one route 40 days ahead:
{ "routes": ["FRA-BKK"], "departureDate": "+40", "maxFlightsPerSearch": 300 }
Daily price tracker for two searches (schedule it once a day; put your own travel dates in the lines):
{ "mode": "monitor", "routes": ["IST-LHR 2027-03-10", "JFK-LAX 2027-03-12 2027-03-19"], "monitorName": "my-routes", "minPriceChangePercent": 2 }
Cheapest day to fly in a two-week window starting 20 days from today, one-week round trips:
{ "mode": "cheapest-days", "routes": ["IST-LHR"], "departureDate": "+20", "scanDays": 14, "returnDate": "+7" }
Business class, two adults, euros, German market, round trip a week later:
{ "routes": ["CDG-DXB"], "departureDate": "+45", "returnDate": "+7", "cabinClass": "business", "adults": 2, "currency": "EUR", "country": "de" }
Nonstop flights sold under two airlines' codes (Google's filter also returns their codeshare flights flown by partners):
{ "routes": ["IST-LHR"], "departureDate": "+30", "maxStops": "nonstop", "airlines": ["TK", "BA"] }
Use cases
- Fare monitoring for travel agencies and tour operators: schedule monitor mode on the routes you sell and act on
CHEAPERrows and on the route row'slowestPriceChangeand price level. - Deal newsletters and travel content: cheapest-days mode for "cheapest day to fly" pages;
priceVsTypicalRangeand the typical range for "is this a good price" copy. - Corporate travel and sustainability reporting: CO₂ per itinerary next to Google's typical figure for the route, with the operating airline and aircraft.
- Airline and route analysis: the whole list, including the connections Google hides, with codeshares, operating carriers, aircraft and legroom.
- Price-history research: about two months (61–62 days) of Google's price history for most searches, from the very first run.
Performance & throughput
Measured on the Apify platform on 2026-09-30 (UTC) with build 0.1.1 at the default 512 MB and the default proxy setting:
| Run | Input | Rows | Searches (requests to Google) | Time |
|---|---|---|---|---|
iTZh7Q2BTvdDvtrtX | The quick-start command above (IST-LHR, +30, 20 flights) | 20 flights + 1 route row (92 flights found, 80 of them hidden by Google) | 1 | 4 s |
v7MUEk4uaS3eoH4je | FRA-BKK, +40, all flights | 169 flights + 1 route row | 1 | 10 s |
N77cWevQcfksNrWW9 | 10 routes (2 round trips), +35, all flights | 1,157 flights + 10 route rows | 10 | 24 s |
q5zJv3jUx3YztU6cZ | Monitor, 3 searches, first run | 314 NEW + 3 route rows | 3 | 6 s |
QbGegxv24Ff8YfFjC | The same monitor 17 seconds later | 0 changes; 3 free route rows | 3 | 2 s |
N1t34QJS5odsq9QKw | Cheapest days, IST-LHR, 30 days | 30 day rows, all priced | 30 | 8 s |
One search is one request to Google. In our 49 platform runs of 2026-09-30 that searched Google with the default setting (135 requests), none was rate-limited or needed the residential proxy. Forced through Apify residential proxy, the FRA-BKK search took 32 s instead of 10 s (UbiFuqPLtEN71NUSc).
Integrations
Use the dataset from the API, schedule the Actor in Apify Console, or connect it to Make, Zapier, n8n, Google Sheets or a webhook. For a price tracker, schedule monitor mode and send the CHEAPER rows to Slack or email.
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('foxlabs/google-flights-scraper').call({ routes: ['IST-LHR'], departureDate: '+30', maxFlightsPerSearch: 20 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("foxlabs/google-flights-scraper").call(run_input={"routes": ["IST-LHR"], "departureDate": "+30", "maxFlightsPerSearch": 20})items = client.dataset(run["defaultDatasetId"]).list_items().items
Data quality
Measured on the platform runs of 2026-09-30 (build 0.1.1): 2,834 flight rows, 35 route rows and 47 day rows from 23 runs, including all runs in the table above.
- Filled on every flight row: airlines, flight numbers,
itineraryKey, departure and arrival times, duration, stops, the Google Flights link (2,834 of 2,834); layovers on all 2,499 flights with stops. - Price: 2,496 of 2,834 flight rows (88%). The others are listed by Google without a price, mostly among the hidden flights; they have
priceUnavailable: trueandprice: null, never 0. - CO₂ and aircraft: 2,832 of 2,834; Google gave neither for two flights (a London–New York connection via Barcelona on Vueling and LEVEL, and a Scoot flight Singapore–Sydney).
- Legroom: 2,741 of 2,762 economy rows. In business class Google gives a seat pitch for fewer flights (29 of 73 in a local CDG-DXB test, not a platform run).
- Uniqueness: no flight appeared twice in any run (for example 1,157 of 1,157 unique
itineraryKeyinN77cWevQcfksNrWW9). - Against Google's own page: we opened 10
googleFlightsUrllinks fromN77cWevQcfksNrWW9. The 5 one-way booking pages and 2 round-trip pages contained every flight number and the date of their row. On 3 search pages, all 41 flights the page lists matched our rows in flight numbers, times, duration, stops, CO₂ and price, and the price level, typical range and last history point matched too. - Whole list: in a recon of 5 routes (434 flights), the number of flights equalled Google's shown list plus its own hidden-flight counter on every route.
- Price history: 61 or 62 days for 31 of 35 searches. Google sent none for a Turkish domestic route (SAW-AYT, two runs), a business-class round trip (CDG-DXB) and a search with airline and nonstop filters (which had no price level either).
- Round trips and passengers: the price of a round-trip row is what Google's own page shows for that outbound flight, "From $407 round trip total": the lowest total with a matching return. On a JFK-LAX round-trip page (2026-09-30), 33 of 33 priced flights carried that label with the same amount, airline and departure time. Prices are for all adults together: in a local test (CDG-DXB round trip, business; 73 flights in both runs, 65 of them priced in both) the 2-adult price was twice the 1-adult price within €1 for 48 flights and higher for 17 (up to 2.9 times), median ratio 2.0.
- Prices move often, mostly by small amounts: in a recon, 70 of 177 FRA-BKK flights were $1 cheaper 22 minutes later (0.04–0.2 %). On the platform, 43 of 314 monitored flights changed price within 31 minutes (
CvxX1oSaU1L81c0wM, threshold 0): 38 by less than 1 %, 5 by 1 % or more (up to −14.7 %). At the default 1 % threshold, monitor mode would have reported only those 5; smaller moves add up until they pass it. SetminPriceChangePercentto 0 to see every move.
Pricing
Pay per event:
flight: every flight row delivered. In monitor mode that is every change row (and every unchanged row when you turnincludeUnchangedon).route-check: once per search that delivered at least one flight row. It is charged with that search's route row.date-price: every cheapest-days row that has a price.
Free rows: status rows for searches that were empty, failed or duplicated; the route row of a search that delivered no flight row; day rows without a price.
A monitor run in which nothing changed charges none of these events (unless you turn includeUnchanged on). Each search still leaves its free route row with the lowest price, the price level and the history. Platform run pair: q5zJv3jUx3YztU6cZ (first run, 314 NEW rows, 3 charged searches), then QbGegxv24Ff8YfFjC 17 seconds later (0 changes, 0 flight rows, 0 charged searches, 3 free route rows).
If the Pricing tab lists an Actor start event, it is charged once per run. Current prices are on the Pricing tab.
FAQ
What are the "hidden" flights? Google Flights shows a short list and hides the rest behind "View more flights": dearer or slower combinations. The Actor asks for the whole list in the same single request. The route row says how many Google hides (hiddenFlightsCount) and whether they are included (hiddenFlightsIncluded).
How does monitor mode decide what changed? Each search's flights are stored with their itineraryKey in the key-value store google-flights-monitor-<monitorName>. The next run compares prices with the price last reported for each flight, so small moves below minPriceChangePercent add up until they pass it. The first run of a search is the baseline: every flight is stored, and up to maxFlightsPerSearch of them are delivered as NEW rows (set it to 300 to get the whole list once). Later runs report only changes; changes that do not fit under maxFlightsPerSearch come in the following runs. A flight that is no longer listed comes once as GONE.
Why does monitor mode need a fixed date? It compares the same flights from run to run. With +30, every daily run would search a different day and list everything as new.
Are prices per person? No: for all adults together, as Google Flights shows them. A round-trip price is Google's "From … round trip total" for that outbound flight, the lowest total with a matching return. The flight rows describe the outbound flights, and googleFlightsUrl opens the matching returns with their totals.
Why is priceLevel empty for some searches? Google sent no price insight for that search; we saw this with airline and stop filters. Then priceLevelCode and the typical range are empty too. See "About priceLevel" above.
Can I get the list of sites that sell the ticket? Not in the data: Google protects that request. The googleFlightsUrl of a one-way flight opens Google's booking page for exactly those flights, which lists the sellers.
Multi-city trips? Not supported: one-way and round trips only.
Do I need a proxy? No. With the default setting the Actor asks Google directly and switches to Apify residential proxy by itself if Google rate-limits it. None of our 49 default-setting test runs that searched Google (135 requests, 2026-09-30) needed it.
Why fewer flights than on Google? maxFlightsPerSearch, the stop or airline filters, or includeHiddenFlights off. Google's list also changes within minutes.
Why do other airlines appear when I filter by airline? Google's airline filter includes codeshare flights sold under those airline codes. In our 2026-10-01 test, a BA, VS filter returned American Airlines AA 103 (sold as BA 1516) and Delta DL 2 (sold as VS 4007). On each row, airlines shows the airline that flies the plane and codeshares lists the numbers it is also sold under, so you can keep only the flights a given airline flies itself.
Troubleshooting
errorstarts withempty:— Google shows no flights for that route, date and filters. Try other dates or fewer filters, and check the airport codes. Nothing was charged.errorstarts withfailed:and says Google Flights did not accept the search — check that both are airport codes (LHR, not the city codeLON). Nothing was charged.errorstarts withfailed:otherwise — Google could not be read for that search; the reason follows. Nothing was charged.errorstarts withskipped:— the same search was already in the input.- A monitor run delivered nothing — nothing changed since the last run; each search still left its free route row.
Notes, limits & legal
- The Actor reads the public Google Flights results any visitor sees. Prices come from airlines and travel sites through Google and change often; keep
scrapedAtwith the data. - Times are local airport times without a time zone.
- Up to 300 flights per search, 60 days per cheapest-days scan, dates up to 365 days ahead.
- "Google" and "Google Flights" are trademarks of Google LLC. This Actor is not affiliated with or endorsed by Google.
Support
Open an issue on the Issues tab with the run ID and the input, or write to info@foxlabs.com.tr.
Changelog
0.1.3 — 2026-10-01
- The run's status message says what was delivered, or why nothing was.
0.1.2 — 2026-10-01
priceLevelfor every code Google sends:low,typical,high.- Monitor mode: the first run stores the whole list as the baseline, so later runs never list an old flight as
NEW. - A city code such as
LONends with one request and a plain message. - Cheapest-days: a day that two lines share is searched and charged once.
See CHANGELOG.md for details.
0.1 — 2026-09-30
First version.