# Google Flights Multi-City Scraper (`boncross/google-flights-multi-city`) Actor

Scrape Google Flights multi-city trips of three legs or more, and quiet routes Google leaves out of its own page. Each row is an itinerary with its price, airlines, aircraft, legroom, layovers, CO2 and contrail warming. $0.30 per 1,000 flights.

- **URL**: https://apify.com/boncross/google-flights-multi-city.md
- **Developed by:** [BonCross API](https://apify.com/boncross) (community)
- **Categories:** Travel
- **Stats:** 1 total users, 1 monthly users, 40.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.21 / 1,000 flight-scrapeds

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 Multi-City Scraper

### What is Google Flights Multi-City Scraper?

Google Flights Multi-City Scraper prices the trips an ordinary flight search can't: an itinerary of three legs or more as one fare, and the quiet routes where Google's page shows nothing even though flights exist. Give it your legs, one per line, or a single route, and you get the itineraries back as structured data. It will also tell you the cheapest date to fly across a window of months.

Three things it does that are hard to get elsewhere:

- **A multi-city trip as one fare.** London, Paris, Rome and back to London returned 21 to 22 itineraries in about a second per search, each priced for the whole trip.
- **Routes that normally come up empty.** Port Moresby to Honiara, a return that an ordinary search leaves blank, returned 13 itineraries in 4 seconds.
- **The cheapest day to go.** Sixty routes with a four-month date window returned 600 itineraries and 7,136 dated prices in 29 seconds.

You also get the contrail warming potential of each leg, which most flight data leaves out, and you pay $0.30 per 1,000 flights on the Free plan ($0.21 on Business) with proxy usage and platform usage included.

**BonCross Verified** means we tested this actor against the live site before release and kept the record:

- 41 of 41 live checks on its inputs and filters passed on 8 October 2026
- 4 searches run on 7 October 2026, a four-leg trip and 3 quiet routes: 60 itineraries, none for the four-leg trip
- Charges matched: 1 start, 4 searches, 60 itineraries

Planning an ordinary one way or return search? Use [Google Flights Scraper](https://apify.com/boncross/google-flights-search). It's built for those and costs two thirds as much per flight. This actor is for the shapes that one can't do: multi-city trips, quiet routes and flexible dates.

What else you can do with it:

- Read the flights of the first leg (flight number, aircraft type, legroom and operating carrier) beside the whole trip's fare
- See layover airports, layover length and whether the connection changes airport
- Get the CO2 estimate per itinerary and per leg, against the route's typical, with Google's lower-emissions badge
- Compare each itinerary's price with what it cost on your last run
- Search several airports at once, or a whole city, and get the pairings in one run
- Exclude an airline or a whole alliance, and sort by price, time, duration or CO2
- Filter on stops, airline, alliance, price, time windows, journey time, layover length, connecting airports and lower emissions
- Follow a link straight into Google's booking page for each itinerary
- See which airline you book and which one actually operates each leg

### Who uses multi-city flight data?

- Travel planners pricing a round-the-world or multi-stop itinerary as one trip, not as separate tickets
- Anyone comparing the same multi-city trip across departure dates to find the cheapest combination
- Teams that need fares for regional and island routes an ordinary search leaves empty
- Booking and corporate travel tools that take multi-leg itinerary data as a feed
- Fare watchers tracking a multi-stop trip daily to catch a drop
- Sustainability teams measuring the carbon cost of a multi-leg travel policy, contrail warming included
- Researchers looking at which aircraft and cabins serve a quiet route and what legroom they offer, or keeping a fare history for pricing or academic work

### What data can you extract?

| | |
|---|---|
| ✈️ Airline names and codes | Flight numbers of the first leg |
| 💷 Price and currency | Trip type: one way, return or multi-city |
| 🕑 Departure and arrival times | 📅 Dates, with next-day arrival offset |
| ⏱ Total journey time | Number of stops |
| 🛬 Layover airport, city and length | Airport-change flag on connections |
| Aircraft type per leg | Legroom in inches |
| 🔌 In-seat power and USB | 📶 Free or paid Wi-Fi per leg |
| 🎬 Live TV, on-demand video and streaming | 🧳 Airline baggage policy links |
| 🌱 CO2 per itinerary and per leg | CO2 against the route's typical, lower-emissions badge |
| ☁️ Contrail warming potential | Cabin flown on each leg |
| 💺 Seat type and legroom rating | Codeshare flag per leg |
| Marketing and operating carrier and flight number | 🛫 Origin and destination airport per leg |
| Price change since your last run | ⭐ Google's best-flights flag |
| 🗺 One fare for the whole multi-city trip | The legs of the trip, and which one the row details |
| 📆 Cheapest fare per departure date | 🔗 Booking link and booking token per itinerary |

### How to scrape a multi-city trip

It's a form. You type the legs and download a spreadsheet.

1. [Create a free Apify account](https://console.apify.com/sign-up) and open [Google Flights Multi-City Scraper](https://apify.com/boncross/google-flights-multi-city).
2. Add your legs, one per line, as origin, destination and date: `LHR,JFK,2026-11-10`.
3. Set cabin, passengers and any airline, stop or layover filters you want.
4. Click Start. A three-leg trip took 1 to 4 seconds in the measured runs below.
5. Download the trip's itineraries as a spreadsheet (Excel or CSV), or take JSON, XML or HTML.

### ⬇️ Input

Give it a multi-city trip, or a single quiet route that an ordinary search returns nothing for. Airport codes such as `LHR` work, and you can use city codes such as `LON` or `TYO` too. If you have more one way or return routes, add them under "More routes", one per line as origin, destination, departure date and an optional return date. Paste Google Flights links into "Google Flights URLs".

For a long list of links, use Start URLs. It loads them in bulk from a file, a Google Sheet or a remote list, and whatever you put there is combined with the pasted links.

You have two limits. "Max itineraries per search" applies to each trip or route separately: ten targets at 50 each returns 500. "Maximum results for the whole run" is the ceiling across all of them. Once it's reached, nothing more is fetched or charged.

**A multi-city trip**

```json
{
  "multiCityLegs": [
    "LHR,JFK,2026-11-10",
    "JFK,LAX,2026-11-15",
    "LAX,LHR,2026-11-20"
  ],
  "currency": "GBP"
}
```

**A four leg trip in business class**

```json
{
  "multiCityLegs": [
    "LHR,NRT,2026-11-10",
    "NRT,ICN,2026-11-15",
    "ICN,SIN,2026-11-18",
    "SIN,LHR,2026-11-22"
  ],
  "cabinClass": "business",
  "adults": 2
}
```

**A quiet route that an ordinary search leaves empty**

```json
{
  "origin": "POM",
  "destination": "HIR",
  "departureDate": "2026-11-17",
  "returnDate": "2026-11-24",
  "currency": "GBP"
}
```

**When is it cheapest to go?**

```json
{
  "origin": "LHR",
  "destination": "JFK",
  "departureDate": "2026-11-17",
  "returnDate": "2026-11-24",
  "flexibleDates": true,
  "flexibleDateFrom": "2026-11-01",
  "flexibleDateTo": "2027-01-15",
  "tripLengthDays": 7,
  "currency": "GBP"
}
```

Switch on "Also return the cheapest date to fly" and give a window with the earliest and latest departure dates. You get one row per departure date in the window, with the lowest fare for that date under the same cabin, stops and airline filters as the search. For a return trip, "Trip length in days" sets how many nights to stay. It defaults to the gap between your departure and return dates.

**Lower-emission options on the first leg, cheapest first**

```json
{
  "multiCityLegs": [
    "LHR,CDG,2026-11-10",
    "CDG,FCO,2026-11-14",
    "FCO,LHR,2026-11-18"
  ],
  "lessCo2Only": true,
  "sortBy": "price"
}
```

**Watch a multi-city trip and report what moved**

```json
{
  "multiCityLegs": [
    "LHR,JFK,2026-11-10",
    "JFK,LAX,2026-11-15",
    "LAX,LHR,2026-11-20"
  ],
  "trackPriceChanges": true,
  "priceMemoryStore": "autumn-tour"
}
```

**Passengers and bags.** Besides adults (default `1`, 1 to 9) you can set children aged 2 to 11, "Infants in seat" (their own seat) and "Infants on lap", 0 to 8 each. The two bag settings, "Carry-on bags" and "Checked bags" (0 to 5), keep only fares that include that many bags, and they work on one way and return searches. Google doesn't apply bag filters to a multi-city itinerary, and they have no effect there.

**Filters.** On a multi-city trip the stop, journey-time, layover and connecting-airport filters apply to the first leg, the one each row details.

| Setting | What it does |
|---|---|
| `maxStops` (0 to 3) | Empty for any number of stops, `0` for nonstop only |
| `airlines`, `excludeAirlines` | Two letter airline codes such as `BA`, or an alliance (`ONEWORLD`, `SKYTEAM`, `STAR_ALLIANCE`), to keep or to leave out. An itinerary is excluded if any of its flights match |
| `maxPrice` | Keep only itineraries at or below this fare, in the chosen currency |
| `departureTimeFrom`, `departureTimeTo`, `arrivalTimeFrom`, `arrivalTimeTo` (hours, 0 to 23) | Keep only flights leaving or landing within these hours |
| `maxDurationMinutes` (60 to 4000) | Keep only itineraries no longer than this, door to door |
| `minLayoverMinutes`, `maxLayoverMinutes` (30 to 1440) | Drop itineraries with a connection shorter or longer than this |
| `onlyConnectingAirports`, `excludeConnectingAirports` | Three letter airport codes such as `AMS`, to connect only through or to avoid |
| `lessCo2Only` | Keep only Google's lower-emission options |
| `sortBy` | Order by price, journey time, departure time, arrival time, CO2 or number of stops. `top` keeps the site's own ranking |
| `language` (default `en`) | Language code for airport and airline names |

#### For developers

- `maxResultsPerSearch` is the per-target limit and `maxItems` the run-wide one. Leave either at its default of 0 and it doesn't limit anything.
- `fastPath` (Try the fast path first, default `true`) gives much faster results. Measured on a three-leg trip, it returned 21 itineraries in under a second against 22 in about six with it off, the extra one being the highest-priced option. Turn it off if you need the last itinerary and can spare the time.
- `maxConcurrency` (Parallel requests, default `8`, 1 to 32) is the number of requests in flight at once. Searches in one run, and the date windows of each price calendar, are fetched at the same time. If a run reports errors, try a lower value.
- Flexible-date windows longer than 61 days are split automatically. The window ends at most 330 days after `flexibleDateFrom` and 330 days from today. A later `flexibleDateTo` is brought in to that limit, with a note in the log.
- With `trackPriceChanges` on, each itinerary's price is kept in a named storage between runs, and the next run fills `previousPrice`, `priceChange`, `priceChangePct` and `previousScrapedAt`. Two watchlists sharing one `priceMemoryStore` name would overwrite each other. Name them apart.
- Alliance membership for `excludeAirlines` is kept by this actor. An airline that joined very recently may not be recognised.
- Each search is retried up to three times. A run where all searches come up empty restarts once and then fails with a message. That way a refusal is never reported as a trip with no flights.
- Rows are saved as the run goes. A run that the platform restarts picks up where it stopped, and no row is saved or charged a second time.

### ⬆️ Output

You get one record per itinerary, plus one record per departure date when flexible dates are switched on. A search Google answers with no itineraries writes one `noItineraries` record naming the trip, which means no search goes unaccounted for. Open the Overview view for a table of price, airlines, times, duration, stops, CO2 and price change.

```json
{
  "recordType": "itinerary",
  "id": "UtB22d",
  "url": "https://www.google.com/travel/flights/search?tfs=…",
  "scrapedAt": "2026-09-26T17:33:34.793Z",
  "origin": "LHR",
  "destination": "LHR",
  "departureDate": "2026-11-10",
  "returnDate": null,
  "isTopFlight": true,
  "price": 577,
  "isPriceAvailable": true,
  "currency": "GBP",
  "priceType": "multi-city",
  "itinerary": ["LHR-CDG 2026-11-10", "CDG-FCO 2026-11-14", "FCO-LHR 2026-11-18"],
  "detailLeg": "LHR-CDG 2026-11-10",
  "airlineCode": "BA",
  "airlineNames": ["British Airways"],
  "operatingAirlineNames": [],
  "originCode": "LHR",
  "destinationCode": "CDG",
  "departureAt": "2026-11-10T07:15",
  "departureTime": "07:15",
  "arrivalAt": "2026-11-10T09:30",
  "arrivalDate": "2026-11-10",
  "arrivalTime": "09:30",
  "arrivalDayOffset": 0,
  "durationMinutes": 75,
  "durationText": "1 hr 15 min",
  "stops": 0,
  "layovers": [],
  "emissionsKg": 58,
  "emissionsTypicalKg": 62,
  "emissionsVsTypicalPct": -6,
  "isLowerEmissions": true,
  "legs": [
    {
      "airlineCode": "BA",
      "airlineName": "British Airways",
      "flightNumber": "BA304",
      "operatedByCode": "BA",
      "operatedByName": "British Airways",
      "operatedByFlightNumber": "BA304",
      "marketingCarriers": [],
      "isCodeshare": false,
      "originCode": "LHR",
      "originName": "Heathrow Airport",
      "destinationCode": "CDG",
      "destinationName": "Paris Charles de Gaulle Airport",
      "departureAt": "2026-11-10T07:15",
      "arrivalAt": "2026-11-10T09:30",
      "durationMinutes": 75,
      "aircraft": "Airbus A320",
      "legroom": "29 in",
      "legroomClass": "below average",
      "cabinClass": "economy",
      "emissionsKg": 58,
      "contrailWarming": "low",
      "amenities": {
        "powerAndUsbOutlets": false,
        "onDemandVideo": false,
        "wifiType": "paid",
        "wifi": true,
        "wifiIsPaid": true
      }
    }
  ],
  "baggageUrls": [{ "airlineCode": "BA", "airlineName": "British Airways", "url": "https://…" }],
  "bookingToken": "CjRIRkw2…",
  "itineraryToken": "[\"CAISA0dCUBoE…\"]",
  "bookingUrl": "https://www.google.com/travel/flights/booking?tfs=…",
  "previousPrice": 598,
  "priceChange": -21,
  "priceChangePct": -3.51,
  "previousScrapedAt": "2026-09-25T17:30:02.114Z"
}
```

On a multi-city row, `price` is the whole trip's fare, `itinerary` lists the trip leg by leg, and `detailLeg` names the leg that `legs`, `stops`, `layovers`, times and duration describe (the first one). On a one way or return search `itinerary` and `detailLeg` are null and the row reads like an ordinary search result.

With `flexibleDates` on, you also get one row per departure date with the lowest fare for it. Its `url` opens the search for that date pair, which puts the cheapest date in the calendar one click from its itineraries:

```json
{
  "recordType": "datePrice",
  "id": "LHR-JFK-2026-11-01-2026-11-08",
  "url": "https://www.google.com/travel/flights/search?tfs=…",
  "origin": "LHR",
  "destination": "JFK",
  "departureDate": "2026-11-01",
  "returnDate": "2026-11-08",
  "tripLengthDays": 7,
  "price": 373,
  "currency": "GBP",
  "scrapedAt": "2026-09-13T17:00:37.402Z"
}
```

Four things to know about the columns:

- `price` covers the whole party you searched for, adults, children and infants together. It isn't a per-person fare.
- `airlineCode` and `flightNumber` name the flight the way Google lists it (`BA113`). If it's a codeshare, `isCodeshare` is true and `marketingCarriers` has the partner airlines selling it under their own numbers (`AA6913`, `IB3541`).
- `operatedByName` tells you, in Google's words, when another airline flies the aircraft, a regional partner for instance ("Republic Airways as American Eagle").
- `bookingUrl` takes you to Google's page for that exact itinerary, priced in your search currency.

### How many itineraries per trip, and how fast?

| Search | Itineraries | Time |
|---|---|---|
| London, Paris, Rome, London | 21 to 22 | about 1 s per search |
| London, New York, Los Angeles, London | 5 | 4 s |
| Port Moresby to Honiara, return | 13 | 4 s |
| Four quiet routes in one run | 16 | 75 s |
| A multi-city trip plus five routes, with flexible dates | 120 itineraries and 535 dated prices | 5-6 s |
| 16 routes with flexible dates | 320 itineraries and 1,711 dated prices | 6-13 s |
| 60 routes with flexible dates over four months | 600 itineraries and 7,136 dated prices | 29 s |
| 60 routes with flexible dates over two months | 600 itineraries and 3,660 dated prices | 7-8 s |

Searches in one run are fetched at the same time, 8 at once by default. That matters on a long list: the 16-route run took 92 s one search at a time. Several thousand routes fit inside the default one-hour run limit. We measured these between 1 and 3 October 2026 as whole runs on Apify, with start-up counted.

Expect small result sets. A multi-city search returns the options Google lists for the first leg, each with the fare for the whole trip, and that came to between 5 and 22 on the trips above. A quiet route returns whatever Google has for it, often a handful. The fares are the ones Google displays in the currency you chose, and a different currency gives a different answer. Now and then Google shows an itinerary with no price. You'll find `isPriceAvailable: false` on those rows.

### How much does it cost?

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| `actor-start`, per run | $0.001 | $0.001 | $0.001 | $0.001 |
| `search-resolved`, per trip or route searched | $0.0005 | $0.0005 | $0.0005 | $0.0005 |
| `flight-scraped`, per itinerary returned, and per dated price when flexible dates are on | $0.0003 | $0.00027 | $0.00024 | $0.00021 |

Enterprise plans pay the Business price.

That's **$0.30 per 1,000 flights** on the Free plan and $0.21 on Business, and there's nothing on top: proxy usage and platform usage are included.

- A multi-city trip returning 6 itineraries: $0.001 + $0.0005 + 6 × $0.0003 = **$0.0033** ($0.0028 on Business).
- Flexible dates on one route: each dated price is a row, charged as `flight-scraped`. A 76-date window across two and a half months adds 76 × $0.0003 = $0.0228 to that route's search ($0.016 on Business).

The $5 of credits that comes with Apify's Free plan each month pays for about 1,500 multi-city searches of 6 itineraries each before you pay anything.

A search Google answers with no bookable flights isn't charged. A search that's answered with an empty list, or that has nothing left after your filters, costs the $0.0005 resolve fee alone.

### Limitations

- **A multi-city row details the first leg.** You get the options for the first leg, each priced for the whole trip, which is what Google itself shows. You don't get all combinations of all legs. On those rows `detailLeg` names the leg the flight details describe ("LHR-CDG 2026-11-10"): `legs`, `stops`, `layovers`, times and duration are that leg's, `price` is the trip's, and `itinerary` lists the legs in order.
- **A trip of four or more legs is priced for some dates and not for others.** The same four cities have returned itineraries on one set of dates and none on another a few weeks apart, while three-leg trips have returned on every test. When Google returns none, you get one row with `recordType: "noItineraries"`, the trip's cities and dates, and an `error` message saying no itineraries were returned for these dates. It costs the resolve fee alone. Try other dates.
- Google publishes no price history or typical-price range for multi-city trips or quiet routes, and you won't get a price insights record for them. Ordinary routes have one, through the plain search actor.
- Flexible dates cover one origin-and-destination pair. A multi-city trip is priced as a whole and Google publishes no per-date calendar for one. The option is skipped there, with a note in the log.
- Google occasionally fails to return results for a search that normally works. After three tries, a search that still returns nothing gets a row with an `error` message saying it was refused. That row isn't charged, and the search is worth running again later.
- On a multi-city trip the stop, journey time, layover and connecting-airport filters apply to the first leg. The price is always the whole trip's.
- You can filter by airline code and by alliance. You can't filter to a named fare brand.
- **The carry-on and checked bag filters have no effect on a multi-city trip.** Google applies them to a one-way or round-trip search but lists a multi-city itinerary without them. The rows have no bag allowance to filter on afterwards, only a link to each airline's baggage page. Both filters work normally on the one-way and round-trip searches this actor also serves.
- A fare here is what Google showed at the time of the run. It isn't a live airline quote and may have moved by the time you book.

### FAQ

#### Do I need proxies or a Google account?

Neither. There's no proxy to set up and no proxy charge, and you don't need an account or an API key of any kind.

#### Why does an ordinary search return nothing for my route?

Google doesn't put results in the page for every search. On quiet routes it serves an empty page even though flights exist. This actor loads those searches the way the site itself does, which is why it finds them.

#### My four-leg trip returned nothing. Is it broken?

Probably not. Google prices trips of four or more legs for some dates and not for others, and the same four cities can return itineraries a few weeks later. Look for the `noItineraries` row in your dataset: it names the trip and says no itineraries were returned for those dates. Move the dates and run it again.

#### Which leg do the flight details describe?

The first. On a multi-city row, `detailLeg` names it, and the price beside it is for the whole trip.

#### How do I find the cheapest date to fly?

Switch on "Also return the cheapest date to fly" and set the earliest and latest departure dates. You get a row per date with the lowest fare, and each row links to the search for that date. It works for one origin and destination, not for a multi-city trip.

#### How do I see what a trip's price did since yesterday?

Schedule the run in Apify and switch on "Compare with the last run". The second run, and each one after it, shows what an itinerary cost last time and the change in money and in percent. If you watch more than one trip list, give each its own "Price memory name".

#### Why is there no price history in my results?

Google only computes price insights for ordinary one way and return routes, which leaves out multi-city trips and quiet routes. Nothing is being dropped: the data doesn't exist for these searches. Ordinary routes do have it, through Google Flights Scraper.

#### Why are some prices missing?

Sometimes Google lists an itinerary and attaches no fare to it. Those rows have a null price and `isPriceAvailable: false`. We don't put a zero there, because a zero would read as a free flight.

#### Is scraping Google Flights legal?

In most jurisdictions, yes: scraping public information is legal, and the fare pages read here are open to anyone without a login. No personal data is collected. Republishing fares is a different matter from reading them, and that part is yours to check.

### Run it on a schedule or from code

A daily watch on a multi-stop trip needs no code. Apify's scheduler runs the actor, and a webhook or the Slack and Google Sheets integrations can pass on the trips whose fare moved.

From your own code, one call runs the actor and returns the dataset. There are clients for JavaScript and Python.

```
POST https://api.apify.com/v2/acts/boncross~google-flights-multi-city/run-sync-get-dataset-items?token=YOUR_TOKEN
```

Agents in Claude, ChatGPT, Cursor or LibreChat can price a trip too. Point the client at the Apify MCP server (`mcp.apify.com`) and reference the actor as `boncross/google-flights-multi-city`. The agent sends the legs as input JSON and gets the itineraries back.

### More from BonCross

- [Google Flights Booking Options Scraper](https://apify.com/boncross/google-flights-booking-options): See every airline and travel agency selling the same flight, each with its own price…
- [Google Flights Scraper](https://apify.com/boncross/google-flights-search): Google Flights for any route and date and get every itinerary with price, airlines,…
- [Google Maps Directions Scraper](https://apify.com/boncross/google-maps-directions): Google Maps directions as JSON for driving, transit, walking and cycling.
- [Google Hotels Search Scraper](https://apify.com/boncross/google-hotels-search): Google Hotels prices for a whole city or map area.
- [Accor Hotel Details Scraper](https://apify.com/boncross/accor-hotel-details): Full Accor hotel records.
- [Agoda Search Scraper](https://apify.com/boncross/agoda-search): Agoda search results for any destination and dates.

### Your feedback

Got a trip that should price and doesn't, or a quiet route we still miss? Open an issue on the Issues tab in Apify Console and include the legs and dates. We read every issue and reply there.

# Changelog

This Actor's version history is a separate document: https://apify.com/boncross/google-flights-multi-city/changelog.md

# Actor input Schema

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

A trip with three or more separate legs. One leg per line as origin, destination, date: LHR,JFK,2026-11-10. Leave the date off a leg to use 30 days from today for the first leg and five days after the previous leg for each later one.

## `origin` (type: `string`):

Departure airport code such as LHR. A city code such as LON or NYC searches every airport in that city. Several can be given, separated by commas, to search them together: "LHR,LGW,LCY". Note LAX, BKK and SHA are both an airport and a city code, so list the airports explicitly if you mean the whole area. You can also type a place name such as "Barcelona" or "Heathrow" and it is resolved for you.

## `destination` (type: `string`):

Arrival airport code such as JFK. A city code such as NYC searches every airport in that city. Several can be given, separated by commas: "JFK,EWR,LGA". You can also type a place name such as "Barcelona" or "Heathrow" and it is resolved for you.

## `departureDate` (type: `string`):

Outbound date as YYYY-MM-DD.

## `returnDate` (type: `string`):

Return date as YYYY-MM-DD. Leave empty for a one way search.

## `extraRoutes` (type: `array`):

One route per line as origin, destination, departure date and an optional return date. Commas separate the fields, so a multi-airport origin uses + instead: "LHR+LGW,JFK+EWR,2026-11-05,2026-11-12".

## `searchUrls` (type: `array`):

Paste search URLs straight from Google Flights. The route, dates, cabin and filters already in the URL are used as they are.

## `startUrls` (type: `array`):

Google Flights page addresses, with the option to load them in bulk from a file, a Google Sheet or a remote list. Use this when you have more links than you want to paste. Combined with anything in the list above.

## `maxResultsPerSearch` (type: `integer`):

Stop after this many itineraries for each route. 0 returns every itinerary Google lists.

## `maxItems` (type: `integer`):

A run-wide ceiling across every route. Zero means no run-wide limit. Rows that report an input the actor could not resolve are not counted here and are never charged for.

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

Travel class the fares are quoted in.

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

Number of adult passengers.

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

Number of children aged 2 to 11.

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

Number of infants travelling in their own seat.

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

Number of infants travelling on an adult lap.

## `carryOnBags` (type: `integer`):

Keep only fares that include this many carry-on bags. Applies to one-way and round-trip searches; Google does not apply bag filters to a multi-city itinerary, so it has no effect there.

## `checkedBags` (type: `integer`):

Keep only fares that include this many checked bags. Applies to one-way and round-trip searches; Google does not apply bag filters to a multi-city itinerary, so it has no effect there.

## `flexibleDates` (type: `boolean`):

Adds a row per departure date with the lowest fare for that date. Each dated price is charged as one result row.

## `flexibleDateFrom` (type: `string`):

First departure date to price, as YYYY-MM-DD.

## `flexibleDateTo` (type: `string`):

Last departure date to price, as YYYY-MM-DD. Windows longer than 61 days are split automatically. At most 330 days after the earliest date and 330 days from today; a later date is brought in to that limit.

## `tripLengthDays` (type: `integer`):

For a return trip, how many nights to stay. Defaults to the gap between your departure and return dates. Ignored for one-way searches.

## `restartAttempt` (type: `integer`):

Set by the actor when it restarts itself in a fresh container after capturing nothing. Leave it alone.

## `maxStops` (type: `integer`):

Leave empty for any number of stops. 0 keeps nonstop flights only, 1 keeps one stop or fewer, 2 keeps two or fewer. On a multi-city trip it applies to the first leg, the one each row details.

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

Keep only these carriers. Two letter airline codes such as BA or AA, or an alliance: ONEWORLD, SKYTEAM, STAR_ALLIANCE.

## `maxPrice` (type: `integer`):

Keep only itineraries at or below this fare, in the currency you asked for.

## `departureTimeFrom` (type: `integer`):

Keep only flights leaving at or after this hour, 0 to 23.

## `departureTimeTo` (type: `integer`):

Keep only flights leaving at or before this hour, 0 to 23.

## `arrivalTimeFrom` (type: `integer`):

Keep only flights landing at or after this hour, 0 to 23.

## `arrivalTimeTo` (type: `integer`):

Keep only flights landing at or before this hour, 0 to 23.

## `maxDurationMinutes` (type: `integer`):

Keep only itineraries no longer than this many minutes door to door. On a multi-city trip it applies to the first leg, the one each row details.

## `minLayoverMinutes` (type: `integer`):

Drop itineraries with a connection shorter than this many minutes. On a multi-city trip it applies to the first leg, the one each row details.

## `maxLayoverMinutes` (type: `integer`):

Drop itineraries with a connection longer than this many minutes. On a multi-city trip it applies to the first leg, the one each row details.

## `onlyConnectingAirports` (type: `array`):

Keep only itineraries that connect through these airports. Three letter codes such as BOS or AMS. On a multi-city trip it applies to the first leg, the one each row details.

## `excludeConnectingAirports` (type: `array`):

Drop itineraries that connect through these airports. Three letter codes such as ORD or CDG. On a multi-city trip it applies to the first leg, the one each row details.

## `lessCo2Only` (type: `boolean`):

Keep only the itineraries Google marks with its green lower-emissions badge (well below typical CO2 for the route). Every row also carries emissionsKg, emissionsTypicalKg and emissionsVsTypicalPct if you want a threshold of your own.

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

Three letter currency code the prices come back in. Default USD.

## `language` (type: `string`):

Language code for airport and airline names.

## `trackPriceChanges` (type: `boolean`):

Remember each itinerary's price between runs and report what changed. Fills previousPrice, priceChange, priceChangePct and previousScrapedAt.

## `priceMemoryStore` (type: `string`):

Named storage the remembered prices live in. Use different names to keep separate watchlists apart.

## `fastPath` (type: `boolean`):

Fetch results directly when Google allows it, which is much faster. Measured on a three-leg trip: the fast path returned 21 itineraries in under a second and the slower path 22 in about six, the extra one being the highest-priced option. Turn this off if you need every last itinerary and can spare the time.

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

How many requests run at once across the whole run. The default is the fastest level measured clean; lower it only if a run reports refusals or errors.

## `excludeAirlines` (type: `array`):

Airline codes to leave out, such as "AA". An alliance name (ONEWORLD, STAR_ALLIANCE, SKYTEAM) excludes its members. An itinerary is dropped if any of its flights match. Alliance membership is maintained here rather than published by the site, so a very recently joined member may not be recognised.

## `sortBy` (type: `string`):

Order of the returned rows. "top" keeps the ranking the site itself uses.

## Actor input object example

```json
{
  "multiCityLegs": [
    "LHR,JFK",
    "JFK,LAX",
    "LAX,LHR"
  ],
  "origin": "LHR",
  "destination": "JFK",
  "returnDate": "",
  "extraRoutes": [],
  "searchUrls": [],
  "startUrls": [],
  "maxResultsPerSearch": 0,
  "maxItems": 0,
  "cabinClass": "economy",
  "adults": 1,
  "children": 0,
  "infantsInSeat": 0,
  "infantsOnLap": 0,
  "carryOnBags": 0,
  "checkedBags": 0,
  "flexibleDates": false,
  "restartAttempt": 0,
  "airlines": [],
  "onlyConnectingAirports": [],
  "excludeConnectingAirports": [],
  "lessCo2Only": false,
  "currency": "USD",
  "language": "en",
  "trackPriceChanges": false,
  "priceMemoryStore": "google-flights-prices",
  "fastPath": true,
  "maxConcurrency": 8,
  "excludeAirlines": [],
  "sortBy": "top"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Google Flights multi-city results: every row the run returned.

## `stats` (type: `string`):

Counts for the run: what was resolved, fetched, filtered and returned.

# 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 = {
    "multiCityLegs": [
        "LHR,JFK",
        "JFK,LAX",
        "LAX,LHR"
    ],
    "origin": "LHR",
    "destination": "JFK",
    "excludeAirlines": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/google-flights-multi-city").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 = {
    "multiCityLegs": [
        "LHR,JFK",
        "JFK,LAX",
        "LAX,LHR",
    ],
    "origin": "LHR",
    "destination": "JFK",
    "excludeAirlines": [],
}

# Run the Actor and wait for it to finish
run = client.actor("boncross/google-flights-multi-city").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 '{
  "multiCityLegs": [
    "LHR,JFK",
    "JFK,LAX",
    "LAX,LHR"
  ],
  "origin": "LHR",
  "destination": "JFK",
  "excludeAirlines": []
}' |
apify call boncross/google-flights-multi-city --silent --output-dataset

```

## MCP server setup

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

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/nfkgfy4dvvJ6XDdbA/builds/Meco8VcDeIVKXWe0I/openapi.json
