# Google Hotels Search Scraper (`boncross/google-hotels-search`) Actor

Scrape Google Hotels prices for a whole city or map area. Just add place names or a bounding box, dates, guests and currency, and get every hotel with nightly and stay price, rating, review count, star class, coordinates and entity id. Track price changes between runs. $2.50 per 1,000 hotels.

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

## Pricing

from $1.75 / 1,000 hotel-listeds

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 Hotels Search Scraper

### What is Google Hotels Search Scraper?

One Google Hotels request shows you roughly 100 to 180 hotels, however big the city is. Google Hotels Search Scraper goes over the area in overlapping passes and removes the duplicates, which is how Osaka came back as 639 priced hotels in one run. Type a city, a district or a map box, set your dates, and you get one row per hotel with its price for that stay.

Why people choose it for market-wide pricing:

- Whole cities, quickly. Osaka, Kyoto and Tokyo together returned 2,615 to 2,811 unique hotels in 9 to 17 seconds on 2 October 2026.
- Google does most of the filtering. Price, rating, star class, amenities, brand, deals, eco-certification and free cancellation are sent to Google, and you get the matching hotels in place of a slice of its default list. In Osaka, asking for one brand returned 19 of its hotels where the unfiltered search showed 4.
- Tax is never a guess. Each priced row says whether the price includes tax, and it did on 120 of 120 rows across English, French, Japanese and German searches. 110 of them also had the pre-tax figure.
- A scheduled run can return only the hotels whose price moved. Unchanged hotels cost you nothing.
- $2.50 per 1,000 hotels on the Free plan, $1.75 on Business. Hotels your filters drop are free.

**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 7 October 2026
- 6 areas run the same day in Portugal, Georgia, Vietnam, the US and Japan, the sixth unresolvable: 100 hotels, 1 error row
- Charges matched the rows
- An area Google cannot place gets a row saying why

There are three Google Hotels actors, and they do different jobs:

