# ✈️🏨 Google Flights & Hotels Scraper (Any Market) (`jy-labs/google-flights-scraper`) Actor

Scrape Google Flights and Google Hotels from one Actor — switch searchType and go. Fares, airlines, layovers, CO₂ and Google's own fare history; hotel rates, ratings and reviews. Structured search tokens, not text queries, so every market returns results without a country proxy.

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

## Pricing

from $0.45 / 1,000 flight results

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

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Google Flights & Google Hotels Scraper

**One Actor, two searches.** Set `searchType` to `flights` or `hotels` and the
same run scrapes either side of Google Travel.

- **✈️ Flights** — fares, airlines, flight numbers, layovers, aircraft, legroom,
  CO₂, and Google's own fare history, for **up to 50 routes in a single run**.
- **🏨 Hotels** — room rates, stay totals, star ratings, review counts and
  coordinates, for up to 50 hotel searches in a single run.

No login. No cookies. Datacenter proxy is enough for every market.

### One line per route

```
LAX-JFK 2026-09-22
ICN-CDG 2026-10-01..2026-10-10
CDG-NRT 2026-09-22
```

`ORIGIN-DESTINATION date` for one way, `date..date` for a return. Paste a column
straight from a spreadsheet. Fifty routes are one run, one job, one result set —
not fifty runs to launch and stitch back together.

### Why searches do not come back empty

Most Google Flights scrapers ask Google in prose — `?q=Flights to NRT from CDG
on 2026-09-22`. Google parses that phrase **in the language you asked for**, so
an English phrase under a French locale is parsed by a French parser, matches
nothing, and returns a normal-looking page with no flights on it. HTTP 200. No
error. An empty run that still cost you money.

Measured 2026-08-20, same route, same date, same minute:

| Request | Locale | Itineraries |
|---|---|---:|
| `?q=Flights to NRT from CDG on 2026-09-22` | `hl=fr gl=fr` | **0** |
| `?q=Flights to NRT from CDG on 2026-09-22` | `hl=en gl=us` | 13 |
| **structured search (this Actor)** | `hl=fr gl=fr` | **10** |
| **structured search (this Actor)** | `hl=de gl=de` | 12 |
| **structured search (this Actor)** | `hl=ko gl=kr` | 12 |
| **structured search (this Actor)** | `hl=ja gl=ja` | 12 |

This Actor never sends a text query. It builds Google's own structured search
token, which carries the route as data and has no language to get wrong.

Across a 14-case sweep — 12 markets, one-way and return, 8 currencies — the
structured form returned results **14 out of 14 times**; the prose form managed
7 of 8 on the same routes.

### Any market without a proxy in that country

`market`, `language` and `currency` are request parameters, not a function of
where your proxy exits. A US datacenter exit asking for `market: de`,
`currency: EUR` returned the **same fare, 145 EUR**, as a residential exit
inside Germany. Residential proxy is never required, so a run costs what
compute costs.

Verified currencies: USD, EUR, GBP, JPY, KRW, CAD, AUD, BRL, SGD, AED and more.
Airport and hotel names come back in the language you asked for.

### What one flight row looks like

```json
{
  "query": "LAX-JFK 2026-09-22",
  "origin": "LAX", "destination": "JFK",
  "market": "fr", "currency": "EUR", "price": 145,
  "airline": "JetBlue", "airlineCode": "B6",
  "stops": 0, "totalDurationMinutes": 330,
  "departure": "2026-09-22T08:00", "arrival": "2026-09-22T16:30",
  "legs": [{
    "origin": "LAX", "destination": "JFK",
    "originName": "Aéroport international de Los Angeles",
    "flightNumber": "524", "aircraft": "Airbus A320", "legroom": "84 cm"
  }],
  "layovers": [],
  "emissions": { "grams": 417000, "routeAverageGrams": 344000, "percentVsAverage": 21 },
  "bookingToken": "CjRIYm1acEdIM3llVzBBQm10MEFCRy0t…",
  "section": "best"
}
```

`stops` is derived from the legs, never read from Google's own stop field —
that field was observed `null` on itineraries that plainly had a connection.

#### Fare history, free

Every route also yields one `flight-price-insights` row carrying Google's
cheapest current fare, its typical fare, the typical range, and roughly **60
days of fare history** as date/price pairs. Useful when you are watching a route
rather than booking today. It is **not charged as a result**.

```json
{ "searchType": "flight-price-insights", "query": "LAX-JFK 2026-09-22",
  "lowestPrice": 119, "typicalPrice": 136,
  "typicalRangeLow": 85, "typicalRangeHigh": 165,
  "priceHistory": [{ "date": "2026-06-21", "price": 134 }, … ] }
```

### Hotels, in the same Actor

Set `searchType` to `hotels` and fill `queries` instead of `routes`:

```json
{ "searchType": "hotels", "queries": ["hotels in Berlin", "hotels in Seoul"],
  "checkIn": "2026-09-22", "checkOut": "2026-09-24", "adults": 2,
  "market": "de", "language": "de", "currency": "EUR" }
```

Each row carries the hotel name, nightly rate, stay total, guest rating, review
count and coordinates. Note that Google's hotel search page serves only
sponsored listings to a plain request; this Actor reads the organic list the way
the page itself does, so a Berlin search returns well over a hundred properties
rather than eight adverts.

### Pricing, and what happens when a search finds nothing

| Event | Charged | Free tier | Paid tiers |
|---|---|---:|---:|
| Search | once per route or hotel query the Actor actually runs | $3.00 / 1,000 | from $2.70 |
| Flight result | per itinerary written to the dataset | $0.50 / 1,000 | from $0.45 |
| Hotel result | per hotel written to the dataset | $0.80 / 1,000 | from $0.72 |
| Fare-history row | never | — | — |

