Google Flights API & Scraper · Live Fares · $3.00/1k
Pricing
from $3.00 / 1,000 flight lookups
Google Flights API & Scraper · Live Fares · $3.00/1k
Live Google Flights prices as an API: one run returns the fares for a route and date as JSON, with airline, stops, a booking link and Google's low / typical / high verdict. A round trip is one request. By FlightPowers. $3.00 per 1,000 lookups. It does not book.
Pricing
from $3.00 / 1,000 flight lookups
Rating
0.0
(0)
Developer
Matan Rabi
Maintained by CommunityActor stats
1
Bookmarked
267
Total users
25
Monthly active users
a day ago
Last modified
Categories
Security
Limited permissionsShare
✈️ Google Flights API: live flight prices as JSON
Live Google Flights prices as an API: one run takes a route and a date, one-way or round trip, and returns the fares Google Flights shows right now as flat JSON (10 rows by default, limit for more), each with the airline, stops, duration, a booking link and Google's own low / typical / high verdict with its price range. A round trip is one request: both legs priced together, one total, one booking link. $3.00 per 1,000 flight lookups, and Apify's free plan comes with $5 of usage every month.
Run it from the Apify Console, the Apify API or a schedule, or give it to Claude, Cursor or ChatGPT as a tool through Apify's MCP server. It finds and prices flights, it does not book them.
People use it three ways. Scan for deals: put the Actor on an Apify schedule and only look when the verdict says low. Put live search in your app: call it from the Apify API and get one JSON row per flight. Run an AI travel agent: add it as a tool through Apify's MCP server.
Flexible dates? Set departure_date_to and one run searches every departure date up to it, up to 31 dates, a few at a time. Details in the price calendar section below.
Flying from or to a city with more than one airport? Put up to 4 airports on each side, separated with commas (TLV,ATH to LHR,CDG), and one run searches every pair and hands back one ranked list. Details in the section on several airports below.
Every search is scanned against Google Flights at the moment you call it. Nothing is served from a cache, so the price you get back is the price the traveller will see.
🌟 Why this Google Flights API
Google's price verdict, not just a number. Every result carries price_insights_low, price_insights_high and a low / typical / high rating for that route and those dates. That is what lets your product say "$56 is a good price for this route" instead of just "$56". Some other Google Flights actors and APIs return it too, so read the output schema of whatever you are comparing rather than taking anyone's word for it.
Round trip is one request, not two. Not two one-way queries stapled together. You get one object per itinerary with a combined total_price, total_duration_seconds, total_stops, and the outbound and return legs already paired, each with its own airline, duration, stop count and layovers. On SerpApi you get the outbound, then spend a second search with a departure_token to see the return, and HasData does the same with a departureToken (both documented on their own pages, read 2026-09-06). Here both legs are priced together and come back as one total with one booking link.
Per leg filtering. Require a nonstop outbound and accept one connection on the way home. Restrict the return to a specific airline. Set a departure window on one leg and an arrival window on the other. Every filter splits per leg on round-trip searches.
An empty result is not a shrug. Google sometimes serves a consent wall, a bot check or a truncated page instead of results, and most scrapers hand that back as an empty list you cannot tell apart from "there are no flights on this route". A page that could not be read is retried; a page where Google genuinely reports no flights is never retried, so a real empty answer costs no extra time. Every run then says which of the two it was, in its status message and in a SEARCH_OUTCOME record.
Flat JSON, one row per flight. No HTML, no nested scrape output to flatten before you can use it. It drops straight into a dataframe, a database, or an LLM tool call.
A month of dates in one run. Set departure_date_to and the run searches every departure date up to it (up to 31), five at a time, and hands back one dataset with every date's fares. For bigger scans you can still fan out runs: the backend takes up to 150 requests per minute.
No second bill. No SerpApi key, no separate proxy account, no monthly rental. Residential proxy routing is included and on by default.
📅 Google Flights price calendar: flexible dates and a whole month
Set departure_date_to and one run searches every departure date from departure_date up to it, up to 31 dates (a whole month), then chart the cheapest price_as_number (or total_price_as_number on a round trip) by departure_date. Every day's rows carry Google's verdict too, so the calendar shows which days are actually cheap for that route, not just which number is smallest. Save the run as a Task on a schedule and the calendar stays fresh.
- Round trips keep their length. The trip length is the gap between your
departure_dateandreturn_date, and every date keeps it:2027-06-15to2027-06-18withdeparture_date_toset to2027-06-20searches 15-18, 16-19, 17-20, 18-21, 19-22 and 20-23 June. Long stays work the same way. - Every row says which date it is.
departure_dateandreturn_dateare on every row, so one dataset holds the whole calendar. - Charged per date searched. Each date is one flight lookup per airport pair (31 dates of one route = 31 lookups, $0.093 at $3.00 per 1,000). A date whose search does not complete is not charged and is listed in the outcome. Several airports and a date range combine: 4 pairs over 31 dates is 124 lookups.
- The outcome covers every date. The status message and
SEARCH_OUTCOMEsay how many dates were searched, and list any date that did not complete, each with its ownstatus,reasonand row count. The run fails only when no date could be searched; withstricton it also fails when any date did not search (dates already searched stay in the dataset and stay charged). - It stays inside the run. Dates are searched five at a time. If the run gets close to its timeout, or your maximum total charge cannot pay for another date, the remaining dates are not started, not charged, and are listed in
SEARCH_OUTCOMEasnot_started. For very slow searches (families, long stays, several airports) give the run a longer timeout or a shorter range. - More than 31 dates: split the range over several runs.
{"endpoint": "roundtrip","from_airport": "JFK","to_airport": "LHR","departure_date": "2027-02-01","return_date": "2027-02-08","departure_date_to": "2027-03-03","currency": "usd"}
🛫 Several airports in one run
Type more than one airport in from_airport or to_airport, separated with commas, and the run searches every origin and destination pair at once:
- Up to 4 airports on each side and up to 8 airport pairs in total, for example 2 origins x 4 destinations. An airport cannot be on both sides.
- All pairs come back in one list, ranked by
sort_typeand cut tolimit. On these searcheslimitis 1 to 20 (default 10). from_airportandto_airporton each row are the airports that flight actually uses, so you can tellATHtoLHRapart fromTLVtoCDG.- A round trip still prices both legs together: one total and one booking link on every row.
- Round trips take airport codes, not city codes.
LHR,LGWworks;LONis refused, and the error lists London's airports so you can pick. One-way searches accept city codes, but do not mix a city code with one of its own airports (LONwithLHR): the same flights get searched twice. - Google's price range: on a one-way it is there only when all the airports on each side are in one city (for example
GRU,VCPtoHND,NRT); across cities it isnull. On a round trip each pair is searched on its own, so every row carries its own pair's range. - Families work too: a child or an infant in
passengers(codes2,3,4) is searched pair by pair through Google's own search call. Those rows carry no price range, and the run can take up to about a minute. - Round trips longer than 32 nights work too, searched the same way, pair by pair, with no price range on those rows.
use_fallbackmust stay off (the default).
A search that breaks one of these rules fails with a message that names the field, and a failed run is not charged.
How it is charged: one flight lookup per airport pair searched. TLV,ATH to LHR,CDG is 4 pairs, so 4 lookups ($0.012 at $3.00 per 1,000). A normal one-airport search is still one lookup. If you set a maximum total charge on the run, leave room for every pair.
{"endpoint": "oneway","from_airport": "TLV,ATH","to_airport": "LHR,CDG","departure_date": "2027-02-04","limit": 20,"currency": "usd"}
🚀 What you get back
| Field | What it is |
|---|---|
price / price_as_number | Fare as a display string and as a number you can sort on |
airline | Operating carrier |
duration / duration_seconds | Total trip time, formatted and in seconds |
stops / stops_info[] | Stop count plus every layover airport and its duration in seconds |
departure_description / arrival_description | Local departure and arrival times in plain text |
buy_link | Deep link into Google Flights for that exact itinerary |
price_insights_low / price_insights_high | Google's historical price band for this route and these dates |
price_range_in_relation_to_other_periods | Google's low, typical or high verdict on the fare |
Round-trip results add total_price, total_price_as_number, total_duration_seconds, total_stops, and a full set of departure_flight_* and return_flight_* fields for each leg.
📖 Usage examples
From the Apify Console
Pick One-way or Round-trip, fill in the airports and dates, press Start. The form is prefilled with a working search, so you can run it once before typing anything.
Example 1: cheapest one-way, nonstop only
{"endpoint": "oneway","from_airport": "JFK","to_airport": "LAX","departure_date": "2026-12-01","max_stops": 0,"sort_type": "Price","currency": "usd"}
Example 2: round-trip, nonstop out, one stop allowed home
{"endpoint": "roundtrip","from_airport": "JFK","to_airport": "TLV","departure_date": "2026-12-01","return_date": "2026-12-08","max_departure_stops": 0,"max_return_stops": 1,"seat_type": 1,"currency": "usd"}
Example 3: business class, specific airlines, morning departures
{"endpoint": "oneway","from_airport": "LHR","to_airport": "SIN","departure_date": "2026-12-01","airline_codes": ["SQ", "BA"],"departure_time_min": 6,"departure_time_max": 12,"seat_type": 3,"limit": 20}
Example 4: family of four, budget capped
{"endpoint": "roundtrip","from_airport": "BER","to_airport": "CDG","departure_date": "2026-12-01","return_date": "2026-12-05","passengers": [1, 1, 2, 2],"max_price": 400,"currency": "eur"}
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("mtnrabi/google-flights-real-time-api").call(run_input={"endpoint": "roundtrip","from_airport": "JFK","to_airport": "TLV","departure_date": "2026-12-01","return_date": "2026-12-08","currency": "usd",})items = client.dataset(run["defaultDatasetId"]).list_items().itemsfor trip in sorted(items, key=lambda t: t["total_price_as_number"]):print(trip["total_price"], trip["departure_flight_airline"], trip["buy_link"])
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('mtnrabi/google-flights-real-time-api').call({endpoint: 'oneway',from_airport: 'JFK',to_airport: 'LAX',departure_date: '2026-12-01',currency: 'usd',});const { items } = await client.dataset(run.defaultDatasetId).listItems();items.sort((a, b) => a.price_as_number - b.price_as_number);console.log(items[0].price, items[0].airline, items[0].buy_link);
REST
curl -X POST "https://api.apify.com/v2/acts/mtnrabi~google-flights-real-time-api/run-sync-get-dataset-items" \-H "Content-Type: application/json" \-H "Authorization: Bearer $APIFY_TOKEN" \-d '{"endpoint": "oneway","from_airport": "JFK","to_airport": "LAX","departure_date": "2026-12-01","max_stops": 1,"currency": "usd"}'
🔍 Input parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
endpoint | string | ✅ | oneway | oneway or roundtrip |
from_airport | string | ✅ | JFK | Departure airport IATA code. Up to 4, comma-separated (TLV,ATH) |
to_airport | string | ✅ | LAX | Destination airport IATA code. Up to 4, comma-separated (LHR,CDG); at most 8 pairs in total |
departure_date | string | ✅ | Outbound date, YYYY-MM-DD | |
return_date | string | ✅* | Return date, YYYY-MM-DD. Required for roundtrip | |
departure_date_to | string | Optional. Search every departure date from departure_date to this date in one run, up to 31. A round trip keeps its trip length on every date. One lookup per date per airport pair | ||
limit | integer | 10 | How many flights to return. 1 to 20 on a search with several airports | |
currency | string | usd | Currency for all prices | |
seat_type | integer | 1 | 1 Economy, 3 Business | |
passengers | integer[] | [1] | 1 adult, 2 child, 3 infant on lap, 4 infant in seat | |
max_price | integer | Only return fares at or below this price | ||
sort_type | string | Overall | Overall, Price or Duration. Applies to one-way and round-trip alike | |
use_fallback | boolean | false | Leave it off. Off already lets a search that Google's page cannot list (for example a round trip longer than 32 nights) fall back to Google's own search call before it gives up. On also tries that call early, whenever a page comes back empty, which can make a run slower. Refused on multi-airport searches | |
strict | boolean | false | On a single date it changes nothing: a run whose search did not complete always fails now (see the FAQ below). On a date range it fails the run when any date did not search; without it the run succeeds with the dates that did | |
use_ext_proxy | boolean | true | Route through a residential proxy | |
max_stops | integer | One-way only. 0 for nonstop | ||
airline_codes | string[] | One-way only. Include only these carriers | ||
exclude_airline_codes | string[] | One-way only. Exclude these carriers | ||
departure_time_min / _max | integer | One-way only. Departure hour window, 0 to 23 | ||
arrival_time_min / _max | integer | One-way only. Arrival hour window, 0 to 23 | ||
max_departure_stops / max_return_stops | integer | Round-trip only. Max stops per leg | ||
departure_airline_codes / return_airline_codes | string[] | Round-trip only. Carriers per leg | ||
departure_exclude_airline_codes / return_exclude_airline_codes | string[] | Round-trip only. Exclusions per leg | ||
departure_departure_time_min / _max | integer | Round-trip only. Outbound departure window | ||
departure_arrival_time_min / _max | integer | Round-trip only. Outbound arrival window | ||
return_departure_time_min / _max | integer | Round-trip only. Return departure window | ||
return_arrival_time_min / _max | integer | Round-trip only. Return arrival window |
* return_date is required only when endpoint is roundtrip.
📊 Output format
One dataset row per flight. Set limit to control how many rows a search produces.
One-way
{"price_range_in_relation_to_other_periods": "low","price_insights_low": 65,"price_insights_high": 135,"from_airport": "Berlin (BER)","to_airport": "Paris (CDG)","departure_date": "2026-06-15","price": "$56","price_as_number": 56,"duration": "1 hr 50 min","duration_seconds": 6600,"buy_link": "https://www.google.com/travel/flights?tfs=...&curr=usd","airline": "easyJet","stops": 0,"stops_info": [],"departure_description": "10:15 AM on Mon, Jun 15","arrival_description": "12:05 PM on Mon, Jun 15"}
stops_info is empty on nonstop flights. Otherwise it lists each layover:
"stops_info": [{ "stop_airport": "AUH", "stop_duration_seconds": 5700 }]
Round-trip
{"price_range_in_relation_to_other_periods": "low","price_insights_low": 135,"price_insights_high": 205,"from_airport": "Berlin (BER)","to_airport": "Paris (CDG)","departure_date": "2026-06-01","return_date": "2026-06-05","total_price": "$119","total_price_as_number": 119,"total_duration_seconds": 12900,"total_stops": 0,"buy_link": "https://www.google.com/travel/flights?tfs=...&curr=usd","departure_flight_departure_description": "7:00 AM on Mon, Jun 1","departure_flight_arrival_description": "8:50 AM on Mon, Jun 1","departure_flight_airline": "easyJet","departure_flight_stops": 0,"departure_flight_duration": "1 hr 50 min","departure_stops_info": [],"return_flight_departure_description": "8:20 PM on Fri, Jun 5","return_flight_arrival_description": "10:05 PM on Fri, Jun 5","return_flight_airline": "easyJet","return_flight_stops": 0,"return_flight_duration": "1 hr 45 min","return_stops_info": []}
🎯 Use cases
- Travel agents and AI assistants. Flat JSON that drops straight into a tool call, with a booking link on every row so the agent can hand the user somewhere to buy.
- Price alerts. Poll a route on a schedule and fire when
price_range_in_relation_to_other_periodsflips tolow. - Fare calendars and cheapest month views. Scan a month of departure dates and chart
price_as_numberby day. - Metasearch and comparison sites. Every result ships with a working
buy_link, so you never have to reconstruct a booking URL. - Revenue and route analysis. Track how fares on a route move over weeks, by carrier and by cabin.
- Corporate travel tooling. Per leg airline and time-of-day filters map directly onto travel policy rules.
🔌 Integrations
Schedules. Save a search as a Task and put it on a cron. 0 7 * * * checks a route every morning, 0 */6 * * * every six hours.
Webhooks. Fire ACTOR.RUN.SUCCEEDED at your own endpoint to push new fares into your database the moment a run finishes.
Make, Zapier and n8n. Apify's connectors let you route results into a spreadsheet, a Slack alert or a CRM without writing code.
Python and JavaScript clients. apify-client on PyPI and npm, examples above.
🤖 Use it from an AI agent over MCP
This Actor is available through Apify's MCP server, so any MCP capable client can search flights as a tool. That includes Claude, Cursor, ChatGPT and anything else that speaks MCP.
https://mcp.apify.com/?tools=actors,docs,mtnrabi/google-flights-real-time-api
The response shape matters here. A model can read a flat object with price_as_number, airline and buy_link and answer a question in one hop. Scrapers that return nested page dumps force you to flatten and summarise before the model can use anything.
📝 Notes worth reading before your first run
Results are live, so run time tracks route complexity. A trunk route between two major hubs comes back fast. A small regional airport with two connections takes longer, because Google itself takes longer. A search that has to retry a page it could not read takes longer still, and only the searches that need it pay that. use_fallback is not a shortcut here: leave it off.
sort_type: "Price" follows Google's ordering, which is not a strict numeric sort. If you need exact price order, sort on price_as_number yourself. Every example above does. It applies to one-way and round-trip alike - on one-way it used to be accepted and then quietly dropped on the way to the search, which is fixed.
Dates are YYYY-MM-DD and airports are IATA codes. New York is JFK, London Heathrow is LHR, Tel Aviv is TLV.
limit controls how many rows you get and therefore what a search costs. The default is 10.
❓ Frequently asked questions
Does Google Flights have a public API?
No. Google shut down the QPX Express API in 2018 and has not replaced it for general use. Anything calling itself a Google Flights API is either scraping the site or reselling someone else who does. This Actor scrapes it directly and returns the result as clean JSON, with no third party reseller in the chain.
Google Flights API alternative: how is this different from the other Google Flights scrapers?
Three things, and they are all checkable on the listing pages. Round-trip is a first class search that returns paired itineraries with a combined total, rather than two separate one-way result sets you have to match up. Google's own price insights come back on every row, so you can tell a user whether a fare is good. And filters split per leg on round-trips, so an outbound and a return can have different rules. Store numbers move, so compare the current pages yourself.
Can I use this as a flight price API for one specific route?
Yes, and it is the most common use. Save the search as a Task, schedule it, and read price_as_number and price_range_in_relation_to_other_periods on each run. That gives you both the fare and Google's judgement of whether the fare is good.
Can I build a fare calendar or search flexible dates?
Yes. Set departure_date_to and one run searches every departure date up to it, up to 31 dates, charged per date searched. That is how cheapest-month calendars and fare heatmaps get built on top of this. See the price calendar section above.
Does it return booking links?
Every result includes buy_link, a deep link into Google Flights for that exact itinerary. It is a real link you can put behind a button, not a search URL the user has to refine.
Can Claude or Cursor call this as an MCP tool?
Yes. Point your MCP client at the URL in the MCP section above. The flat response shape means a model can answer a route and price question without any post-processing.
How do I scrape Google Flights with Python?
Use the apify-client package and call this Actor, as in the Python example above. Writing your own Google Flights scraper means maintaining a headless browser, rotating residential proxies, and rewriting your parser every time Google changes the page. That is the work this Actor already does.
Which airport codes does it accept?
Three letter IATA codes. JFK, LHR, CDG, TLV, SIN. City level codes such as NYC and LON also work where Google supports them, except on a round trip with several airports, which takes airport codes only. Put up to 4 codes per side, comma-separated, to search several airports in one run.
What happens if a route has no flights?
You get an empty dataset rather than an error - and the run tells you whether that empty dataset is a real answer or a search that did not complete. See the next question.
How do I tell "no flights" apart from a failed search?
An empty dataset used to mean either one. A search that was handed a page it could not read - a consent wall, a bot check, a truncated response - is now retried automatically, while a page where Google genuinely reports no flights is never retried, so a real empty result costs no extra time. Whatever is left over, every run now reports:
- Run status message: one sentence on the run, e.g. "Search completed. Google returned no flights for this route and date, so the empty dataset is a real answer." or "Search did NOT complete (blocked_page). 0 row(s) pushed. This is not the same as 'no flights found'."
SEARCH_OUTCOMEin the run's key-value store, the machine-readable version:status(ok,empty,partial,degraded, orunreported),reason,results, and the raw headers the backend sent.- The run log: the same sentence, at
WARNINGlevel when the search did not complete.
One-way has its own version of this, and it is the one that would have been hardest to spot: flight rows are dropped when their price cannot be parsed, and that happens after the page has already been recognised as a real results page. A markup change at Google that renamed the price node would have emptied every row on every page and reported it, with total confidence, as "there are no flights on this route". Dropped rows now count: lose all of them and the run reports degraded with reason unreadable_prices and retries; lose some and you get the rest, reported as partial.
Round-trip is held to the same standard, which is harder than it sounds: a round-trip prices a return leg for each outbound candidate, and each of those fetches can fail on its own. A run only reports empty when every candidate it planned to price was attempted and every one of them read a real Google Flights page saying it had nothing. A fan-out that was blocked, or that stopped on the request's time ceiling, reports degraded or partial, never "no flights". When it stopped early, the reason is search_truncated, and x-search-incomplete-combinations in the SEARCH_OUTCOME headers counts the candidates it did not get to finish.
A run whose search did not complete fails, and is not charged, instead of finishing with an empty dataset. That used to need strict; it is now how every run behaves, because the backend reports an incomplete search as an error rather than as an empty result. So an empty dataset on a run that succeeded means one thing: Google genuinely has no flights for that search. strict is still accepted and changes nothing.
💳 Pricing
Pay per event: $3.00 per 1,000 flight lookups, one lookup per airport pair searched however many flights come back (a normal search is one pair, so one lookup per run; TLV,ATH to LHR,CDG is 4; a date range is one lookup per date searched, per pair), plus a small per-result fee ($0.01 per 1,000 rows) and Apify's Actor-start fee. Apify's free plan comes with $5 of usage every month, enough to try it on your own routes. The Store pricing tab is authoritative.
🔗 Related
- Booking.com hotel prices, same approach, live rates and availability: mtnrabi/booking-real-time-api
Using this Actor? A rating takes ten seconds and helps other developers find it. Found a bug or need a field that is not here? Open an issue with the input you used and the output you got, and I will answer.