# Google Flights Scraper: Failed Runs Cost Nothing (`montyburrows/google-flights`) Actor

Scrape live Google Flights fares for any route and date: price, airline, times, stops and layovers. A run that fails or finds nothing costs nothing. A free dry run shows the price first, and the run stops at a spend cap you set.

- **URL**: https://apify.com/montyburrows/google-flights.md
- **Developed by:** [Monty Burrows](https://apify.com/montyburrows) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 itinerary delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Flights Scraper

Scrape live Google Flights fares as clean JSON, CSV or Excel. Each row has the price, airline,
departure and arrival times, stop count, total duration and every layover.

Give it a list of origins, destinations and dates. Every combination is searched, so one run can
build a whole fare calendar, such as LHR to JFK every day in September in business class.

One way, round trip and multi-city. Adults, children and infants. Alliance and stop filters.
Over a hundred currencies.

### Quick start

Press Start. The form is already filled in with a real search, LHR to JFK thirty days out, so the
first run works without you typing anything. It returns roughly 25 to 30 itineraries in about ten
seconds and costs around $0.03.

Then change three fields to your own search:

| Field           | Example                                             |
| --------------- | --------------------------------------------------- |
| From            | `LHR`, or several codes to compare departure points |
| To              | `JFK`, or several                                   |
| Departure dates | `+30`, or `2026-11-10`                              |

To see the cost before you spend anything, tick Dry run. It reports how many results you would get
and what they would cost, and it never charges you.

![A finished dry run in the Apify Console: its status line reads "Dry run: about 33 results for roughly $0.0330. Nothing was charged."](https://api.apify.com/v2/key-value-stores/rYePaVHU5R57ddPqm/records/console-dry-run.png)

The status line is the estimate. A dry run writes no rows, so the results table stays empty, and the
small figure beside the status is Apify's platform usage, which the developer pays under
pay-per-event pricing, not you.

#### Dates that do not go stale

Every date field takes a calendar date such as `2026-11-10`, or a number of days from today such
as `+30`.

Use the relative form on a schedule. A daily run pinned to a calendar date returns fares until that
date arrives and then returns nothing, with no error to tell you why. `+30` still means thirty days
out a year later, so a scheduled run needs no maintenance.

```jsonc
// A rolling fare tracker: the next five departure days, re-checked every day.
{ "origins": ["LHR"], "destinations": ["JFK"], "departureDates": ["+1", "+2", "+3", "+4", "+5"] }
```

### What you get

One row per itinerary.

![A finished run's results in the Apify Console, in table view, with the Overview, Schedule, What was searched, Connections and All fields views](https://api.apify.com/v2/key-value-stores/rYePaVHU5R57ddPqm/records/console-dataset.png)

| Field                                                    | Description                                                            |
| -------------------------------------------------------- | ---------------------------------------------------------------------- |
| `price`                                                  | Lowest total fare for the whole party, in your chosen currency         |
| `currency`                                               | ISO code, such as `USD`, `GBP` or `EUR`                                |
| `tripType`                                               | `one-way`, `round-trip` or `multi-city`                                |
| `priceBasis`                                             | How to read `price`: `one-way`, `whole-trip` or `per-leg`              |
| `legIndex` / `legCount`                                  | Which leg this row prices, out of how many                             |
| `airline` / `airlines`                                   | Marketing airline, and every airline on the itinerary                  |
| `operatedBy`                                             | The carrier actually flying it, when it differs from the one selling   |
| `stops`                                                  | `0` for nonstop                                                        |
| `departureAirport` / `departureTime`                     | Airport name and local 24-hour time                                    |
| `departureDate` / `arrivalDate`                          | Full ISO dates, so an overnight arrival is unambiguous                 |
| `arrivalAirport` / `arrivalTime`                         | Airport name and local 24-hour time                                    |
| `durationMinutes`                                        | Total journey time                                                     |
| `overnight`                                              | `true` when it lands on a later day than it left                       |
| `layovers[]`                                             | Each connection's airport, city and wait in minutes                    |
| `tripLegs[]`                                             | Every leg that was priced, so a round-trip row explains itself         |
| `returnDate`                                             | The return that was searched, or `null` on a one-way                   |
| `searchUrl`                                              | The exact Google Flights search. Paste it into a browser and check     |
| `origin` / `destination` / `searchDate`                  | What was searched, so rows from a multi-route run stay self-describing |
| `adults` / `children` / `infantsInSeat` / `infantsOnLap` | The party this fare was quoted for                                     |

### Example output

These are real rows, not a mock-up. LHR to JFK and LHR to BOS, round trip, economy, out on
10 November and back on 17 November 2026, scraped on 22 September 2026. The run returned 36
itineraries across 9 airlines, 28 of them nonstop, priced from $479 to $673, in 13 seconds.

![The cheapest itinerary for each airline and route from that run, with price, stops, times, duration and connection](https://api.apify.com/v2/key-value-stores/rYePaVHU5R57ddPqm/records/example-output.png)

| Price | Route      | Airline         | Stops   | Departs | Arrives | Duration |
| ----- | ---------- | --------------- | ------- | ------- | ------- | -------- |
| $479  | LHR to BOS | Icelandair      | 1 stop  | 12:15   | 17:50   | 34h 35m  |
| $494  | LHR to JFK | SWISS           | 1 stop  | 06:00   | 13:15   | 12h 15m  |
| $498  | LHR to JFK | Austrian        | 1 stop  | 06:00   | 14:15   | 13h 15m  |
| $537  | LHR to JFK | JetBlue         | Nonstop | 07:25   | 10:32   | 8h 07m   |
| $537  | LHR to JFK | British Airways | Nonstop | 08:20   | 11:20   | 8h 00m   |
| $537  | LHR to JFK | Virgin Atlantic | Nonstop | 09:05   | 12:25   | 8h 20m   |

One row in full, exactly as it is written to your dataset, with nothing trimmed:

```json
{
  "searchUrl": "https://www.google.com/travel/flights/search?tfs=CBwQAhoeEgoyMDI2LTExLTEwagcIARIDTEhScgcIARIDSkZLGh4SCjIwMjYtMTEtMTdqBwgBEgNKRktyBwgBEgNMSFJAAUgBcAGCAQsI____________AZgBAQ&hl=en&gl=US&curr=USD",
  "tripType": "round-trip",
  "origin": "LHR",
  "destination": "JFK",
  "searchDate": "2026-11-10",
  "returnDate": "2026-11-17",
  "tripLegs": [
    { "origin": "LHR", "destination": "JFK", "date": "2026-11-10" },
    { "origin": "JFK", "destination": "LHR", "date": "2026-11-17" }
  ],
  "legIndex": 1,
  "legCount": 1,
  "priceBasis": "whole-trip",
  "cabinClass": "economy",
  "adults": 1,
  "children": 0,
  "infantsInSeat": 0,
  "infantsOnLap": 0,
  "price": 494,
  "currency": "USD",
  "airline": "SWISS",
  "airlines": ["SWISS"],
  "operatedBy": null,
  "stops": 1,
  "departureAirport": "Heathrow Airport",
  "departureTime": "06:00",
  "departureDate": "2026-11-10",
  "arrivalAirport": "John F. Kennedy International Airport",
  "arrivalTime": "13:15",
  "arrivalDate": "2026-11-10",
  "durationMinutes": 735,
  "overnight": false,
  "layovers": [{ "airport": "Zurich Airport", "city": "Zürich", "durationMinutes": 75 }],
  "id": "round-trip|1|LHR|JFK|2026-11-10|2026-11-17|economy|1|0|0|0|2026-11-10|06:00|2026-11-10|13:15|1|SWISS",
  "actorName": "google-flights",
  "scrapedAt": "2026-09-22T15:20:45.248Z"
}
```

Paste a row's `searchUrl` into a browser and you land on the search it came from. That is how you
check any fare here against the source.

### Input

| Setting                   | What it does                                                                        |
| ------------------------- | ----------------------------------------------------------------------------------- |
| **Trip type**             | One way, round trip or multi-city.                                                  |
| **From** / **To**         | Pick from 1,149 major airports and 22 whole cities, or type any IATA code.          |
| **Departure dates**       | `YYYY-MM-DD`, or `+30` for days from today. Add a range to find the cheapest day.   |
| **Return dates**          | Round trip only, same formats. Every departure is paired with every return.         |
| **Trip length in nights** | Easier than listing returns: enter `7` and `14` to price both from every departure. |
| **Multi-city legs**       | Multi-city only. One row per leg, each with a from, a to and a date.                |
| **Passengers**            | Adults, children aged 2 to 11, infants in seat, infants on lap. Up to 9 seated.     |
| **Cabin**                 | Economy, premium economy, business or first.                                        |
| **Maximum stops**         | Any, nonstop only, 1 stop or fewer, 2 stops or fewer. Applied by Google itself.     |
| **Currency**              | Over a hundred, each verified end to end against the live page.                     |
| **Dry run**               | Estimates the result count and cost, then stops. Costs nothing.                     |
| **Maximum results**       | Hard cap on rows. The run stops the moment it is reached.                           |
| **Maximum spend (USD)**   | Hard cap on cost. The run stops before exceeding it.                                |

A **city** in From or To, such as London, New York, Paris, Tokyo and eighteen more, searches every
airport in it at once. `LON` to `NYC` returns Heathrow, Gatwick and Stansted to JFK, Newark and LaGuardia in
one run, which is usually the search you actually want. Any code not in the list can still be typed.

There is **no limit** on how many origins, destinations or dates you provide. Cost is bounded by
Maximum results and Maximum spend, and Dry run tells you the real number for free before you commit.

A single route on a single date returns roughly 25 to 30 itineraries, which is a useful number to size
**Maximum results** against.

#### How prices are quoted

Every row carries a `priceBasis` field, so you never have to work out what a price covers by
checking which trip type produced it.

| Trip type  | `priceBasis` | What `price` means                        |
| ---------- | ------------ | ----------------------------------------- |
| One way    | `one-way`    | That flight                               |
| Round trip | `whole-trip` | The entire return journey, all passengers |
| Multi-city | `per-leg`    | That leg alone, priced as a one-way       |

On a round trip, Google prices each outbound option at the whole-trip total, because you pick an
outbound before it will show you returns. A round-trip row therefore describes the outbound flight
and prices the whole journey.

On multi-city, each leg is searched and priced separately, so you get every option for every leg.
That is more than Google's own multi-city view gives you, which lists first-leg choices only. A
three-city trip returned 44 bookable itineraries in testing where Google's multi-city page showed
26 first-leg options.

The caveat, and the reason `priceBasis` exists: the sum of the legs is not a single multi-city
ticket, and a combined fare is often cheaper. If you need one ticket, use these prices to find the
right flights and then price that itinerary directly. Adding three one-ways together and calling it
a multi-city fare would be the wrong number.

### Accuracy

Fares are read from the itinerary descriptions Google writes into the page for screen-reader users.
Those are sentences with named fields rather than an internal feed of unlabelled positional arrays.

That is the difference that matters. If a positional feed shifts by one element, every row still
parses, the row count does not change, and the price column starts reporting a number that is not
the price. A reworded sentence cannot fail that way. The parse rate collapses, this Actor's health
checks fail, the run stops, and you are charged nothing.

Currency is read the same way. The Actor takes the currency from the page rather than trusting the
one it asked for, because Google does not reject a currency it cannot serve. It prices in a
different one instead. When that happens the run stops and names the currency it got, rather than
billing you for prices in the wrong money.

Checked against the leading alternative in this category, this Actor found a real mispricing in it:
two Zurich-connecting SWISS itineraries an hour apart, with the overnight departure's cheaper fare
attached to the morning flight. That is the failure described above, in production. This Actor also
returns layover airports and durations, operating carriers, and correct next-day arrival dates,
which that Actor does not.

### Limitations

**Multi-city is priced leg by leg, not as one ticket.** This is deliberate. Google's own multi-city
cannot be read reliably without a browser. Asked one way it returns an empty page. Asked another it
returns a healthy-looking page that has priced a plain round trip instead, at prices identical to
the pound, for a search that never happened. Each leg is therefore searched on its own. If another
scraper offers combined multi-city fares at HTTP speed, compare one of its open-jaw prices against
the equivalent round trip before trusting it.

**Nine seated passengers is the maximum.** Above that Google returns one-passenger fares without
saying so, so the search is refused rather than answered wrongly.

**A route with no flights fails the run instead of returning an empty dataset.** The page this Actor
reads never says "no flights". A route with no service renders the same endless loading shell as a
rate-limited request, so an empty answer cannot be told apart from a blocked one. Reporting "no
flights exist" on evidence that might mean "we were throttled" would be a guess, so the run fails,
says which it cannot rule out, and charges you nothing. If you think the route should have flights,
run it again.

**No flight numbers.** Google does not put them in the data this Actor reads.

**English only.** Results are requested in English so the fields can be read reliably.

### Pricing

Pay per result, and nothing else. No per-run fee, no charge for compute, no charge for retries.

| Plan     | Per 1,000 results |
| -------- | ----------------- |
| Free     | $1.00             |
| Bronze   | $0.90             |
| Silver   | $0.80             |
| Gold     | $0.70             |
| Platinum | $0.70             |
| Diamond  | $0.70             |

You pay only for results you actually receive. If the run fails, returns nothing, or Google changes
the page and the health checks catch it, you are charged nothing. Duplicate itineraries are removed
before billing, so the same flight is never sold to you twice.

Tick Dry run first to see the cost before you spend anything.

![A real dry run's estimate: 50 itineraries for $0.05 against caps of 60 results and $0.10, with $0.00 charged, beside the three billing rules](https://api.apify.com/v2/key-value-stores/rYePaVHU5R57ddPqm/records/cost-control.png)

![How a run becomes a bill: your search is fetched, parsed and held in a ledger, every row is checked, and only a run that passes is charged](https://api.apify.com/v2/key-value-stores/rYePaVHU5R57ddPqm/records/how-billing-works.png)

### Support and feature requests

Email actors@montyburrows.com if you find a bug, need an extra field, or want a filter that is not
here yet. Feature requests are welcome and usually quick.

# Actor input Schema

## `tripType` (type: `string`):

One way, round trip, or multi-city. A round-trip price is the total for the whole journey. Multi-city searches each leg as its own one-way and prices it separately. See the Multi-city legs field.

## `origins` (type: `array`):

Pick airports or whole cities from the list, or type any three-letter IATA code, such as LHR or LON. A city code searches every airport in that city at once. Add several to compare departure points. Ignored for multi-city.

## `destinations` (type: `array`):

Pick airports or whole cities from the list, or type any three-letter IATA code, such as JFK or NYC. A city code searches every airport in that city at once. Add several to compare destinations. Ignored for multi-city.

## `departureDates` (type: `array`):

Dates to search, each as YYYY-MM-DD, or as days from today, so +30 means thirty days from now. Relative dates are the ones to use on a schedule: they never go stale. Add several to build a fare calendar and find the cheapest day to fly. Past dates are skipped. Ignored for multi-city.

## `returnDates` (type: `array`):

Round trip only. Each as YYYY-MM-DD, or days from today such as +37. Every departure date is paired with every return date, so you get a full matrix. Returns falling before their departure are skipped.

## `tripLengthNights` (type: `array`):

An easier alternative to Return dates: enter 7 and 14 to price a one-week and a two-week trip from every departure date. Combine with Return dates if you want both.

## `multiCityLegs` (type: `array`):

Multi-city only. The legs of your trip, in order. Dates may be YYYY-MM-DD or days from today, such as +30. Each leg is searched and priced as its own one-way, so you get every option for every leg. That is more than Google's own multi-city view shows, which lists first-leg choices only. This is not a single multi-city ticket, and a combined fare is often cheaper than the sum of one-ways, so every row is marked priceBasis: per-leg.

## `adults` (type: `integer`):

Travellers aged 12 and over. Prices returned are the total for the whole party, not per person.

## `children` (type: `integer`):

Children aged 2 to 11. Airlines often price them differently from adults, which is why they are a separate field rather than just more adults.

## `infantsInSeat` (type: `integer`):

Under 2, occupying their own seat.

## `infantsOnLap` (type: `integer`):

Under 2, travelling on an adult's lap. Cannot exceed the number of adults.

## `cabinClass` (type: `string`):

Which cabin to price. Each cabin is a separate search on Google, so results are the fares actually offered in that cabin.

## `maxStops` (type: `string`):

Filter applied by Google itself, so a nonstop-only search returns more nonstop options than filtering the results afterwards would.

## `alliance` (type: `string`):

Restrict results to one alliance's carriers, which helps with status runs and points redemptions. Applied by Google itself, so you get that alliance's full inventory rather than a filtered subset.

## `airlines` (type: `array`):

Optional. Restrict results to specific carriers by two-letter IATA code, such as BA, AA, DL or LH. Combined with Airline alliance as a union, which is what Google does. Leave empty for every airline. An unrecognised code returns no flights rather than being ignored, so check the code if a run comes back empty.

## `currency` (type: `string`):

Currency to price fares in. Only currencies verified end to end against the live page are offered, because an unverified one could be quietly substituted by Google and you would be billed for prices in the wrong money.

## `dryRun` (type: `boolean`):

Estimate how many results you would get and what they would cost, then stop. Nothing is written to the dataset and you are charged nothing.

## `maxResults` (type: `integer`):

The most results this run may produce. The run stops as soon as it is reached. This is a hard cap, not a target. A single route on a single date typically returns 25 to 30 itineraries.

## `maxCostUsd` (type: `number`):

The most this run may cost you, in US dollars. The run stops before exceeding it. Leave empty to be limited only by Maximum results.

## `searchOrder` (type: `string`):

Only matters if a run stops early on Maximum results. Route first gives you every date for the first route (best for fare calendars); date first gives you every route on the first date (best for comparing routes).

## `requestDelayMs` (type: `integer`):

Pause between searches. Google answers a throttled request with an empty page rather than an error, so pacing the run is what stops it quietly losing results. Lower it only if you are sure.

## `proxy` (type: `object`):

Apify Proxy configuration. The default (datacentre) is the cheapest option and is all this Actor needs.

## `proxyTier` (type: `string`):

Datacentre is cheap and fast, and Google Flights serves it the full results page. Residential costs considerably more and is not needed here.

## `maxConcurrency` (type: `integer`):

How many requests to run at once against a single host. Searches are issued one at a time, so this mainly bounds retries.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## `requestTimeoutSecs` (type: `integer`):

How long a single request may take before it is retried.

## `debug` (type: `boolean`):

Log every request and retry. Useful when opening a support ticket.

## Actor input object example

```json
{
  "tripType": "one-way",
  "origins": [
    "LHR"
  ],
  "destinations": [
    "JFK"
  ],
  "departureDates": [
    "+30"
  ],
  "adults": 1,
  "children": 0,
  "infantsInSeat": 0,
  "infantsOnLap": 0,
  "cabinClass": "economy",
  "maxStops": "any",
  "alliance": "any",
  "currency": "USD",
  "dryRun": false,
  "maxResults": 1000,
  "maxCostUsd": 5,
  "searchOrder": "route-then-date",
  "requestDelayMs": 1000,
  "proxy": {
    "useApifyProxy": true
  },
  "proxyTier": "datacenter",
  "maxConcurrency": 4,
  "maxRequestRetries": 4,
  "requestTimeoutSecs": 30,
  "debug": false
}
```

# Actor output Schema

## `itineraries` (type: `string`):

One row per itinerary. A dry run writes none: its estimate is in the run summary.

## `runSummary` (type: `string`):

What the run did and what it charged. After a dry run, the estimate: the count, the price and every note that qualifies them.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "origins": [
        "LHR"
    ],
    "destinations": [
        "JFK"
    ],
    "departureDates": [
        "+30"
    ],
    "maxCostUsd": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("montyburrows/google-flights").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "origins": ["LHR"],
    "destinations": ["JFK"],
    "departureDates": ["+30"],
    "maxCostUsd": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("montyburrows/google-flights").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "origins": [
    "LHR"
  ],
  "destinations": [
    "JFK"
  ],
  "departureDates": [
    "+30"
  ],
  "maxCostUsd": 5
}' |
apify call montyburrows/google-flights --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,montyburrows/google-flights"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Am51jTtAaCNgMMeeJ/builds/g8Qzuk1VXQGlRPHhP/openapi.json
