# Google Hotels Scraper: Price Tracker & OTA Comparison (`dig_stays/google-hotels-rates-ota-offers`) Actor

Scrape worldwide hotel prices, OTA offers and room details. Save watchlists, track price changes automatically, flag budget matches and export accommodation audit data to JSON or CSV.

- **URL**: https://apify.com/dig\_stays/google-hotels-rates-ota-offers.md
- **Developed by:** [Chris Hopkin](https://apify.com/dig_stays) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 1,000 hotel results

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 Scraper: Price Tracker & OTA Comparison

Scrape Google Hotels prices worldwide, compare OTA and official-site offers, and **automatically track price changes for a saved hotel watchlist**. Set adults and child ages, check up to 10 stays, collect room details and flag quotes that meet your nightly budget. Export hotel rates, qualifying change events and accommodation audit evidence as JSON or CSV.

Use it to answer: **Which providers are showing rates for this hotel and stay, and what is the lowest price we actually collected?** Results distinguish Google's search headline from provider offers and report currency mismatches and incomplete enrichment explicitly.

### Automatic price tracking and budget monitoring

Enable `monitorHistory` to remember observations between runs in your account. You no longer need to download and paste a baseline. Use `hotelUrls` for a stable property watchlist, choose fixed stay dates, save a task and attach a schedule in Apify when you want recurring checks.

Start with the [hotel competitor monitoring template](https://apify.com/dig_stays/monitor-hotel-competitor-prices). Replace the sample properties and enter exact dates to track the same trip over time. Its rolling-date default is useful for exploration and checks different stays on different days.

```json
{
  "searchQueries": [],
  "hotelUrls": ["https://www.google.com/travel/hotels/entity/ChgIie601P_pkqiBARoLL2cvMXYyN3JfcGsQAQ/prices"],
  "checkIn": "2026-12-10",
  "checkOut": "2026-12-12",
  "adults": 2,
  "currency": "USD",
  "countryCode": "us",
  "monitorHistory": true,
  "watchlistName": "singapore-december-trip",
  "minimumChangePercent": 5,
  "minimumChangeAmount": 10,
  "budgetPerNight": 200,
  "maxResults": 1
}
```

Replace example dates with your future trip. `EVENTS` and `EVENTS.csv` contain qualifying `price_drop`, `price_rise`, `budget_match` and `budget_exceeded` events. A price-change event must pass **both** thresholds: in this example, at least 5% and USD 10 per night. `CHANGES.csv` still includes all observations, including unchanged ones. A zero-row event list is a normal result when nothing qualifies.

The first run establishes history and can flag initial budget matches. Sequential unchanged runs do not repeat the same budget match. After a price leaves and re-enters budget, a new match is emitted. Budget checks require verified dates and guests, a numeric rate and the requested currency. Missing quotes remain unknown; the Actor never interprets a vanished search result as sold out.

History is isolated by account and watchlist. If no name is supplied, a saved task has its own history; direct Actor runs use a default watchlist. Supply different names for independent projects. Reusing a name deliberately shares history across your tasks. Up to 30 run snapshots are retained in a named key-value store, with up to 2,200 most recent comparison identities loaded. Delete that store in Apify Storage to reset history. Storage is subject to your platform retention/access settings and applicable storage charges. Failed jobs do not replace earlier observations.

Run one instance of a watchlist at a time. Concurrent runs preserve separate snapshots but can generate duplicate notifications because both may read the same previous state. Events carry deterministic IDs for retry deduplication; they are **output records**, not automatically sent emails or messages. Connect an Apify run-success webhook or your existing integration, fetch `EVENTS`, and notify only when its `count` is greater than zero. Check `RUN_SUMMARY` for partial coverage.

Comparisons require the **same exact stay, guests, market, currency and price metric**. Rolling dates are useful for lead-time research but usually represent a different trip each day, so they do not create fixed-trip price-drop events. Use fixed dates or overlapping explicit stays to track a booking opportunity. Rates from different providers can refer to different rooms and terms; a change in the lowest displayed quote is not certified same-room repricing.

### Accommodation audit export

`AUDIT` contains one observation per hotel/stay/party with `entityId`, `referenceId`, dates, guests, coordinates when available, source evidence and a `price_check` block (`attempted`, `method`, `outcome`, `live_price`, `note`). Use `auditReferences` to map Google entity IDs to spreadsheet row IDs. A valid observed quote uses outcome `observed_quote`; missing or unsuitable quotes use `unknown` and a null price. Failed jobs are listed separately. Check this outcome vocabulary against your consuming code before integration; this is not a guaranteed drop-in replacement for every audit pipeline.

`live_price` is a **nightly single-room quote**, not a guaranteed tax-inclusive trip total. `available: true` means a displayed quote was found; it does not verify checkout inventory. Google hotel entity IDs cannot be used as Airbnb room IDs. No commute time is invented; pass available coordinates to your preferred routing service.

### Quick start

1. Enter a destination such as `hotels in Singapore`, `hotels near JFK Airport`, or `hotels in London`.
2. Set check-in/check-out, or leave the rolling-date defaults: 30 days ahead for 2 nights.
3. Run the Actor. Open **Hotel rates** or **Provider price comparison** in the output dataset.
4. Download the **Provider offers CSV** for one row per offer. Check **Run health and coverage** for any incomplete searches or currency mismatches.

The default search returns up to 20 hotels and enriches the first 5 with provider offers. Raise `maxOfferHotelsPerQuery` to enrich more results. Set `maxPages` above 1 and `maxResultsPerQuery` above 20 for deeper destination coverage. Direct hotel URLs always collect offers.

### Who is this useful for?

| Workflow | What you get | How to use it repeatedly |
| --- | --- | --- |
| Hotel competitor monitoring | Headline rates, named providers, lowest collected rates and official-site offers | Save exact competitor URLs and run for upcoming stays |
| Travel and accommodation audits | Property IDs, dates, currency, coordinates and rate evidence | Join by `entityId`; use coordinates in a separate mapping service |
| OTA price research | Organic and sponsored offers from available providers | Compare displayed prices across Booking.com, Expedia, Agoda, Hotels.com and others |
| Destination benchmarking | Ratings, review counts, class, amenities and prices from search cards | Search specific districts, landmarks or airports to sample a market |
| Automated dashboards | Stable entity IDs, timestamps, explicit error statuses, JSON and flat offers CSV | Use the Apify API, schedules, webhooks or your existing integration |

Coverage depends on what Google returns. US, Singapore and UK examples are provided; searches are not restricted to those countries. This Actor reads Google Hotels. It does not scrape Airbnb or Booking.com directly, and it does not collect guest-review text.

### Example: compare Singapore hotel prices

```json
{
  "searchQueries": ["hotels in Singapore"],
  "checkInDaysFromToday": 30,
  "nights": 2,
  "currency": "USD",
  "countryCode": "us",
  "maxResultsPerQuery": 20,
  "includeOffers": true,
  "maxOfferHotelsPerQuery": 5,
  "maxResults": 20
}
```

For US market research, try `hotels near JFK Airport`, `hotels in Miami Beach` or `hotels in Manhattan`. For more targeted coverage in Asia, try `hotels in Orchard Road Singapore` or `hotels near Changi Airport`. Search ordering changes; it is not a complete hotel census.

### Example: monitor the same hotels every week

Copy each hotel's `googleHotelsUrl` from a search result into `hotelUrls`. This avoids relying on its position in the next destination search. Set `searchQueries` to an empty list for URL-only monitoring.

```json
{
  "searchQueries": [],
  "hotelUrls": [
    "https://www.google.com/travel/hotels/entity/ChgIie601P_pkqiBARoLL2cvMXYyN3JfcGsQAQ/prices"
  ],
  "checkInDaysFromToday": 30,
  "nights": 2,
  "currency": "USD",
  "countryCode": "us",
  "maxResults": 20
}
```

Save the input as a task and attach an Apify Schedule if you want recurring checks. Rolling dates keep a consistent booking lead time. For changes to the price of one particular trip, use fixed `checkIn` and `checkOut` dates instead and enable `monitorHistory`. Alternatively, pass the previous run’s `SNAPSHOT` JSON as `baseline` with history disabled. Comparisons require the same property, exact stay dates, party, market, currency and price metric.

Direct URL mode returns identity and offer data. Search-card fields such as amenities and review count may be empty in URL-only results. Google Maps short links and arbitrary hotel websites are not accepted.

### Example: family rates across several stays

This checks the same property 7, 30 and 90 days ahead for two adults and a seven-year-old. It can save three billable hotel/stay results. Dates and party are verified on the returned pages.

```json
{
  "searchQueries": [],
  "hotelUrls": ["https://www.google.com/travel/hotels/entity/ChgIie601P_pkqiBARoLL2cvMXYyN3JfcGsQAQ/prices"],
  "checkInOffsets": [7, 30, 90],
  "nights": 2,
  "adults": 2,
  "childrenAges": [7],
  "currency": "USD",
  "countryCode": "us",
  "maxResults": 3
}
```

For fixed stays, replace `checkInOffsets` with `stays`, an array of up to 10 objects containing `checkIn` and `checkOut` in YYYY-MM-DD format. Choose future dates. Do not combine date grids with the single exact-date inputs. Large grids need a longer timeout or smaller batches; `RUN_SUMMARY` records unfinished work.

### Example: track changes for a fixed trip

After a successful run, download **Snapshot for the next comparison**. On the next check of the same hotels and fixed dates, paste that complete JSON into `baseline`. With the JavaScript client, load a record from a previous run you own:

```javascript
const previous = await client.keyValueStore(previousRun.defaultKeyValueStoreId).getRecord('SNAPSHOT');
const nextRun = await client.actor('dig_stays/google-hotels-rates-ota-offers').call({
  ...sameFixedStayInput,
  baseline: previous.value
});
```

`changeStatus` is `new`, `not_comparable`, `unchanged`, `decreased` or `increased`. Only older, matching observations with known prices/currency are compared. A zero previous price has no percentage change. The metric is lowest collected provider offer when available, otherwise search headline. A provider or room mix can change, so this measures the displayed-price signal, not identical-room price movement.

### Deeper destination coverage

Set `maxPages` to 3 and `maxResultsPerQuery` to 50 to collect up to 50 unique hotels across three pages. The default remains one page and 20 hotels. Raise `maxOfferHotelsPerQuery` separately if you need detail on more than the first five hotels. Increase `maxResults` and the run timeout for larger batches.

Each result carries `searchPage` and `searchCoverage`. Coverage reports fetched pages, duplicate count, next-page availability and the stop reason: result limit, page limit, source exhausted, time limit, missing token, stalled pagination or page failure. Already-collected results survive a later-page error. Source ranking can move between requests; deduplication prevents repeated billing within the run, but cannot prove complete market coverage.

### Multiple rooms: planning estimates

Use exact property URLs for reliable intersections across room requirements. This example requests two adult-only rooms and one parent-and-child room. It fetches two distinct configurations and creates two billable hotel results per stay, not three. Top-level adult/child inputs are ignored when roomRequests is supplied.

```json
{
  "searchQueries": [],
  "hotelUrls": ["https://www.google.com/travel/hotels/entity/ChgIie601P_pkqiBARoLL2cvMXYyN3JfcGsQAQ/prices"],
  "checkInDaysFromToday": 30,
  "nights": 2,
  "roomRequests": [
    {"label":"Couples","adults":2,"childrenAges":[],"quantity":2},
    {"label":"Parent and child","adults":1,"childrenAges":[7],"quantity":1}
  ],
  "currency":"USD",
  "maxResults":2
}
```

The **Multi-room estimates** output sums independently quoted rooms. It also lists estimates from providers appearing in every configuration. All estimates have `jointAvailabilityVerified:false`: multiplying a single-room price does not prove that several rooms are available at that rate. Missing configurations or missing provider prices produce an incomplete plan with a null total. For destination searches, enable provider enrichment and collect enough results to find properties common to every configuration.

### Comparing room offers responsibly

`roomComparisons` groups matching room names, bed descriptions, guest capacity, displayed cancellation wording, meal text and currency across providers. Only groups with all those attributes have a numerical comparison spread. Groups with missing terms are labelled `incomplete_attributes`, with null comparison totals. Matching text is evidence for review, not a shared supplier room ID; `verifiedSameRoom`, `checkoutVerified` and `taxBasisVerified` remain false. Hidden member conditions, different tax treatment or live inventory changes still need checking on the booking site.

### What the output means

| Field | Meaning |
| --- | --- |
| `entityId`, `name`, `googleHotelsUrl` | Property identity and reproducible source link |
| `pricePerNight`, `priceSource` | Search headline in search mode; lowest collected provider price in URL mode |
| `currency`, `requestedCurrency`, `currencyMatched` | Actual parsed currency versus the requested currency; no automatic conversion |
| `checkIn`, `checkOut`, `nights` | Requested stay dates; mismatched returned dates cause an error |
| `datesVerified`, `dateVerificationMethod` | Returned English date controls match requested month, day and weekday; year is encoded in the request but not separately visible in those controls |
| `adults`, `childrenAges`, `occupancyVerified` | Verified one-room party; requested adult count and child ages must match Google’s controls |
| `rooms[]`, `roomCount`, `roomsStatus` | Source room names, beds, capacity, cancellation wording, meal text, provider rates and redirects where exposed |
| `address`, `website` | Google’s directions destination text (may include the hotel name) and official website when exposed |
| `changeStatus`, `previousPrice`, `priceChange`, `priceChangePercent` | Change against the supplied snapshot; new or incompatible observations are not assigned invented changes |
| `available`, `availabilitySource` | `true` means a price was displayed; null means unknown. It is not a booking guarantee or a complete availability calendar |
| `rating`, `reviewCount`, `hotelClass`, `amenities` | Property metadata available on destination search cards |
| `latitude`, `longitude` | Coordinates when found; these may be null and are not independently geocoded |
| `offers[]` | Provider, nightly rate, displayed stay total, currency, sponsored flag, official-site flag and booking redirect |
| `providerCount`, `organicOfferCount` | Distinct provider names and number of collected organic offers |
| `lowestOfferProvider`, `lowestOfferPricePerNight` | Lowest collected nightly offer in `comparisonCurrency` |
| `lowestOfferStayTotal` | Total attached to that same offer, if displayed; never invented by multiplying the nightly rate |
| `officialSitePricePerNight` | Lowest collected offer explicitly labelled Official Site |
| `offerPriceSpread` | Highest minus lowest collected nightly rate in the same currency |
| `offersStatus`, `offersError`, `warnings` | Whether enrichment succeeded, failed, was not requested, found no offers, or was skipped because of the enrichment limit |
| `scrapedAt`, `offersScrapedAt` | UTC collection timestamps |

**These are displayed-price comparisons, not verified room-rate parity.** Providers can quote different room types, refund rules, meal plans, member deals and tax treatments. `comparisonBasis` is `displayed_offers_not_room_matched`. A lower displayed rate is a lead for investigation, not proof that the hotel is being undercut for the identical product.

`offerCount` can exceed `providerCount`: sponsored and organic placements or different prices from one provider are retained. Exact repeated offer cards are removed. Duplicate hotels across input searches/URLs are saved and charged once per property, stay and actual currency within the run; the first result is retained.

### Sample result

Abbreviated real test output, collected on 25 September 2026. Prices are historical examples, not current quotes. The full JSON also includes provider offers, room details, timestamps and warnings.

```json
{
  "name": "Paradox Singapore",
  "entityId": "ChgIie601P_pkqiBARoLL2cvMXYyN3JfcGsQAQ",
  "checkIn": "2026-11-10",
  "checkOut": "2026-11-12",
  "adults": 2,
  "childrenAges": [
    7
  ],
  "occupancyVerified": true,
  "lowestOfferPricePerNight": 277,
  "comparisonCurrency": "USD",
  "roomCount": 5,
  "changeStatus": "new"
}
```

### Export and API

- **Dataset:** one row per hotel, with nested `offers` and `rooms` for JSON consumers. Use the comparison table for quick review.
- **OFFERS.csv:** one row per collected provider offer, including hotel ID, dates, adults, child ages, prices, currency and links. No extra charge for this export.
- **ROOMS.csv:** one row per exposed room offer. Cancellation wording and meal text are preserved; null means not exposed.
- **CHANGES.csv:** one row per hotel/stay, with previous price and delta when a compatible baseline exists.
- **SNAPSHOT:** portable JSON for a manual `baseline`, also stored automatically when `monitorHistory` is enabled. Watchlist history is separated by account.
- **EVENTS / EVENTS.csv:** qualifying price changes and budget transitions. Connect your own integration for notifications.
- **AUDIT:** accommodation audit observations, your optional row references, price-check evidence and failed jobs.
- **ROOM\_COMPARISONS:** room-offer groups with matching visible labels, matched/missing fields and explicit unverified checkout/tax flags. Unknown terms never establish equality.
- **ROOM\_PLANS:** independent room quotes, quantities, combined estimates and same-provider alternatives; incomplete plans do not receive a total.
- **RUN\_SUMMARY:** saved counts, duplicate count, failed inputs, currency mismatches, offer failures and early-stop warnings. A partially successful run may have Apify status SUCCEEDED; inspect the summary's `status` too.

JavaScript example using the official Apify client:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('dig_stays/google-hotels-rates-ota-offers').call({
  searchQueries: ['hotels in New York'],
  checkInDaysFromToday: 30,
  nights: 2,
  maxResultsPerQuery: 10,
  maxOfferHotelsPerQuery: 10,
  currency: 'USD',
  countryCode: 'us'
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('RUN_SUMMARY');
console.log(items, summary.value);
```

Use Apify's API tab for your own authenticated endpoint. Keep API tokens private.

### Pricing and cost examples

| Apify subscription tier | Price per 1,000 hotel results |
| --- | ---: |
| Free | $2.50 |
| Bronze | $2.25 |
| Silver | $2.00 |
| Gold, Platinum, Diamond | $1.75 |

Platform usage is included in this Actor's event pricing. Provider-offer enrichment, room details, change calculations and CSV exports do not add a separate result fee. **One hotel on three stay dates counts as three hotel results. Two different room configurations on those dates count as six. Quantity within an identical room configuration reuses the quote and does not multiply result charges.** There is also an Actor-start fee of **$0.00005 at the default 512 MB memory**; Apify scales that event with memory, with a minimum of one event.

At the Free-tier rate, 20 saved hotels cost approximately **$0.05005**, including the default start fee. Checking 20 exact properties once daily for 30 days is approximately **$1.5015**, assuming one run each day, all 20 results returned and default memory. These are Actor charges, not a promise of available account credit.

Failed or empty searches do not incur hotel-result events, but the start fee still applies. Use `maxResults` and Apify's maximum charge setting to bound spending. A hotel with successful base data and failed optional offer enrichment still counts as one result; check `offersStatus`.

### Limits and troubleshooting

- **Coverage:** up to 10 pages and 200 unique hotels per query, 10 queries and up to 20 direct hotel URLs, evaluated across up to 10 stays and 5 room configurations per run. The total default cap is 200 hotel/stay results; the configurable maximum is 2,200. Pagination follows Google’s returned next-page tokens and removes overlapping properties. It is not an exhaustive city census: ranking changes, page/result caps, source failures and time limits can truncate coverage. Inspect searchCoverage and RUN\_SUMMARY.
- **Currency:** Currency is encoded in Google’s travel state as well as the request URL. Google may still return a different currency or omit prices. Always use the actual `currency` and check `currencyMatched` before arithmetic. Never relabel a returned price. Changing `countryCode` is a preference, not a guarantee of geographic serving location.
- **Language:** pages are requested and parsed in English for consistency. Destination names may use local text. The legacy `language` field is normalized to `en`.
- **Occupancy:** 1–6 adults, up to 4 children aged 0–17, and no more than 6 guests in one room. Ages 0–1 share Google’s age band and are reported as 1. For multiple rooms, roomRequests collects separate single-room quotes and produces planning estimates. Google’s guest picker does not confirm a combined multi-room booking; joint availability and checkout totals remain unverified. Requested guests must be confirmed by the returned page.
- **Offers:** source coverage varies, may include membership conditions, and is not guaranteed to include every provider. Redirect links can expire. Room offers are collected when exposed in the initial page, not guaranteed for every property/provider. No final checkout validation, complete room inventory, cross-provider room matching or tax normalization. A breakfast label does not necessarily mean breakfast is included.
- **Dates:** use real future dates and stays of 1–30 nights. The visible date controls must match before rates are saved. A request is not treated as confirmation on its own.
- **Errors:** transient network/HTTP failures are retried with a timeout. Blocks and missing source structures are reported. If every search fails, the run fails instead of reporting a misleading success.
- **Large jobs:** the Actor stops before its time or charge budget is exhausted and reports unfinished work in `RUN_SUMMARY`. Split larger batches or increase the run timeout.

### Support and feedback

Open an issue on this Actor with the input, run ID, expected result and affected output fields. Remove tokens and private information before posting. Reports with a reproducible hotel URL and stay dates are particularly useful for investigating source changes.

### Changelog

- **1.5:** optional automatic watchlist history; price thresholds and budget transitions; JSON/CSV events; accommodation audit export with user references and explicit evidence. History remains isolated by account and watchlist.
- **1.3:** token-based pagination with coverage reporting and partial recovery; independent multi-room planning estimates; room comparison evidence with missing-field safeguards.
- **1.2:** verified adult/child occupancy; currency state encoding; multi-stay date grids; room names and displayed cancellation/meal terms; portable snapshots and historical price deltas; room/change CSV exports.
- **1.1:** organic booking offers; official-site flags; exact-property URL monitoring; same-currency comparison fields; flat offers CSV; date checks; explicit enrichment errors; retries and request timeouts; duplicate and charge-limit handling; accurate fatal-run status.
- **1.0:** destination search with stay dates, headline rates and sponsored offer extraction.

# Actor input Schema

## `searchQueries` (type: `array`):

Up to 10 destination or hotel-name searches. Use maxPages for deeper coverage, or hotelUrls for exact-property monitoring.

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

YYYY-MM-DD. Leave blank to use the relative date below.

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

YYYY-MM-DD. Supply both exact dates, or leave both blank.

## `checkInDaysFromToday` (type: `integer`):

Keeps saved tasks evergreen. Ignored when exact dates are supplied.

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

Length of stay when relative dates are used.

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

The Actor requests this currency and reports the currency actually returned by Google.

## `countryCode` (type: `string`):

Two-letter market code used by Google, such as us, sg or gb.

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

Output is normalized to English for reliable parsing. Kept for compatibility; other values are normalized to en. Destination searches can still use local names.

## `maxResultsPerQuery` (type: `integer`):

Up to 200 unique hotels per query and stay. Set maxPages above 1 to go beyond the first page. Pagination deduplicates overlapping results.

## `includeOffers` (type: `boolean`):

Visits the price comparison page for the first hotels and adds Booking.com, Expedia, Hotels.com and other available offers.

## `maxOfferHotelsPerQuery` (type: `integer`):

Enrich this many unique hotels across all collected pages of each query. More detail takes longer. Direct URLs always collect offers.

## `hotelUrls` (type: `array`):

Optional, up to 20 URLs copied from googleHotelsUrl in a previous run. Use https://www.google.com/travel/hotels/entity/ENTITY\_ID/prices. Always collects provider offers for these properties; requested dates replace the URL dates.

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

Total saved hotel/stay results across all dates, queries and URLs. Each unique hotel/stay/party/currency is billed once per run. Default 200. Also respects the run charge and time limits.

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

Adults in one room. The returned page must confirm the requested party before rates are saved.

## `childrenAges` (type: `array`):

Ages 0–17, e.g. \[7,12]. Up to 4 children and 6 total guests in one room. Ages 0–1 use Google’s 0–1 band.

## `stays` (type: `array`):

Optional, up to 10 objects such as \[{"checkIn":"2026-12-10","checkOut":"2026-12-12"}]. Do not combine with exact single dates or checkInOffsets. Each saved hotel per stay is one billable result.

## `checkInOffsets` (type: `array`):

Optional, up to 10 day offsets, e.g. \[7,30,90]. Uses nights for each stay. Do not combine with stays or exact single dates. Multiplies the possible result count.

## `baseline` (type: `object`):

Optional manual SNAPSHOT comparison. Leave blank when automatic watchlist history is enabled. Matches exact property, dates, occupancy, market, currency and price metric.

## `maxPages` (type: `integer`):

Follow Google’s next-page tokens, up to 10 pages per query/stay/room configuration. Also raise maxResultsPerQuery above 20. Stops on missing/repeated tokens, time limits or errors; see searchCoverage.

## `roomRequests` (type: `array`):

Optional, up to 5 different configurations and 10 total rooms. Example: \[{"adults":2,"childrenAges":\[],"quantity":2,"label":"Couples"},{"adults":1,"childrenAges":\[7],"quantity":1,"label":"Parent and child"}]. Overrides top-level adults/childrenAges. Collects independent single-room quotes and estimates combined costs; does NOT verify joint stock or a multi-room checkout. Each hotel/stay/configuration is one billable result; quantity multiplies the estimate, not the bill.

## `monitorHistory` (type: `boolean`):

Automatically compare with this account’s previous watchlist observations. Keeps the last 30 run snapshots in a named store. First run establishes history. Exact dates must match; rolling stays are different trips.

## `watchlistName` (type: `string`):

Use a distinct name per project, e.g. london-crew. Reuse it to share history between your tasks. Defaults to this saved task, or default for runs without a task. Do not overlap runs of one watchlist.

## `minimumChangePercent` (type: `number`):

Price-drop and price-rise events require this percentage AND the amount threshold. Set 0 to disable this threshold. All price comparisons remain in CHANGES.csv.

## `minimumChangeAmount` (type: `number`):

Absolute nightly change in your preferred currency. Used together with the percentage threshold.

## `budgetPerNight` (type: `number`):

Flag displayed quotes at or below this amount in the requested currency. Budget events fire on first match and re-entry, not every unchanged run with history enabled. Taxes and checkout stock are unverified.

## `auditReferences` (type: `object`):

Map Google entity IDs to your spreadsheet IDs, e.g. {"GOOGLE\_ENTITY\_ID":"row-12"}. AUDIT output includes price\_check, dates, occupancy, coordinates and source evidence. Google IDs are not Airbnb room IDs.

## Actor input object example

```json
{
  "searchQueries": [
    "hotels in Singapore"
  ],
  "checkInDaysFromToday": 30,
  "nights": 2,
  "currency": "USD",
  "countryCode": "us",
  "language": "en",
  "maxResultsPerQuery": 20,
  "includeOffers": true,
  "maxOfferHotelsPerQuery": 5,
  "hotelUrls": [],
  "maxResults": 200,
  "adults": 2,
  "childrenAges": [],
  "stays": [],
  "checkInOffsets": [],
  "maxPages": 1,
  "roomRequests": [],
  "monitorHistory": false,
  "minimumChangePercent": 5,
  "minimumChangeAmount": 0
}
```

# Actor output Schema

## `hotelRates` (type: `string`):

All hotel price rows saved during this run.

## `offerCsv` (type: `string`):

One row per collected offer, ready for spreadsheet analysis. No separate result charge.

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

Failed searches, saved count, duplicates, currency mismatches and enrichment errors.

## `roomCsv` (type: `string`):

Source room names, beds, displayed cancellation terms, meal text and prices where exposed.

## `priceChanges` (type: `string`):

All comparisons against automatic watchlist history or a manual baseline, for the same stay, guests, market, currency and metric.

## `snapshot` (type: `string`):

Current observations, also saved to private watchlist history when enabled. Can be used as a manual baseline.

## `roomComparisons` (type: `string`):

Groups identical displayed room attributes across providers and flags missing evidence. Not certified room parity.

## `roomPlans` (type: `string`):

Independent room quotes multiplied by quantities, with missing configurations and same-provider alternatives. Joint availability is unverified.

## `events` (type: `string`):

Threshold-based price changes and budget transitions. Output only; connect your own integration for notifications.

## `eventsCsv` (type: `string`):

Only qualifying events, ready for spreadsheets.

## `audit` (type: `string`):

Reference IDs, nightly quotes, unknown availability, coordinates and source evidence. Includes failed jobs.

# 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 = {
    "searchQueries": [
        "hotels in Singapore"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dig_stays/google-hotels-rates-ota-offers").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 = { "searchQueries": ["hotels in Singapore"] }

# Run the Actor and wait for it to finish
run = client.actor("dig_stays/google-hotels-rates-ota-offers").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 '{
  "searchQueries": [
    "hotels in Singapore"
  ]
}' |
apify call dig_stays/google-hotels-rates-ota-offers --silent --output-dataset

```

## MCP server setup

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

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/7tCi7Q9dowRTTMtAO/builds/I7itTiVQQmE4gR3ES/openapi.json