| Actor | What you give it | What you get |
|---|---|---|
| Google Hotels Search Scraper (this one) | A city, district or map box | The hotels in that area, one price each |
| [Google Hotels Prices Scraper](https://apify.com/boncross/google-hotels-prices) | One hotel | The sellers Google compares for it, with a price from each |
| [Google Hotels Reviews Scraper](https://apify.com/boncross/google-hotels-reviews) | One hotel | Its guest reviews |

Start here when you don't have a hotel list yet. Each row has the entity id the other two accept.

What else it does:

- Prices any date range and 1 to 8 adults, in the currency you choose
- Returns the nightly price and the whole-stay price, plus the pre-tax price wherever Google shows one
- Sorts by price, rating or review count using Google's own sorts, which means a price sort finds the cheapest hotels Google has for those dates
- Keeps hotels, vacation rentals or both
- Adds each hotel's price change since your last run, in money and per cent
- Answers a phrase such as "hostels in Lisbon" with Google's ranking for it, checked with the language set to English, Japanese and German
- Writes a row saying why when an area can't be found

### What data do you get for each hotel?

| | |
|---|---|
| 🏨 Hotel name | Google entity id and page URL |
| 💰 Price per night | Whole-stay price |
| Pre-tax price where shown, and whether the price includes tax | Currency, dates and guests |
| ⭐ Guest rating out of 5 | Review count |
| Star class | Property type: hotel or rental |
| 📍 Latitude and longitude | Which area and which pass returned it |
| Google's one-line blurb | 📈 Price change since the last run |
| Previous price and when it was read | Scrape timestamp |
| ☎️ Phone, website and Place ID (text queries) | Maps amenities and categories (text queries) |

### How to scrape Google Hotels prices for a city

There's nothing to install and no code to write. The input is a form, and a city's hotels arrive as a spreadsheet.

1. [Create a free Apify account](https://console.apify.com/sign-up) and open [Google Hotels Search Scraper](https://apify.com/boncross/google-hotels-search).
2. Type one or more place names under Locations, such as Porto. For an exact boundary, draw a box under Custom areas.
3. Pick check-in and check-out, the number of adults and a currency.
4. Add filters if you want a narrower list, then press Start.
5. Download the hotels as Excel, CSV, JSON, XML or HTML.

### ⬇️ Input

A place name and a stay are enough. Leave the dates empty and the run prices two nights starting 30 days from today.

You have two kinds of limit. "Max hotels per area" and "Max results per text query" apply to each target separately: ten areas at 50 each returns 500. "Maximum results for the whole run" caps the total, and nothing past it is fetched or charged for.

#### A city for fixed dates

```json
{
  "locations": ["Porto"],
  "checkIn": "2026-11-13",
  "checkOut": "2026-11-15",
  "adults": 2,
  "currency": "USD",
  "language": "en",
  "country": "pt",
  "tiles": 3
}
```

Setting Country to the hotel's own market usually returns the fullest list. One Tokyo box returned 160 hotels with `country: "jp"` and 102 with `country: "us"`.

#### A scheduled monitor that reports price changes

Leave the dates empty and the run prices a rolling window. A daily schedule then always looks the same distance ahead. With a price store, only hotels whose price moved are returned and charged.

```json
{
  "locations": ["Aspen, Colorado", "Porto"],
  "checkInDaysAhead": 30,
  "nights": 2,
  "adults": 2,
  "currency": "USD",
  "priceStoreName": "aspen-porto-30-days-ahead",
  "onlyNewOrChanged": true
}
```

#### An exact map box, filtered

```json
{
  "customAreas": [
    { "label": "Porto centre", "south": 41.122, "west": -8.627, "north": 41.143, "east": -8.586 }
  ],
  "checkIn": "2026-11-13",
  "checkOut": "2026-11-15",
  "tiles": 2,
  "minRating": 4,
  "minReviews": 100,
  "hotelClasses": [4, 5],
  "amenities": ["freeWifi", "pool"],
  "freeCancellationOnly": true,
  "maxPricePerNight": 400,
  "currency": "USD",
  "sortBy": "price"
}
```

Run on 7 October 2026, this box returned four and five star hotels from $75 a night, cheapest first.

#### Google's ranking for a phrase

```json
{
  "textQueries": ["hostels in Lisbon", "ryokan in Kyoto"],
  "maxResultsPerTextQuery": 40,
  "checkIn": "2026-11-13",
  "checkOut": "2026-11-15"
}
```

Text-query rows give you Google's ranking for the phrase and the Maps details (Place ID, categories, address, phone, website, amenities). They have no dated price, which means the price limits and "Only hotels with a price" don't apply to them.

#### Which filters Google applies, and which are applied to the rows

Google applies these itself, and returns the hotels that match: the price, rating and review-count sorts, the price limits, a guest-rating floor of 3.5 or more, star classes, amenities, brands, special offers, eco-certification, free cancellation and rentals-only. Sorted by price (the default), you get the cheapest hotels Google has for your dates. `"minRating": 4` returns more hotels rated 4 and up than an unfiltered search holds.

The rest are applied to each row Google returns: rating floors below 3.5 or between Google's steps, the review-count floor, the name filters, hotels-only and the name sort.

A few details:

- `freeCancellationOnly` keeps hotels that offer a free-cancellation rate, and prices them at that rate.
- `specialOffersOnly` keeps hotels Google marks as a deal for the dates. `ecoCertifiedOnly` keeps those with an eco-certification.
- `brands` takes chain or brand names such as `["Hilton", "Marriott"]` and matches them against the brands Google lists for each place. An exact name wins. Otherwise any brand containing the text matches, and "Marriott" also takes Courtyard by Marriott.
- `amenities` takes Google's list, and a hotel must have all of the ones you tick: `freeWifi`, `freeBreakfast`, `restaurant`, `bar`, `childFriendly`, `petFriendly`, `freeParking`, `parking`, `evCharger`, `roomService`, `fitnessCentre`, `spa`, `pool`, `indoorPool`, `outdoorPool`, `airConditioned`, `wheelchairAccessible`, `beachAccess`, `allInclusive`, `airportShuttle`, `casino`, `hotTub`, `onsen`, `publicBath`, `openAirBath`, `privateBath`, `sauna`.

#### All inputs

| Field | Notes |
|---|---|
| `locations` | Place names, one area each: a city, a town or a district. |
| `customAreas` | A bounding box (`south`, `west`, `north`, `east`) or a point with `radiusKm`, each with a `label`. |
| `textQueries`, `maxResultsPerTextQuery` | Phrases for Google's own ranking, and the cap per phrase (default 60, up to 500). |
| `maxHotelsPerArea`, `maxItems` | The per-area cap and the run-wide cap. 0 means no cap on either. |
| `checkIn`, `checkOut` | Dates as YYYY-MM-DD. |
| `checkInDaysAhead`, `nights` | Used when the dates are empty: default 30 days ahead for 2 nights. |
| `adults` | 1 to 8, default 2. Prices are quoted for this occupancy. |
| `minRating`, `minReviews` | Rating and review-count floors. 0 is off. |
| `minPricePerNight`, `maxPricePerNight` | Price band per night, taxes and fees included, in your currency. A hotel with no price for the dates is dropped too. |
| `hotelClasses` | Star classes to keep, such as `[4, 5]`. Properties with no class are dropped when this is set. |
| `amenities`, `brands` | Google's amenity and brand filters. |
| `freeCancellationOnly`, `specialOffersOnly`, `ecoCertifiedOnly` | Google's own switches. All off by default. |
| `nameInclude`, `nameExclude` | Case-insensitive pieces of a name to keep (`["Hyatt", "Marriott"]`) or drop (`["hostel", "capsule"]`). |
| `onlyWithPrice` | Drop hotels Google shows without a price for the dates. |
| `propertyTypes` | `all` (default), `hotels` or `rentals`. |
| `sortBy` | `price` (default), `rating`, `reviews` or `name`. |
| `currency`, `language`, `country` | Defaults `USD`, `en` and `us`. |
| `priceStoreName`, `onlyNewOrChanged` | The price memory, and the switch that returns only hotels whose price moved. |
| `tiles` | Coverage, 1 to 8, default 3. 1 is a single pass, 3 covers a city, 5 a metro area. |
| `expandFactor`, `maxConcurrency`, `delayMs` | Tuning. See below. |

#### For developers

- `maxHotelsPerArea` and `maxResultsPerTextQuery` are per target. `maxItems` is run-wide. A row reporting an area that couldn't be resolved isn't counted against `maxItems` and isn't charged.
- A text query stops after three requests in a row that find no new lodging place, and at three times the requests its limit needs. A phrase that isn't about lodging ends quickly.
- `expandFactor` (default `1.5`, 0.1 to 10) widens or narrows the area resolved for a place name. `1` keeps Google's own framing, `1.5` takes in the suburbs, and below `1` tightens on the centre.
- `tiles` above 3 makes more requests and takes longer. Raise it to 4 or 5 for a metro area.
- `maxConcurrency` (default `32`, 1 to 32) is how many requests run at once across the run. `delayMs` (default `0`, up to 10,000) adds a pause before each request. The defaults are the settings we measured, and we'd keep them.
- `priceStoreName` names a key-value store that remembers each hotel's price for the same dates, occupancy and currency. From the second run on, rows gain the four `previous…` and `priceChange…` columns.
- If Apify moves a run to another server, or it restarts, it carries on where it stopped. No hotel is returned or charged twice.
- `checkin`, `checkout` and `checkinDaysAhead` in lower case still work.
- For `customAreas`, `brands` takes Google's numeric brand ids, not names.

### ⬆️ Output

You get one record per hotel. This one is from the Porto example above, run on 7 October 2026.

```json
{
  "id": "ChoIgb3p6s3jrr2nARoNL2cvMTFiOGMyenh6MBAB",
  "url": "https://www.google.com/travel/hotels/entity/ChoIgb3p6s3jrr2nARoNL2cvMTFiOGMyenh6MBAB",
  "entityId": "ChoIgb3p6s3jrr2nARoNL2cvMTFiOGMyenh6MBAB",
  "entityUrl": "https://www.google.com/travel/hotels/entity/ChoIgb3p6s3jrr2nARoNL2cvMTFiOGMyenh6MBAB",
  "name": "O2 Hostel",
  "stars": 2,
  "rating": 4.2,
  "ratingScale": 5,
  "reviewCount": 492,
  "lat": 41.147064199999996,
  "lng": -8.5965319,
  "pricePerNight": 30,
  "pricePerNightText": "US$30",
  "pricePerNightExcludingTax": 22,
  "priceTotal": 60,
  "priceTotalText": "US$60",
  "priceTotalExcludingTax": 44,
  "pricesIncludeTax": true,
  "currency": "USD",
  "checkIn": "2026-11-13",
  "checkOut": "2026-11-15",
  "nights": 2,
  "adults": 2,
  "snippet": null,
  "propertyType": "hotel",
  "area": "Porto",
  "tile": "41.122076,-8.62698,41.142671,-8.586338",
  "previousPricePerNight": null,
  "previousScrapedAt": null,
  "priceChange": null,
  "priceChangePct": null,
  "scrapedAt": "2026-10-07T20:25:52.402Z"
}
```

Some columns need a word:

| Column | Meaning |
|---|---|
| `id`, `url`, `entityId`, `entityUrl` | `id` and `url` repeat the entity id and its page. The entity id is what the Prices and Reviews actors take, which is why it keeps a column of that name. |
| `pricePerNight`, `priceTotal` | Taxes and fees included, in any language and country. |
| `pricesIncludeTax` | `true` on a priced row, null on a hotel with no price for your dates. |
| `pricePerNightExcludingTax`, `priceTotalExcludingTax` | The same prices before tax, filled where Google shows one: 110 of 120 rows on 2 October 2026. |
| `stars`, `rating` | `stars` is the official class where the property has one. `rating` is the guest score out of 5. |
| `snippet` | Google's one-line blurb. Most hotels don't have one: it was on 12% of rows in a 100-hotel sample. |
| `tile` | The bounding box (south, west, north, east) of the pass that found the hotel. |
| `previousPricePerNight`, `previousScrapedAt`, `priceChange`, `priceChangePct` | Filled only with a price memory store, from the second run on. |

Setting a price limit leaves out hotels with no price for your dates. In a sort, unpriced hotels come last.

### How many hotels can you get, and how fast?

These are whole runs on Apify from 2 October 2026: 2 adults, 2 nights, USD, coverage 3.

| Search | Hotels returned | Run time |
|---|---|---|
| Osaka, first 10 hotels | 10 | 8 s (two runs) |
| Osaka, Kyoto and Tokyo, every hotel | 2,615 to 2,811 unique | 9 to 17 s (two runs) |
| 10 cities, first 40 hotels each | 400 | 20 s |
| 70 cities on five continents, first 40 hotels each | 2,800 | 2.6 to 3 min |

A run covers about 25 areas a minute at the default coverage. That's roughly 1,400 areas in the default one-hour run.

Most of a run is spent waiting for Google, and its answer time changes from minute to minute. The same search can take two or three times as long in a slow minute.

Coverage recovers most of a city. It isn't a provably complete list, because Google ranks and truncates what it returns for any viewport. Raising Coverage to 4 or 5, or drawing a wider box, finds more.

### How much does it cost to search Google Hotels?

You pay per hotel that reaches your dataset, plus two small fixed fees.

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| `actor-start`, per run | $0.002 | $0.002 | $0.002 | $0.002 |
| `area-searched`, per resolved area, however many passes it takes | $0.005 | $0.005 | $0.005 | $0.005 |
| `query-checked`, per text query Google answered that returns no hotel | $0.0005 | $0.0005 | $0.0005 | $0.0005 |
| `hotel-listed`, per hotel returned | $0.0025 | $0.00225 | $0.002 | $0.00175 |

Enterprise plans pay the Business price.

**$2.50 per 1,000 hotels** on the Free plan, $1.75 on Business. That's the whole price: proxy usage and platform usage are included.

Hotels dropped by your filters are free, and so are unchanged hotels when "Only new or changed prices" is on. A text query that returns no hotel at all is billed the $0.0005 `query-checked` fee once. A query that couldn't be searched is free.

- A whole city. Osaka returned 639 hotels: $0.002 + $0.005 + 639 × $0.0025 = **$1.60** ($1.13 on Business). A city of 600 to 1,500 hotels costs $1.51 to $3.76 on the Free plan ($1.06 to $2.63 on Business).
- A daily district monitor. For a district of about 150 hotels, day one is $0.002 + $0.005 + 150 × $0.0025 = $0.38 ($0.27 on Business). With a price store, a day on which 30 prices moved costs $0.002 + $0.005 + 30 × $0.0025 = **$0.08** ($0.06 on Business).

The $5 of credits that Apify's Free plan includes each month buys about 1,950 hotels here: thirteen district-sized runs of 150.

### Limitations

- The price is Google's displayed price per night for the adults you set. It doesn't tell you who is selling at that price. For the price from each seller, pass the entity id to Google Hotels Prices Scraper.
- Occupancy is adults only here. Children and several rooms are handled by Google Hotels Prices Scraper.
- Google reshuffles which hotels an area search surfaces. Two identical runs can return a noticeably different selection. Raise Coverage for a fuller list.
- The box Google resolves for a place name can shift slightly between runs. For a monitor that stays put, draw a custom area.
- Area rows don't list a hotel's amenities or description. The amenities filter narrows the set, but the list itself appears only on text-query rows.
- Google's guest-rating filter has three steps (3.5+, 4.0+, 4.5+). A floor below 3.5, the part of a floor between two steps and the review-count floor trim the rows Google returns. They don't widen the search.
- Brands, special offers and eco-certification narrow hotels only. While one is set, vacation rentals aren't added, and rows have no brand, deal or eco column.
- Pre-tax prices appear only where Google shows them.
- Text-query rows have no dated price.

### FAQ

#### Do I need proxies or a Google account to scrape Google Hotels?

Neither. There's nothing to log in to, and proxy usage is included in the price with nothing to set up.

#### How many hotels will I get for my city?

As many as Google shows across the passes. Osaka at coverage 3 gave 639. For a wider metro area, raise Coverage to 4 or 5 or draw a bigger custom box.

#### Why do some hotels have no price?

Google won't price them for those dates, usually because they're sold out or can't be booked through Google. Switch on "Only hotels with a price" to drop them.

#### Why did the same search return different hotels the second time?

Google reshuffles which hotels it surfaces for an area, and the effect is large on a small capped run. Run with a higher Coverage and no per-area cap if you need the list to be stable, and compare runs on the entity id.

#### How do I track a city's prices day by day?

Leave the dates empty, set "Check-in days ahead", name a price memory store and switch on "Only new or changed prices". Put the run on an Apify Schedule. Each run then returns the hotels whose price moved, with the old price and the change.

#### Which market and currency are the prices for?

The ones you set. Currency takes any ISO code, and the numbers arrive in it. Country and Language choose the Google market. The domestic market usually lists more local properties and sometimes different prices.

#### Can I search by phrase, in another language?

Text queries take any phrase Google Maps understands, and we've checked them with Language set to English, Japanese and German. On 7 October 2026 two phrases run with Japanese settings returned 30 hotels between them, and two run with German settings returned 16. Rows from a phrase have no dated price.

#### Is scraping Google Hotels legal?

The actor reads only what Google serves to any logged-out visitor, and it collects hotel listings and prices, not personal data. Scraping publicly available data is generally legal, but you're responsible for complying with Google's terms and applicable law in how you use it.

### Run it on a schedule, or from your own code

A daily schedule with a price store is the setup we'd recommend for rate shopping. Each run reports who moved overnight, and a webhook can push that to Slack or a Google Sheet through Apify's integrations.

From code, `POST https://api.apify.com/v2/acts/boncross~google-hotels-search/run-sync-get-dataset-items` with the input JSON as the body returns the hotels in one call for short runs. Longer ones use the standard run endpoints, or the `apify-client` package for Node.js or Python.

AI agents can call it through the Apify MCP server at `mcp.apify.com`, where it appears as the tool `google-hotels-search`. An agent can ask for a city and dates, then hand the entity ids it gets to the Prices actor.

### More from BonCross

- [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.
- [Booking.com Hotel Search Scraper](https://apify.com/boncross/booking-hotel-search): Every hotel Booking.com is selling for a destination and your dates.
- [Expedia Hotel Search Scraper](https://apify.com/boncross/expedia-hotel-search): Every hotel Expedia and Hotels.com list for a destination and your dates.
- [Google Maps Reviews Scraper](https://apify.com/boncross/google-maps-reviews): Google Maps reviews for any place using URLs, Place IDs or names.
- [Tripadvisor Place Details Scraper](https://apify.com/boncross/tripadvisor-location-details): The full Tripadvisor record for any hotel, restaurant or attraction.

### Your feedback

Is a hotel missing from an area you know well, or a price that doesn't match what Google shows? Open an issue on the Issues tab in Apify Console and give us the place and dates. We read every one and reply there.

# Changelog

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

# Actor input Schema

## `locations` (type: `array`):

One area per entry: a city ("Porto"), a town ("Aspen, Colorado") or a district ("Shibuya, Tokyo"). Each one is resolved to the area Google Hotels would search for that name.

## `textQueries` (type: `array`):

Phrases Google Maps understands, such as "hostels in Lisbon" or "ryokan in Kyoto". You get Google's ranking for the phrase, 20 per page, with the entity id (so Google Hotels Prices Scraper can price them), Place ID, rating, class, categories, address, phone, website and Maps amenities. No dated price; use Locations for that. Centred on the first Location when you give one.

## `maxResultsPerTextQuery` (type: `integer`):

Stop after this many lodging results per text query (20 per request). A query also stops after three requests in a row with no new lodging place, and at three times the requests its limit needs, so a phrase that finds no hotels ends quickly.

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

A run-wide ceiling across every target. The per-target limit above applies to each one separately, so ten targets at 50 each returns 500; set this to cap the total. 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.

## `maxHotelsPerArea` (type: `integer`):

Return at most this many hotels per area, after sorting. 0 = no cap.

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

YYYY-MM-DD. Leave empty and the actor uses "Check-in days ahead" instead, which suits scheduled runs. The lowercase spelling checkin still works.

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

YYYY-MM-DD. Leave empty to use "Nights". The lowercase spelling checkout still works.

## `checkInDaysAhead` (type: `integer`):

Only used when Check-in date is empty: check in this many days from today. The lowercase spelling checkinDaysAhead still works.

## `nights` (type: `integer`):

Used when Check-out date is empty.

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

Number of adults (1 to 8). Prices are quoted for this occupancy.

## `minRating` (type: `number`):

Drop hotels rated below this (0 to 5). 0 = off. From 3.5 up it is also asked of Google itself, so an area returns more hotels that qualify instead of fewer rows.

## `minReviews` (type: `integer`):

Drop hotels with fewer reviews. 0 = off.

## `minPricePerNight` (type: `number`):

Drop hotels cheaper than this per night, taxes and fees included, in the selected currency. A hotel with no price for these dates is dropped too. Text-query results carry no price and are not affected. 0 = off.

## `maxPricePerNight` (type: `number`):

Drop hotels dearer than this per night, taxes and fees included, in the selected currency. A hotel with no price for these dates is dropped too. Text-query results carry no price and are not affected. 0 = off.

## `hotelClasses` (type: `array`):

Keep only these star classes, e.g. \[4, 5]: Google's own hotel-class filter. Empty = all. Properties without a class (many guesthouses and rentals) are dropped when this is set.

## `amenities` (type: `array`):

Google Hotels' own amenity filters; a hotel must have all of those ticked.

## `freeCancellationOnly` (type: `boolean`):

Only hotels offering a free-cancellation rate, priced at that rate.

## `specialOffersOnly` (type: `boolean`):

Only hotels Google marks as a deal for these dates (priced below their usual rate).

## `ecoCertifiedOnly` (type: `boolean`):

Only hotels with an eco-certification listed on Google.

## `brands` (type: `array`):

Only hotels of these chains or brands, e.g. \["Hilton", "Marriott"]. Matched against the brands Google lists for each place: an exact name first, otherwise every brand whose name contains it ("Marriott" also takes Courtyard by Marriott). A brand with no hotels in a place returns none there. Applies to locations; custom areas take Google's numeric brand ids.

## `nameInclude` (type: `array`):

Case-insensitive substrings; keep hotels whose name contains any of them (e.g. \["Hyatt", "Marriott"]).

## `nameExclude` (type: `array`):

Case-insensitive substrings; drop hotels whose name contains any of them (e.g. \["hostel", "capsule"]).

## `onlyWithPrice` (type: `boolean`):

Drop properties Google shows without a price for these dates (sold out or not bookable through Google).

## `propertyTypes` (type: `string`):

Google mixes hotels and vacation rentals in one map. Keep both, hotels only, or rentals only (decided from the entity id, so it is free).

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

The order hotels come back in within each area. Price, rating and review count are Google's own sorts, so they change which hotels are found as well as their order: with Price you get the cheapest Google has, not the cheapest of its default selection. Hotels with no price for these dates sort last.

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

ISO code, e.g. USD, EUR, GBP, JPY. Prices are returned in this currency.

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

Google interface language code, e.g. en, ja, de. Affects hotel names and the set of properties Google chooses to show.

## `country` (type: `string`):

Google market code, e.g. us, jp, gb. The local market often lists more domestic properties: the same Tokyo box gave 160 hotels with jp and 102 with us.

## `priceStoreName` (type: `string`):

Name of a key-value store. When set, the actor remembers each hotel's price for these dates, occupancy and currency, and on the next run adds previousPricePerNight, priceChange and priceChangePct to every result. Pair it with a schedule to watch a city's prices.

## `onlyNewOrChanged` (type: `boolean`):

Needs a price memory store. Skips hotels whose price is the same as last run. You are not charged for the ones skipped.

## `customAreas` (type: `array`):

Explicit areas instead of, or in addition to, place names. Either a bounding box {"label":"Porto centre","south":41.122,"west":-8.627,"north":41.143,"east":-8.586} or a point with radius in km {"label":"Kyoto Station","lat":34.9858,"lng":135.7588,"radiusKm":2}.

## `tiles` (type: `integer`):

How thoroughly to cover each area. 1 is a single pass, which Google caps at roughly 100 to 180 hotels; 3 covers a city; 5 covers a metro area. Higher values make more requests and take longer.

## `expandFactor` (type: `number`):

Widen or narrow the area Google resolves for a place name. 1 keeps Google's own framing; 1.5 covers the suburbs too; below 1 tightens on the centre.

## `delayMs` (type: `integer`):

Optional pause before each call, in milliseconds. 0 (the default) runs as fast as the parallel limit allows.

## `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.

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

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

## Actor input object example

```json
{
  "locations": [
    "Porto"
  ],
  "textQueries": [],
  "maxResultsPerTextQuery": 60,
  "maxItems": 0,
  "maxHotelsPerArea": 0,
  "checkIn": "",
  "checkOut": "",
  "checkInDaysAhead": 30,
  "nights": 2,
  "adults": 2,
  "minRating": 0,
  "minReviews": 0,
  "minPricePerNight": 0,
  "maxPricePerNight": 0,
  "hotelClasses": [],
  "amenities": [],
  "freeCancellationOnly": false,
  "specialOffersOnly": false,
  "ecoCertifiedOnly": false,
  "brands": [],
  "nameInclude": [],
  "nameExclude": [],
  "onlyWithPrice": false,
  "propertyTypes": "all",
  "sortBy": "price",
  "currency": "USD",
  "language": "en",
  "country": "us",
  "priceStoreName": "",
  "onlyNewOrChanged": false,
  "customAreas": [],
  "tiles": 3,
  "expandFactor": 1.5,
  "delayMs": 0,
  "maxConcurrency": 32,
  "restartAttempt": 0
}
```

# Actor output Schema

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

Google Hotels area search: 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 = {
    "locations": [
        "Porto"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/google-hotels-search").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 = { "locations": ["Porto"] }

# Run the Actor and wait for it to finish
run = client.actor("boncross/google-hotels-search").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 '{
  "locations": [
    "Porto"
  ]
}' |
apify call boncross/google-hotels-search --silent --output-dataset

```

## MCP server setup

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

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/tO1Dac18tBLyNRste/builds/UB9OSx1kerrrgDrri/openapi.json
