Google stopped supporting the Google Flights API back in 2018. Unfortunately, this meant developers could no longer get the data they needed directly from the source. But that doesn’t mean the need for this data went away. If anything, it pushed people to find alternative ways of getting the information.
A dedicated API still returns Google Flights data, and the Python scraper here runs on it. An itinerary typically includes the price, airline, flight numbers, departure and arrival times, layovers, legroom, and a carbon estimate (some itineraries come back without a price, so a parser has to allow for that), so the same requests feed price trackers and fare-drop alerts.
Getting Started
All the examples in this article use HasData’s Google Flights API. To start using this API and run the examples provided, sign up for a free account at HasData.com and get your API key.

Endpoint for a GET request to the Flights API:
https://api.hasdata.com/scrape/google/flightsA basic request to the Google Flights API looks like this:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=LHR&arrivalId=JFK&outboundDate=2027-05-15&returnDate=2027-05-30' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Don’t forget to include your HasData API key. Here’s a Python example:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
return_date = (date.today() + timedelta(days=55)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "BER", # Departure airport code (IATA)
"arrivalId": "BKK", # Arrival airport code (IATA)
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())Swap in your key and the request above runs as is.
What the API Returns
The response shape depends on how many itineraries survive your filters, and a parser has four cases to handle:
- A broad search returns
bestFlightsandotherFlightsarrays. - A narrow one (strict filters, premium cabins, tight time windows) skips the split and returns a single
allFlightsarray with the handful of itineraries that matched, two to four in our runs. A parser that reads onlybestFlightsreports empty results on searches that in fact succeeded. - When nothing matches at all, the body has an
errorfield with the message “Google Flights returned no flights for these search parameters”. - Requests with a
bookingTokenreturnselectedFlightsplusbookingOptions.
The cURL commands below carry fixed dates in the future, and the Python versions compute theirs from today.
Full descriptions of every parameter are in the documentation, and each section below carries a runnable example for its own group.
Parameter Compatibility Table
A few of the API’s 27 search parameters conflict with each other, and the table condenses the rules. The four left out are locale and depth switches, currency, gl, hl and deepSearch.
| Parameter | Required | Trip types | Can’t combine with |
|---|---|---|---|
departureId, arrivalId | Yes, except multiCity | oneWay, roundTrip | multiCityJson (carries the route instead) |
outboundDate | Yes, except multiCity | oneWay, roundTrip | |
returnDate | For roundTrip | roundTrip | |
type | No (defaults roundTrip) | ||
multiCityJson | For multiCity | multiCity | route params above |
departureToken | No | roundTrip, multiCity | bookingToken |
bookingToken | No | all | departureToken |
showHidden | No | all | |
travelClass | No | all | |
adults, children, infantsInSeat, infantsOnLap | No | all | |
sortBy | No | all | |
stops | No | all | |
includeAirlines | No | all | excludeAirlines |
excludeAirlines | No | all | includeAirlines |
bags | No | all | |
maxPrice | No | all | |
lessEmissions | No | all | |
outboundTimes | No | all | |
returnTimes | No | roundTrip | |
layoverDuration | No | all | pointless with stops=nonStop |
maxDuration | No | all | |
includeConnections | No | all | excludeConnections |
excludeConnections | No | all | includeConnections |
Every conflict in the last column rejects the request with a 422 and an errors array naming the rule, and so does a returnDate earlier than outboundDate. Filters that merely over-narrow the search fail softly instead, with a smaller or empty result set.
Core Route Parameters (From, To, Dates)
The main parameters used most often are:
departureId,arrivalId– airport codes (IATA) or location IDs (kgmid – Google Knowledge Graph ID).outboundDate,returnDate– departure and return dates inyyyy-MM-ddformat.type– trip type (roundTrip,oneWay,multiCity). If not specified, it defaults toroundTrip. If you’re usingmultiCity, you also needmultiCityJsonfor flight details.departureToken– a token that helps retrieve return flight data forroundTripor the next leg for amultiCitytrip.
The required parameters are departureId, arrivalId, and outboundDate. If the trip type isn’t set (or is explicitly roundTrip), then returnDate is also required.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=BER&arrivalId=BKK&outboundDate=2027-07-15&type=oneWay' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "BER", # Departure airport code (IATA)
"arrivalId": "BKK", # Arrival airport code (IATA)
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay" # Trip type: "oneWay", "roundTrip" or "multiCity" (requires multiCityJson)
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())The two required IDs plus a date make a complete request, and everything after this section only narrows it.
One-Way Trip to Tokyo
Goal: Find flights from New York (JFK) to Tokyo (HND) about six weeks out, one way.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=JFK&arrivalId=HND&outboundDate=2027-06-10&type=oneWay' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
params = {
"departureId": "JFK",
"arrivalId": "HND",
"outboundDate": outbound_date,
"type": "oneWay",
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 624,
"type": "One way",
"totalDuration": 865,
"flights": [
{
"departureAirport": {
"id": "JFK",
"name": "John F. Kennedy International Airport",
"time": "2026-10-25 11:10"
},
"arrivalAirport": {
"id": "HND",
"name": "Haneda Airport",
"time": "2026-10-26 14:35"
},
"duration": 865,
"airplane": "Boeing 787",
"airline": "American",
"flightNumber": "AA 167",
"legroom": "31 in",
"travelClass": "Economy",
"extensions": [
"Average legroom (31 in)",
"In-seat power & USB outlets",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 554 kg"
]
}
],
"carbonEmissions": {
"thisFlight": 554000,
"typicalForThisRoute": 685000,
"differencePercent": -19
},
"bookingToken": "W1t7ImRlcGFydHVyZUlkIjoiSkZLIiwi..."
}
],
"priceInsights": {
"lowestPrice": 624,
"typicalPriceRange": [
500,
810
],
"priceLevel": "typical",
"priceHistory": [
[
1783742400,
750
],
[
1783828800,
750
],
[
1783915200,
750
]
]
}
}The data was retrieved using a script available in Google Colab.
Round Trip with Fixed Return
Goal: Find flights from Paris (CDG) to Bali (DPS) six weeks out, with return flights 20 days later.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=CDG&arrivalId=DPS&outboundDate=2027-07-01&returnDate=2027-07-21&type=roundTrip' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
params = {
"departureId": "CDG",
"arrivalId": "DPS",
"outboundDate": outbound_date,
"returnDate": return_date,
"type": "roundTrip",
}To get return flights, we make a request with the roundTrip parameter. This gives us a list of Paris – Bali flights and a departureToken for return flights. Then, we extract the departureToken from the results and make another request, adding it as a parameter.
response = requests.get(url, headers=headers, params=params)
data = response.json()
departure_token = None
for key in ["bestFlights", "otherFlights", "allFlights"]:
if key in data and data[key]:
departure_token = data[key][0].get("departureToken")
break
if departure_token:
params["departureToken"] = departure_token
response2 = requests.get(url, headers=headers, params=params)
data2 = response2.json()
print(data2)import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
return_date = (date.today() + timedelta(days=65)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "CDG",
"arrivalId": "DPS",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"type": "roundTrip",
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
departure_token = None
for key in ["bestFlights", "otherFlights", "allFlights"]:
if key in data and data[key]:
departure_token = data[key][0].get("departureToken")
break
if departure_token:
params["departureToken"] = departure_token
response2 = requests.get(url, headers=headers, params=params)
data2 = response2.json()
print(data2)Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 1029,
"type": "Round trip",
"totalDuration": 1065,
"extensions": [
"1 checked bag up to 25 kg included",
"Full refund for cancellations",
"Free change, possible fare difference",
"Bag and fare conditions depend on the return flight"
],
"flights": [
{
"departureAirport": {
"id": "CDG",
"name": "Aéroport de Paris-Charles de Gaulle",
"time": "2026-10-25 21:35"
},
"arrivalAirport": {
"id": "DXB",
"name": "Dubai International Airport",
"time": "2026-10-26 7:20"
},
"duration": 405,
"airplane": "Airbus A380",
"airline": "Emirates",
"flightNumber": "EK 76",
"oftenDelayedByOver30Min": true,
"legroom": "32 in",
"travelClass": "Economy",
"extensions": [
"Above average legroom (32 in)",
"In-seat power & USB outlets",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 368 kg"
]
},
{
"departureAirport": {
"id": "DXB",
"name": "Dubai International Airport",
"time": "2026-10-26 9:10"
},
"arrivalAirport": {
"id": "DPS",
"name": "I Gusti Ngurah Rai International Airport",
"time": "2026-10-26 22:20"
},
"duration": 550,
"airplane": "Airbus A380",
"airline": "Emirates",
"flightNumber": "EK 398",
"legroom": "32 in",
"travelClass": "Economy",
"extensions": [
"Above average legroom (32 in)",
"In-seat power & USB outlets",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 486 kg"
]
}
],
"layovers": [
{
"id": "DXB",
"name": "Dubai International Airport",
"duration": 110
}
],
"carbonEmissions": {
"thisFlight": 855000,
"typicalForThisRoute": 819000,
"differencePercent": 4
},
"departureToken": "W1t7ImRlcGFydHVyZUlkIjoiQ0RHIiwi..."
}
],
"priceInsights": {
"lowestPrice": 1029,
"typicalPriceRange": [
920,
1350
],
"priceLevel": "typical",
"priceHistory": [
[
1783720800,
917
],
[
1783807200,
915
],
[
1783893600,
987
]
]
}
}Note the departureToken inside each round-trip itinerary, because the return-leg request in the booking section consumes it.
Passengers & Cabin Class Options
Here’s how you set the cabin class and number of passengers:
travelClass– flight class (economy,premiumEconomy,business,first).adults,children– number of adults and children.infantsInSeat,infantsOnLap– number of infants (with a separate seat or on a lap).
For children, you need to specify kids aged 2 to 11 years. The flight class should be a text value (not a number): economy, premiumEconomy, business, first.
CURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=FRA&arrivalId=YYZ&outboundDate=2027-08-05&returnDate=2027-08-16&type=roundTrip&travelClass=first&adults=2&children=1&infantsInSeat=1&infantsOnLap=1' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
return_date = (date.today() + timedelta(days=56)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "FRA",
"arrivalId": "YYZ",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"type": "roundTrip",
"travelClass": "first", # Travel class: economy, premiumEconomy, business, first
"adults": 2, # Number of adult passengers
"children": 1, # Number of children (ages 2-11)
"infantsInSeat": 1, # Number of infants with their own seat
"infantsOnLap": 1, # Number of infants on a lap
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())The examples below each set a goal first.
Business Class for Two Adults and One Infant on Lap
Goal: Find flights from Frankfurt (FRA) to Toronto (YYZ) in business class six weeks out, with a return flight 15 days later. The tickets are for two adults and one infant on lap.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=FRA&arrivalId=YYZ&outboundDate=2027-10-15&returnDate=2027-10-30&type=roundTrip&travelClass=business&adults=2&infantsOnLap=1' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
return_date = (date.today() + timedelta(days=60)).isoformat() # 15 days after the outbound
params = {
"departureId": "FRA",
"arrivalId": "YYZ",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"type": "roundTrip",
"travelClass": "business",
"adults": 2,
"infantsOnLap": 1,
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 7402,
"type": "Round trip",
"totalDuration": 670,
"flights": [
{
"departureAirport": {
"id": "FRA",
"name": "Frankfurt Airport",
"time": "2026-10-25 13:05"
},
"arrivalAirport": {
"id": "KEF",
"name": "Keflavík International Airport",
"time": "2026-10-25 15:55"
},
"duration": 230,
"airplane": "Boeing 737MAX 9 Passenger",
"airline": "Icelandair",
"flightNumber": "FI 521",
"travelClass": "Business",
"extensions": [
"Extra reclining seat",
"In-seat power & USB outlets",
"On-demand video",
"Free Wi‑Fi",
"Carbon emissions estimate: 822 kg"
]
},
{
"departureAirport": {
"id": "KEF",
"name": "Keflavík International Airport",
"time": "2026-10-25 17:00"
},
"arrivalAirport": {
"id": "YYZ",
"name": "Toronto Pearson International Airport",
"time": "2026-10-25 19:15"
},
"duration": 375,
"airplane": "Boeing 737MAX 8 Passenger",
"airline": "Icelandair",
"flightNumber": "FI 603",
"travelClass": "Business",
"extensions": [
"Standard recliner seat",
"In-seat power & USB outlets",
"On-demand video",
"Free Wi‑Fi",
"Carbon emissions estimate: 1366 kg"
]
}
],
"layovers": [
{
"id": "KEF",
"name": "Keflavík International Airport",
"duration": 65
}
],
"carbonEmissions": {
"thisFlight": 2188000,
"typicalForThisRoute": 4458000,
"differencePercent": -51
},
"departureToken": "W1t7ImRlcGFydHVyZUlkIjoiRlJBIiwi..."
}
]
}For return flights, you need to get the departureToken from one of the flights and use it to fetch return options. We covered this in the first example, and we’ll break it down in detail later in the Complex Example section.
Family Trip (2 Adults, 2 Kids, 1 Infant with a Seat) in Economy
Goal: Find flights from Singapore (SIN) to Melbourne (MEL) in economy six weeks out, with a return 19 days later. The tickets are for two adults, two kids, and one infant with a separate seat.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=SIN&arrivalId=MEL&outboundDate=2027-11-01&returnDate=2027-11-20&type=roundTrip&travelClass=economy&adults=2&children=2&infantsInSeat=1' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
return_date = (date.today() + timedelta(days=64)).isoformat() # 19 days after the outbound
params = {
"departureId": "SIN",
"arrivalId": "MEL",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"type": "roundTrip",
"travelClass": "economy",
"adults": 2,
"children": 2,
"infantsInSeat": 1,
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 1879,
"type": "Round trip",
"totalDuration": 455,
"flights": [
{
"departureAirport": {
"id": "SIN",
"name": "Singapore Changi Airport",
"time": "2026-10-25 23:40"
},
"arrivalAirport": {
"id": "MEL",
"name": "Melbourne Airport",
"time": "2026-10-26 10:15"
},
"duration": 455,
"airplane": "Airbus A330",
"airline": "Qantas",
"flightNumber": "QF 38",
"legroom": "31 in",
"travelClass": "Economy",
"extensions": [
"Average legroom (31 in)",
"In-seat power & USB outlets",
"On-demand video",
"Carbon emissions estimate: 1709 kg"
]
}
],
"carbonEmissions": {
"thisFlight": 1709000,
"typicalForThisRoute": 1685000,
"differencePercent": 1
},
"departureToken": "W1t7ImRlcGFydHVyZUlkIjoiU0lOIiwi..."
}
]
}Five passengers, one request, and the per-seat pricing comes back already summed into the itinerary price.
Filtering & Customization
These parameters let you filter, sort, and customize your search:
sortBy– sorting criteria (price,duration, etc.).stops– number of layovers.excludeAirlines,includeAirlines– filter by airlines.bags– number of carry-on bags per passenger.maxPrice– max ticket price.lessEmissions– prioritize flights with lower CO₂ emissions.
excludeAirlines and includeAirlines are mutually exclusive, so a request carries one of them.
CURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=SIN&arrivalId=MEL&outboundDate=2027-11-01&type=oneWay&sortBy=price&stops=twoStopsOrFewer&includeAirlines=SQ%2CQF&bags=1&maxPrice=900&lessEmissions=true' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "SIN",
"arrivalId": "MEL",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
"sortBy": "price", # Sorting preference: price, duration, stops, etc.
"stops": "twoStopsOrFewer", # Max number of layovers: '0', '1', '2', 'nonStop', 'oneStopOrFewer', 'twoStopsOrFewer'
"includeAirlines": "SQ,QF", # Airlines to include (comma-separated IATA codes)
# "excludeAirlines": "UA", # Airlines to exclude (comma-separated IATA codes)
"bags": "1", # Number of carry-on bags per passenger
"maxPrice": "900", # Maximum price
"lessEmissions": "true", # Prefer flights with lower carbon emissions
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())The number of stops, as mentioned in the comments in the code, can be specified either as a number or as a text value.
Cheapest Non-Stop Flight to New York
Goal: Find direct flights from London (LHR) to New York (JFK) six weeks out, one-way. Sort by price.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=LHR&arrivalId=JFK&outboundDate=2027-06-15&type=oneWay&sortBy=price&stops=nonStop' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
params = {
"departureId": "LHR",
"arrivalId": "JFK",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
"sortBy": "price",
"stops": "0",
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 1074,
"type": "One way",
"totalDuration": 495,
"flights": [
{
"departureAirport": {
"id": "LHR",
"name": "Heathrow Airport",
"time": "2026-10-25 7:45"
},
"arrivalAirport": {
"id": "JFK",
"name": "John F. Kennedy International Airport",
"time": "2026-10-25 12:00"
},
"duration": 495,
"airplane": "Airbus A321neo",
"airline": "JetBlue",
"flightNumber": "B6 2220",
"legroom": "32 in",
"travelClass": "Economy",
"extensions": [
"Above average legroom (32 in)",
"In-seat power & USB outlets",
"On-demand video",
"Free Wi‑Fi",
"Carbon emissions estimate: 565 kg"
]
}
],
"carbonEmissions": {
"thisFlight": 565000,
"typicalForThisRoute": 327000,
"differencePercent": 73
},
"bookingToken": "W1t7ImRlcGFydHVyZUlkIjoiTEhSIiwi..."
}
],
"priceInsights": {
"lowestPrice": 1074,
"typicalPriceRange": [
495,
1000
],
"priceLevel": "high",
"priceHistory": [
[
1783724400,
597
],
[
1783810800,
597
],
[
1783897200,
597
]
]
}
}The priceInsights block rates this $1,074 fare as high against the typical $495-1,000 range for the route, and that field, rather than the fare itself, is what a price alert should read.
Flight to Tokyo with Bags, Low CO₂ Priority & No Ryanair
Goal: Find a one-way flight from Los Angeles (LAX) to Tokyo (HND) six weeks out. The price should be under $900, with one carry-on bag. Exclude Ryanair (FR) flights. Priority goes to flights with low CO2 emissions.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=LAX&arrivalId=HND&outboundDate=2027-08-10&type=oneWay&bags=1&maxPrice=900&lessEmissions=true&excludeAirlines=FR' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
params = {
"departureId": "LAX",
"arrivalId": "HND",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
"excludeAirlines": "FR", # Airlines to exclude (comma-separated IATA codes)
"bags": "1", # Number of carry-on bags per passenger
"maxPrice": "900", # Maximum price
"lessEmissions": "true", # Prefer flights with lower carbon emissions
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 559,
"type": "One way",
"totalDuration": 720,
"flights": [
{
"departureAirport": {
"id": "LAX",
"name": "Los Angeles International Airport",
"time": "2026-10-25 11:05"
},
"arrivalAirport": {
"id": "HND",
"name": "Haneda Airport",
"time": "2026-10-26 15:05"
},
"duration": 720,
"airplane": "Boeing 787",
"airline": "American",
"flightNumber": "AA 27",
"legroom": "31 in",
"travelClass": "Economy",
"extensions": [
"Average legroom (31 in)",
"In-seat power & USB outlets",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 486 kg"
]
}
],
"carbonEmissions": {
"thisFlight": 486000,
"typicalForThisRoute": 647000,
"differencePercent": -25
},
"bookingToken": "W1t7ImRlcGFydHVyZUlkIjoiTEFYIiwi..."
}
]
}Every itinerary in the response satisfies all the filters at once, so there is nothing to post-filter in your own code.
Bangkok Flights with Emirates, One Layover, Max $1000
Goal: Find flights from Paris (CDG) to Bangkok (BKK) six weeks out, with no more than one stop, one-way, only with Emirates (EK), and under $1000. Sort the flights by duration.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=CDG&arrivalId=BKK&outboundDate=2027-09-10&type=oneWay&maxPrice=1000&includeAirlines=EK&stops=oneStopOrFewer&sortBy=duration' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
params = {
"departureId": "CDG",
"arrivalId": "BKK",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
"sortBy": "duration",
"stops": "oneStopOrFewer",
"includeAirlines": "EK",
"maxPrice": "1000",
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"allFlights": [
{
"price": 833,
"type": "One way",
"totalDuration": 935,
"extensions": [
"Checked baggage for a fee",
"Partial refund for cancellations",
"Free change, possible fare difference",
"Bag and fare conditions depend on the return flight"
],
"flights": [
{
"departureAirport": {
"id": "CDG",
"name": "Aéroport de Paris-Charles de Gaulle",
"time": "2026-10-25 10:00"
},
"arrivalAirport": {
"id": "DXB",
"name": "Dubai International Airport",
"time": "2026-10-25 19:40"
},
"duration": 400,
"airplane": "Airbus A380",
"airline": "Emirates",
"flightNumber": "EK 72",
"oftenDelayedByOver30Min": true,
"legroom": "32 in",
"travelClass": "Economy",
"extensions": [
"Above average legroom (32 in)",
"In-seat power & USB outlets",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 369 kg"
]
},
{
"departureAirport": {
"id": "DXB",
"name": "Dubai International Airport",
"time": "2026-10-25 22:35"
},
"arrivalAirport": {
"id": "BKK",
"name": "Suvarnabhumi Airport",
"time": "2026-10-26 7:35"
},
"duration": 360,
"airplane": "Boeing 777",
"airline": "Emirates",
"flightNumber": "EK 374",
"legroom": "32 in",
"travelClass": "Economy",
"extensions": [
"Above average legroom (32 in)",
"In-seat power & USB outlets",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 374 kg"
]
}
],
"layovers": [
{
"id": "DXB",
"name": "Dubai International Airport",
"duration": 175
}
],
"carbonEmissions": {
"thisFlight": 744000,
"typicalForThisRoute": 675000,
"differencePercent": 10
},
"bookingToken": "W1t7ImRlcGFydHVyZUlkIjoiQ0RHIiwi..."
}
],
"priceInsights": {
"lowestPrice": 833,
"typicalPriceRange": [
790,
1250
],
"priceLevel": "typical",
"priceHistory": [
[
1783720800,
819
],
[
1783807200,
818
],
[
1783893600,
818
]
]
}
}With one carrier and a layover cap, the result set thins out to the allFlights shape described at the top of the article.
Time-Based Filtering
These filters let you set flight time and duration limits, including:
outboundTimes,returnTimes– departure/arrival time ranges. You can set up to 4 time boundaries (2 for departure, 2 for arrival) to filter flights.layoverDuration– min and max layover duration.maxDuration– max flight duration.
The most confusing filters here are outboundTimes and returnTimes, they must be set using military time, and each number marks the start of an hour. The second number is inclusive through the end of its hour, so 5,7 allows departures from 5:00 AM to 7:59 AM. For example, if you want the outbound flight in that window, you can just set:
outboundTimes = "5,7"Now, for the return flight between 6 PM and 11 PM, you’d set it like this:
returnTimes = "18,23"But what if we want to set both the departure and arrival times? Let’s say the outbound flight is between 8 AM and 11 AM, and the arrival time is between 4 PM and 9 PM:
outboundTimes = "8,11,16,21"The same goes for the return flight. For example, outbound from 3 AM to 6 AM, and arrival from 9 AM to 11 AM:
returnTimes = "3,6,9,11"Here’s a cURL request with all parameters:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=CDG&arrivalId=FCO&outboundDate=2027-09-06&returnDate=2027-09-06&outboundTimes=7,10,14,18&returnTimes=16,19,20,23&layoverDuration=45,360&maxDuration=900' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
return_date = (date.today() + timedelta(days=56)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "CDG",
"arrivalId": "FCO",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"outboundTimes": "7,10,14,18", # Preferred outbound time ranges (HH,HH,HH,HH) - first two for earliest/latest departure, last two for arrival
"returnTimes": "16,19,20,23", # Preferred return time ranges (HH,HH,HH,HH) - first two for earliest/latest departure, last two for arrival
"layoverDuration": "45,360", # Min and max layover duration in minutes
"maxDuration": "900" # Maximum total flight duration (in minutes)
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())Both boundaries are optional, so a single number filters from that hour onward.
Morning Paris to Rome Under Three Hours
Goal: Find flights from Paris (CDG) to Rome (FCO) six weeks out, departing in the morning window 7,12 (7:00 AM through 12:59 PM) with a max flight time of 3 hours (180 min).
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=CDG&arrivalId=FCO&outboundDate=2027-09-06&type=roundTrip&returnDate=2027-09-06&outboundTimes=7,12&maxDuration=180' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
params = {
"departureId": "CDG",
"arrivalId": "FCO",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"outboundTimes": "7,12",
"maxDuration": "180"
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"allFlights": [
{
"price": 374,
"type": "Round trip",
"totalDuration": 125,
"extensions": [
"Checked baggage for a fee",
"Full refund for cancellations",
"Free change, possible fare difference",
"Bag and fare conditions depend on the return flight"
],
"flights": [
{
"departureAirport": {
"id": "CDG",
"name": "Aéroport de Paris-Charles de Gaulle",
"time": "2026-10-25 11:40"
},
"arrivalAirport": {
"id": "FCO",
"name": "Leonardo da Vinci International Airport",
"time": "2026-10-25 13:45"
},
"duration": 125,
"airplane": "Airbus A321neo",
"airline": "ITA",
"flightNumber": "AZ 319",
"legroom": "31 in",
"travelClass": "Economy",
"extensions": [
"Average legroom (31 in)",
"In-seat USB outlet",
"On-demand video",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 113 kg"
]
}
],
"carbonEmissions": {
"thisFlight": 113000,
"typicalForThisRoute": 104000,
"differencePercent": 9
},
"departureToken": "W1t7ImRlcGFydHVyZUlkIjoiQ0RHIiwi..."
}
]
}The 11:40 departure falls inside the 7,12 window, since the boundary hour runs through 12:59, and the 125-minute flight clears the 180-minute cap by 55 minutes on this route.
Layover Airport Filtering
includeConnections lists the layover airports a result may use, and excludeConnections lists the ones it may not. They can’t both be sent on the same request, so pick the shorter list to write.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=ORD&arrivalId=BKK&outboundDate=2027-08-15&type=oneWay&excludeConnections=DXB,PEK' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "ORD",
"arrivalId": "BKK",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
# "includeConnections": "DOH,FRA", # Required connection airports (comma-separated IATA codes)
"excludeConnections": "DXB,PEK", # Forbidden connection airports (comma-separated IATA codes)
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())On a route with many one-stop options, the two excluded hubs visibly reshuffle the list.
Berlin to Barcelona Avoiding London and Paris Hubs
Goal: Find flights from Berlin (BER) to Barcelona (BCN) six weeks out, while excluding layovers in London and Paris.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=BER&arrivalId=BCN&outboundDate=2027-06-15&type=oneWay&excludeConnections=LHR,CDG' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script parameters:
params = {
"departureId": "BER",
"arrivalId": "BCN",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
"excludeConnections": "LHR,CDG"
}Example response:
{
"requestMetadata": {
"status": "ok"
},
"bestFlights": [
{
"price": 70,
"type": "One way",
"totalDuration": 160,
"flights": [
{
"departureAirport": {
"id": "BER",
"name": "Berlin Brandenburg Airport",
"time": "2026-10-25 21:25"
},
"arrivalAirport": {
"id": "BCN",
"name": "Josep Tarradellas Barcelona-El Prat Airport",
"time": "2026-10-26 00:05"
},
"duration": 160,
"airplane": "Airbus A320",
"airline": "Vueling",
"flightNumber": "VY 1075",
"legroom": "29 in",
"travelClass": "Economy",
"extensions": [
"Below average legroom (29 in)",
"In-seat USB outlet",
"Stream media to your device",
"Wi‑Fi for a fee",
"Carbon emissions estimate: 134 kg"
]
}
],
"carbonEmissions": {
"thisFlight": 134000,
"typicalForThisRoute": 170000,
"differencePercent": -21
},
"bookingToken": "W1t7ImRlcGFydHVyZUlkIjoiQkVSIiwi..."
}
]
}The full response holds nine itineraries, four of them nonstop and the rest connecting through Belgrade, Frankfurt or Düsseldorf. Neither excluded airport appears among them, which is what a short European route usually looks like. The parameter does its work on long-haul searches like the Chicago to Bangkok request above.
Booking and Advanced Options
Other parameters worth mentioning:
bookingToken– fetch booking details for selected flights, has a short lifetime.showHidden– include hidden flights.
bookingToken is pulled from flight search results and helps retrieve booking options. It can’t be used with departureToken.
cURL request:
curl --location 'https://api.hasdata.com/scrape/google/flights?departureId=ORD&arrivalId=BKK&outboundDate=2027-08-15&type=oneWay&showHidden=true&bookingToken=W1t7ImRlcGFydHVyZU' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR-API-KEY'Python script:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "ORD",
"arrivalId": "BKK",
"outboundDate": outbound_date, # about six weeks out
"type": "oneWay",
"showHidden": "true", # Show full list of results with hidden ones
"bookingToken": "W1t7ImRlcGFydHVyZU" # Show booking options for selected flights
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
print(response.json())Example response:
{
"requestMetadata": {
"status": "ok"
},
"selectedFlights": [
"..."
],
"bookingOptions": [
{
"together": {
"bookWith": "British Airways",
"marketedAs": [
"BA 7069",
"BA 4269",
"BA 178"
],
"optionTitle": "Basic Economy",
"price": 523,
"bookingRequest": {
"url": "https://www.google.com/travel/clk/f",
"postData": "u=ADowPOKEfgwxjeX_5bv_K5-JEw9y1L..."
}
}
}
],
"priceInsights": {
"lowestPrice": 523,
"typicalPriceRange": [
530,
2050
],
"priceLevel": "low"
}
}With bookingToken, basic search parameters like departureId and outboundDate don’t affect the final result.
Complex Example
Sometimes, a single request isn’t enough to get the data you need. This section shows an example of a more complex request. The sections above already cover every parameter, so this one skips the cURL variants.
So, let’s say we need economy class tickets from Paris (CDG) to London (LTN) six weeks out, for two adults and two kids. We’re only looking for flights from EASYJET UK LIMITED (U2) and British Airways PLC (BA), sorted by price, with a max budget of $700. Ideally, just one layover. We also need return tickets 24 days later, with the same conditions.
To get this data, we need to make three requests:
- Find flights to London and pick the cheapest one.
- Get return flights from the same airline.
- Fetch booking options for the selected flights.
The parameters will stay the same across all requests, the only differences will be departureToken and bookingToken, which we’ll extract from the responses.
The script starts the same way as in previous examples, with just a few parameter adjustments:
params = {
"departureId": "CDG",
"arrivalId": "LTN",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"type": "roundTrip",
"adults": "2",
"children": "2",
"travelClass": "economy",
"stops": "1",
"maxPrice": "700",
"includeAirlines": "U2,BA",
"sortBy": "price",
"showHidden": "true"
}Once we get the data, we parse the JSON and extract the departureToken from the first flight. Since the results are sorted by price, this will be the cheapest option:
departure_token = None
for key in ["bestFlights", "otherFlights", "allFlights"]:
if key in data and data[key]:
departure_token = data[key][0].get("departureToken")
breakIf we have the departureToken, we move to the next step - adding it to the request parameters and fetching return flights:
if departure_token:
params["departureToken"] = departure_token
response2 = requests.get(url, headers=headers, params=params)
data2 = response2.json()Same process here – we need to extract the bookingToken:
booking_token = None
for key in ["bestFlights", "otherFlights", "allFlights"]:
if key in data2 and data2[key]:
booking_token = data2[key][0].get("bookingToken")
breakOnce we have the bookingToken, we make the third and final request to retrieve booking details and display them:
if booking_token:
params.pop("departureToken", None)
params["bookingToken"] = booking_token
response3 = requests.get(url, headers=headers, params=params)
booking_data = response3.json()
print("\n=== Booking Results ===\n")
print(f"\nURL: {booking_data['requestMetadata']['url']}\n")
for i, trip in enumerate(booking_data.get("selectedFlights", []), 1):
print(f" Flight {i} ({trip['type']}):")
print(f" Total duration: {trip['totalDuration']} min.")
print(f" Layovers: {', '.join([lay['name'] for lay in trip.get('layovers', [])]) if trip.get('layovers') else 'Non-stop'}")
print(f" Carbon footprint: {trip['carbonEmissions']['thisFlight'] / 1000} kg CO₂")
for flight in trip["flights"]:
print(f" {flight['departureAirport']['name']} ({flight['departureAirport']['time']}) -> {flight['arrivalAirport']['name']} ({flight['arrivalAirport']['time']})")
print(f" {flight['airline']} | {flight['airplane']} | {flight['flightNumber']}")
print(f" {flight['travelClass']} | {flight['legroom']} | {'Frequently delayed' if flight.get('oftenDelayedByOver30Min') else 'On time'}")
print()
print("\n=== Booking Options ===\n")
for option in booking_data.get("bookingOptions", []):
if "together" in option:
together = option["together"]
print(f" Book with {together['bookWith']}")
print(f" Flights: {', '.join(together['marketedAs'])}")Now, let’s put it all together:
import requests
from datetime import date, timedelta
outbound_date = (date.today() + timedelta(days=45)).isoformat()
return_date = (date.today() + timedelta(days=69)).isoformat()
api_key = "YOUR-API-KEY"
params = {
"departureId": "CDG",
"arrivalId": "LTN",
"outboundDate": outbound_date, # about six weeks out
"returnDate": return_date,
"type": "roundTrip",
"adults": "2",
"children": "2",
"travelClass": "economy",
"stops": "1",
"maxPrice": "700",
"includeAirlines": "U2,BA",
"sortBy": "price",
"showHidden": "true"
}
params = {k: v for k, v in params.items() if v}
url = "https://api.hasdata.com/scrape/google/flights"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
departure_token = None
for key in ["bestFlights", "otherFlights", "allFlights"]:
if key in data and data[key]:
departure_token = data[key][0].get("departureToken")
break
if departure_token:
params["departureToken"] = departure_token
response2 = requests.get(url, headers=headers, params=params)
data2 = response2.json()
booking_token = None
for key in ["bestFlights", "otherFlights", "allFlights"]:
if key in data2 and data2[key]:
booking_token = data2[key][0].get("bookingToken")
break
if booking_token:
params.pop("departureToken", None)
params["bookingToken"] = booking_token
response3 = requests.get(url, headers=headers, params=params)
booking_data = response3.json()
print("\n=== Booking Results ===\n")
print(f"\nURL: {booking_data['requestMetadata']['url']}\n")
for i, trip in enumerate(booking_data.get("selectedFlights", []), 1):
print(f" Flight {i} ({trip['type']}):")
print(f" Total duration: {trip['totalDuration']} min.")
print(f" Layovers: {', '.join([lay['name'] for lay in trip.get('layovers', [])]) if trip.get('layovers') else 'Non-stop'}")
print(f" Carbon footprint: {trip['carbonEmissions']['thisFlight'] / 1000} kg CO₂")
for flight in trip["flights"]:
print(f" {flight['departureAirport']['name']} ({flight['departureAirport']['time']}) -> {flight['arrivalAirport']['name']} ({flight['arrivalAirport']['time']})")
print(f" {flight['airline']} | {flight['airplane']} | {flight['flightNumber']}")
print(f" {flight['travelClass']} | {flight['legroom']} | {'Frequently delayed' if flight.get('oftenDelayedByOver30Min') else 'On time'}")
print()
print("\n=== Booking Options ===\n")
for option in booking_data.get("bookingOptions", []):
if "together" in option:
together = option["together"]
print(f"Book with {together['bookWith']}")
print(f" Flights: {', '.join(together['marketedAs'])}")
else:
print("Error: No bookingToken found in second request.")
else:
print("Error: No departureToken found in first request.")Here’s the result:

You can also find this script in Colab Research and use it for your own needs, just replace the parameters as needed.
Conclusion
A search returns the outbound legs, a departureToken request returns the return legs, and a bookingToken request returns the booking options. Everything else is filters on that first request, and the compatibility table lists which of them travel together. Swap in your own route and start narrowing from there.