Paid Apify plans get 5–10% off every per-unit price.

A search that returns nothing is charged as a search and **nothing else** — you
are never billed per-result for rows you did not receive. And an empty answer is
not taken at face value: the search is retried once on a different exit first,
because Google sometimes answers a throttled request with an empty page in well
under a second. The search is still charged only once, however many attempts it
takes.

If *every* search in a run comes back empty, the run exits non-zero and shows
red, instead of finishing as a green run with an empty dataset.

**Platform usage is billed separately**, as Apify compute and data transfer on
your own plan — the per-unit prices above do not include it. This Actor is built
to keep that number small: it runs at 512 MB with no browser and needs only
datacenter proxy, so measured runs on the Apify platform cost **$0.0003–0.0013
in platform usage for a two- or three-route job**. Residential proxy, which is
where scraping bills usually go, is never used.

Every run writes a `RUN_SUMMARY` record to the key-value store listing each
search, its row count, and why it failed if it did — so a disappointing run can
be diagnosed without paying to repeat it.

### Limits worth knowing before you buy

- **50 searches per run.** Split larger jobs across runs.
- **Fares are what Google shows**, including the basic-economy fares Google
  lists. This Actor does not resolve OTA booking pages; `bookingToken` is
  provided so you can.
- **Google's result count varies by route.** A busy domestic route returned 30
  itineraries; a thin long-haul one returned 8. `maxResultsPerSearch` caps the
  top end, not the bottom.
- **Hotel rows carry no OTA price comparison.** For per-OTA hotel pricing across
  30+ booking sites, use a dedicated Google Hotels Actor.
- **A hotel with no rooms left for your dates still returns a row**, with its
  name, rating, review count and coordinates but a `null` price. Google itself
  shows the property without a rate; filter on `price !== null` if you only want
  bookable ones.
- **Calling from the API, mind the case.** `market` and `language` take
  lowercase codes (`de`, `ko`) and `currency` takes uppercase (`EUR`, `KRW`).
  The Console dropdowns handle this for you; a raw API call with `"market":
  "DE"` is rejected by the platform before the run starts.
- Google changes these payloads without notice. When a field starts arriving
  empty, that is usually why.

### Input reference

| Field | Applies to | Default | Notes |
|---|---|---|---|
| `searchType` | both | `flights` | `flights` or `hotels` |
| `routes` | flights | — | one route line each, max 50 |
| `queries` | hotels | — | one hotel search each, max 50 |
| `adults` / `children` | both | 1 / 0 | children are flights-only |
| `cabinClass` | flights | `economy` | economy, premium-economy, business, first |
| `maxStops` | flights | any | `0` for nonstop only |
| `checkIn` / `checkOut` | hotels | tomorrow, +1 night | `YYYY-MM-DD` |
| `market` / `language` / `currency` | both | `us` / `en` / `USD` | independent of proxy country. `market` and `language` are **lowercase** (`de`), `currency` is **uppercase** (`EUR`) |
| `maxResultsPerSearch` | both | 50 | caps rows per search |
| `includePriceInsights` | flights | `true` | the free fare-history row |

# Actor input Schema

## `searchType` (type: `string`):

Google Flights (default) or Google Hotels. Flights use the Routes field; hotels use the Hotel Queries field.

## `routes` (type: `array`):

One route per line: "LAX-JFK 2026-09-22" for one way, "ICN-CDG 2026-10-01..2026-10-10" for a return trip. Up to 50 routes run in a single job. Used when searchType is "flights".

## `queries` (type: `array`):

One hotel search per line, e.g. "hotels in Berlin". Used when searchType is "hotels".

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

Adult passengers (flights) or guests (hotels).

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

Child passengers. Flights only.

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

Flights only.

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

Leave empty for any number of stops. 0 returns nonstop flights only.

## `checkIn` (type: `string`):

YYYY-MM-DD. Hotels only. Defaults to tomorrow.

## `checkOut` (type: `string`):

YYYY-MM-DD. Hotels only. Defaults to the day after check-in.

## `market` (type: `string`):

Two-letter country code. Sets which market's fares Google quotes. An in-country proxy exit is NOT required — the market is chosen by this field. Lowercase ISO country code — the Console dropdown fills this in; a raw API call must send "de", not "DE".

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

Two-letter language code for airport and hotel names. Lowercase ISO language code — send "ko", not "KO".

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

Three-letter currency code for all prices. Uppercase ISO currency code — send "KRW", not "krw".

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

Caps rows per route or per hotel query. Lower it to cap spend.

## `includePriceInsights` (type: `boolean`):

Adds one extra row per route with Google's lowest/typical fare and its fare history. Not charged as a result.

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

Datacenter proxy is enough — this Actor never needs residential exits, in any market.

## Actor input object example

```json
{
  "searchType": "flights",
  "routes": [
    "LAX-JFK 2026-09-22"
  ],
  "adults": 1,
  "children": 0,
  "cabinClass": "economy",
  "market": "us",
  "language": "en",
  "currency": "USD",
  "maxResultsPerSearch": 50,
  "includePriceInsights": true,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

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

Per-search row counts and failure reasons, so an empty run can be diagnosed without repeating it.

# 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 = {
    "routes": [
        "LAX-JFK 2026-09-22"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jy-labs/google-flights-scraper").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 = { "routes": ["LAX-JFK 2026-09-22"] }

# Run the Actor and wait for it to finish
run = client.actor("jy-labs/google-flights-scraper").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 '{
  "routes": [
    "LAX-JFK 2026-09-22"
  ]
}' |
apify call jy-labs/google-flights-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/UyTrNjBfM8jhgXWce/builds/PYYQlRlCfogePurDk/openapi.json
