# Hotel Rate Monitor: Google Hotels OTA Prices & Parity (`steady-scrape/hotel-rate-monitor`) Actor

Track hotel prices on Google Hotels from every booking site (Booking.com, Expedia, Agoda, the hotel's own site) for your dates. Rate parity, a 12-month price calendar and, on scheduled runs, only the price changes since the last run.

- **URL**: https://apify.com/steady-scrape/hotel-rate-monitor.md
- **Developed by:** [SteadyScrape](https://apify.com/steady-scrape) (community)
- **Categories:** Travel, E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.50 / 1,000 hotel + stay checkeds

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

## Hotel Rate Monitor: Google Hotels OTA Prices & Parity

**Hotel Rate Monitor** checks the prices Google Hotels shows for the hotels and dates you choose, from **every booking site** at once: Booking.com, Expedia, Agoda, Hotels.com, Trip.com, Vio.com and the hotel's own website. It tells you which site is cheapest, whether an OTA is **undercutting the hotel's direct rate (rate parity)**, and, when it runs on a schedule, **only what changed since the last run**. It's built for revenue managers, hotel owners, travel agencies, deal sites and analysts. You pay per hotel and date checked, never for hotels that weren't found, sold-out dates or failed checks. Prices start at **$0.006 per hotel and date checked** ($6 per 1,000 checks, less on paid Apify plans).

Prices are sourced from Google via the DataForSEO API, not a headless browser. That means no captchas, no proxies to rotate and no broken runs when Google changes its page layout. Reliability is the main point of this Actor.

### What you can do with it

- **Rate shopping for your comp set:** check your hotel and 5-20 competitors for the next 30, 60 and 90 days every morning, and get a sheet of every site's price.
- **Rate parity monitoring:** catch an OTA selling your rooms below your direct rate (`parityStatus: undercut`), with the site name and the percentage.
- **Price-change alerts:** schedule it with a Monitor ID and get only the change rows (price up/down per site, new lowest site, sold out, parity break) in Slack, email or a webhook.
- **Seasonality and pricing gaps:** one price calendar per hotel shows Google's lowest nightly price for every date across 1-12 months.
- **Market research:** search "hotels in Lisbon" and get the top results with stars, ratings and Google's lowest price, or their full rate grid.

### How to use it

1. Add hotels by name (with the city, e.g. *Hotel Mar Rey Tamarindo*), by Google Hotels URL (the one with `/entity/` in it) or by hotel id. Or type a search like *hotels in Tamarindo*. A name is matched to its Google Hotels listing once ($0.002) and remembered for 90 days; URLs and ids skip the lookup. Matching is strict: if no listing clearly carries the hotel's own name, you get a free *no confident match* row instead of a guess.
2. Pick check-in dates. Relative dates such as `+7d`, `+30d` or `+2w` move forward with every scheduled run. Then set nights, guests and currency.
3. Optional: add a **Monitor ID** (any name, e.g. `tamarindo-compset`) and schedule the task daily. The first run saves a baseline, and each later run reports what changed.

#### Example input

```json
{
  "hotels": ["The Beach Bungalows Tamarindo", "Ten North Tamarindo Beach Hotel", "Tamarindo Diria"],
  "location": "Costa Rica",
  "checkInDates": ["+14d", "+30d"],
  "nights": 2,
  "adults": 2,
  "currency": "USD",
  "monitorId": "tamarindo-compset",
  "calendarRange": "none"
}
```

### Output

Every row has a `recordType`. The dataset has ready-made views: **Rates per hotel & stay**, **Price per booking site**, **Changes since last run**, **Price calendar**, **Search listings** and **Not found / failed**. Export to JSON, CSV, Excel or Google Sheets.

| hotelName | checkIn | nights | lowestPrice | lowestSource | officialPrice | parityStatus | undercutByPct | medianPrice | sourceCount | observedAt |
|---|---|---|---|---|---|---|---|---|---|---|
| Wyndham Tamarindo | 2026-11-11 | 1 | 144 | Clicktrip.com | 174 | undercut | 17.24 | | 19 | 2026-10-28T19:01:15Z |
| The Hoxton, Shoreditch | 2026-11-27 | 1 | £285 | Super.com | £324 | undercut | 12.04 | | 27 | 2026-10-28T19:23:40Z |
| Hotel Emporio Cancún | 2026-11-27 | 3 | 94 | Traveluro | 100 | undercut | 6.0 | 116.5 | 26 | 2026-10-28T19:24:02Z |
| Tamarindo Diria | 2026-11-11 | 1 | 189 | Tamarindo Diria (official) | 189 | official_cheapest | 0 | | 17 | 2026-10-28T19:01:41Z |

*(Rows from test runs on 2026-10-28, trimmed; observedAt values are illustrative. Prices are per night in the run's currency.)*

**Every price carries a timestamp.** `observedAt` is the ISO 8601 UTC time (e.g. `2026-10-28T19:01:15Z`) when that price snapshot was fetched from Google. It's on every `rate`, `rate_source`, `calendar`, `listing` and `change` row, and on each entry of the `rates` array. A calendar row's dates all come from one snapshot, so its `observedAt` covers its whole `prices` list. `fetchedAt` is when the run received the result.

`medianPrice` and `lowestVsMedianPct` help you spot an unusually low offer, for example one site at -60% versus the median, which is often a members-only or non-refundable rate worth checking.

Each `rate` row also has a `rates` array with one entry per booking site: `source`, `sourceDomain`, `pricePerNight`, `totalPrice`, `currency`, `isAd`, `isOfficialSite`, `freeCancellationUntil`, `bookingUrl`, `priceRank` and `observedAt`. Set **Output format** to *one row per booking site* to get a flat table instead.

A **change** row, from a scheduled run with a Monitor ID:

```json
{"recordType": "change", "changeType": "price_down", "hotelName": "Ten North Tamarindo Beach Hotel", "checkIn": "2026-12-12",
 "checkOut": "2026-12-13", "source": "Booking.com", "oldPrice": 145, "newPrice": 116, "changePct": -20, "currency": "USD",
 "previousCheckAt": "2026-10-28T19:23:24Z", "detectedAt": "2026-10-28T19:29:36Z", "observedAt": "2026-10-28T19:29:21Z",
 "monitorId": "tamarindo-compset"}
```

Google rotates sponsored listings between searches, so a site that shows up only as an ad never counts as appearing or disappearing. Its price moves are still reported. Change types: `price_up`, `price_down`, `new_source`, `source_removed`, `new_lowest_source`, `parity_break`, `parity_restored`, `sold_out`, `back_available` and `calendar_changes` (one row per hotel listing every changed date).

The run summary (key-value store record `OUTPUT`) lists the checks done, the hotels not found, sold-out stays, failed checks, changes and the amount charged.

### Pricing

Pay per event. You only pay for results: hotels not found, sold-out or unbookable stays, provider errors and invalid input are free.

| Event | Price |
|---|---|
| Run start (only when the run returns data) | $0.01 |
| Hotel + stay checked (all booking sites) | $0.006 standard (less on paid plans, see below) / $0.012 live |
| Price calendar (per hotel, only with 7+ priced dates) | $0.01 standard (less on paid plans, see below) / $0.025 live |
| Hotel name matched (per name newly matched to a Google Hotels listing) | $0.002 |
| Search listing (per hotel, without rates) | $0.001 |
| Change detected (per change row) | $0.002 |

Paid Apify plans get standard-speed checks and calendars cheaper, applied automatically. Hotel + stay checked: $0.006 on the Apify Free plan, $0.0054 on Starter (Bronze), $0.0048 on Scale (Silver), $0.0045 on Business (Gold) and above. Price calendar: $0.01 on Free, $0.009 on Starter, $0.008 on Scale, $0.0075 on Business and above. Live speed, run starts, name matches, search listings and changes cost the same on every plan.

Name matches are remembered for 90 days, so repeat runs with the same names don't pay the lookup again. Names that match nothing, or have no confident match, are free and remembered for 7 days (no repeat lookups); Google Hotels URLs and hotel ids never need a lookup.

*Worked example: a 10-hotel comp set (added by name) × 3 check-in dates, checked daily at standard speed for 30 days = 900 checks × $0.006 = $5.40, plus 30 run starts × $0.01 = $0.30, plus about 5 change rows a day (150 × $0.002 = $0.30), plus 10 name matches in the first run (10 × $0.002 = $0.02). That's about **$6.02 a month** on the Free plan, or about $4.67 on Business (Gold). A one-off search of 20 hotels without rates costs $0.03 ($0.01 + 20 × $0.001).*

**Standard** speed (the default) is the cheapest and usually finishes in under a minute; busy periods can take a few minutes. **Live** returns in seconds and costs more. If a check waits in the standard queue longer than **Max standard queue wait** (default 10 minutes, up to 20), it moves to the provider's faster priority queue automatically, still billed as a normal standard check. Standard checks never switch to live; a check that still doesn't finish is listed as failed, not charged, and retried on the next run. Hotel-name lookups always run on the standard queue, even when you pick live speed (expect roughly 20 seconds extra the first time a name is looked up; repeat runs use the remembered match). A lookup that doesn't finish within the queue wait is listed as failed, not charged, and retried on the next run.

#### Free Apify plan limits

On the free Apify plan, runs use **standard speed only** (live is switched to standard), stuck checks don't move to the priority queue, and each run checks at most **50 hotel/date combinations** and looks up at most **50 new hotel names**. Upgrade to a paid Apify plan for live speed and bigger runs.

#### Limits

Up to 200 hotels and 5,000 hotel/date checks per run (50 on the free plan). Memory: 256 MB by default, max 512 MB (the Actor uses under 100 MB).

### How it compares

- **Reliability:** no browser, no proxies and no scraping of Google's page, so Google layout changes don't break runs. Failed checks are never charged and are listed in the run so you can see them.
- **Monitoring built in:** most Google Hotels scrapers return a snapshot. This Actor remembers the last run for each Monitor ID and returns only the differences, with parity breaks flagged.
- **What it doesn't do:** room types and rate plans (Google shows one price per site), guest review text, or booking. For room-level rates, use the OTA itself.

### FAQ

**Are the prices per night or for the whole stay?** `pricePerNight` is the nightly price as Google shows it for that market (usually including taxes and fees in most countries). `totalPrice` is that price × nights.

**Why do prices differ from what I see on Google?** Prices depend on the searcher's country (**Market**), currency, guests and time. Set Market to where your guests book from.

**What does "\[ad]" mean?** Google shows some booking sites as sponsored listings. They're real public offers, so they count for parity. Turn off *Include sponsored prices* to drop them.

**A hotel wasn't found, or says "no confident match".** A name is only accepted when a Google Hotels listing clearly carries the hotel's own name (words like *hotel*, *resort* or the town don't count), so the Actor never silently swaps in a neighbouring hotel. The row shows the closest listing it rejected (`closestMatch`). Check the spelling, add the city, or paste the Google Hotels URL. You aren't charged for it. The unmatched name is remembered for 7 days, so fix the spelling or Market rather than re-running the same name.

**How far ahead can I check?** Up to about 330 days, as on Google Hotels.

**Is scraping Google Hotels legal?** This Actor doesn't scrape Google's pages. Prices are sourced from Google via the DataForSEO API, and it returns public prices only, with no personal data. See *Data source & terms* below, and check your own use case with your legal team.

### Integrations

- **Schedules:** save a task with a Monitor ID and run it daily or hourly from Apify Schedules.
- **API & webhooks:** start runs and read results with the Apify API, or fire a webhook when a run finishes. Use *Output only changes* to make alerts easy.
- **Make, Zapier, n8n, Google Sheets, Slack:** use the Apify integrations to push change rows wherever your team works.
- **AI agents (MCP):** call it from Claude, ChatGPT or Cursor through the Apify MCP server ("check Booking.com vs direct for these 5 hotels next weekend").

### Data source & terms

Prices and other data are sourced from Google via the DataForSEO API. The service is not affiliated with or endorsed by Google. Data is provided for monitoring and analytics of properties the customer selects, and may not be used to create a service that competes with general search engines. The service is not offered to customers in Russia, Belarus, Syria, North Korea, or occupied Crimea.

### Notes

Not affiliated with or endorsed by Google, Booking.com, Expedia or any other booking site (see *Data source & terms* above). Prices are informational and can change at any time. Always confirm on the booking site. This Actor is published by **SteadyScrape** (Apify user `steady-scrape`) and is not related to the similarly named `steadyscrape` or `steady_scrape` accounts.

***

Built and maintained by **SteadyScrape**: reliable data Actors that don't break. Questions or requests? Open an issue on the Issues tab.

### More from SteadyScrape

- [Google Maps Leads: No Website, Unclaimed Profiles](https://apify.com/steady-scrape/google-maps-business-segments): local hospitality and hotel lead lists from Google Maps, filtered to no website, unclaimed profile, low rating or few reviews.
- [Google Trends API](https://apify.com/steady-scrape/google-trends-api): search interest for destinations and travel topics over time, with breakout queries and related topics.
- [Company Data by Domain](https://apify.com/steady-scrape/company-profile-by-domain): name, logo, description, industry and social links for hotel and OTA websites, from each company's own site.
- [AI Visibility & Share of Voice Tracker](https://apify.com/steady-scrape/ai-brand-visibility-tracker): see whether ChatGPT and Perplexity mention, rank and cite your hotel brand versus competitors.

See every **SteadyScrape (steady-scrape)** Actor on [our Apify profile](https://apify.com/steady-scrape).

### Acceptable use

Prices and other data are sourced from Google via the DataForSEO API. The service is not affiliated with or endorsed by Google. Data is provided for monitoring and analytics of properties the customer selects, and may not be used to create a service that competes with general search engines. The service is not offered to customers in Russia, Belarus, Syria, North Korea, or occupied Crimea.

You may not use results from this service to build or operate a search engine, or any product or service that competes with or harms the business of search engine providers.

— SteadyScrape

# Actor input Schema

## `hotels` (type: `array`):

Hotel names (add the city for accuracy, e.g. 'Hotel Mar Rey Tamarindo'), Google Hotels URLs (the ones with /entity/ in them) or Google hotel ids (ChcI...). Up to 200 per run. A name is matched once ($0.001 per newly matched name) and remembered for 90 days; names with no match are free and remembered for 7 days. URLs and ids need no lookup.

## `searchQuery` (type: `string`):

E.g. 'hotels in Tamarindo' or 'boutique hotels near Times Square'. Adds the top results (Max hotels from search) to the list above.

## `maxHotels` (type: `integer`):

How many hotels to take from the search (organic results first).

## `fetchRates` (type: `boolean`):

On: each hotel found by the search gets a full rate check (all booking sites). Off: you get one cheap listing row per hotel (name, stars, rating, Google's lowest price).

## `stars` (type: `array`):

Only these star ratings in the search, e.g. 4, 5.

## `minRating` (type: `string`):

Only hotels with at least this Google guest rating in the search.

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

Order of the search results.

## `checkInDates` (type: `array`):

One or more check-in dates: YYYY-MM-DD or relative to the run day, e.g. +7d, +30d, +2w. Relative dates keep a schedule rolling forward. Each hotel is checked for each date.

## `sweepDays` (type: `integer`):

0 = off. N = check N consecutive check-in dates starting from the first date above (e.g. 14 = the next two weeks of arrivals).

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

Length of each stay. Total price = nightly price x nights.

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

Adults per room (max 6 guests in total).

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

One age (0-17) per child, e.g. 8, 13. Max 6 guests in total.

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

3-letter code: USD, EUR, GBP, MXN, CRC...

## `location` (type: `string`):

Where the traveller searches from, which changes the prices and booking sites Google shows. A country ('United States', 'United Kingdom', 'Costa Rica') or a city in Google's format ('London,England,United Kingdom'). For name lookups, use the hotel's country or city.

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

Language code for hotel names and site titles, e.g. en, es, fr.

## `calendarRange` (type: `string`):

Adds one calendar row per hotel with Google's lowest 1-night price for every date in the range, starting at the first check-in date. Great for spotting peak dates and pricing gaps.

## `includeAds` (type: `boolean`):

Google shows some booking sites as ads. They are real public offers; they're marked isAd so you can filter them.

## `parityTolerancePct` (type: `integer`):

A booking site counts as undercutting the hotel's own site when it's cheaper by more than this percentage.

## `outputFormat` (type: `string`):

Nested (one row per hotel and date) or flat (one row per booking site).

## `monitorId` (type: `string`):

Any name, e.g. 'tamarindo-compset'. Runs with the same Monitor ID compare against the previous run and add change rows: price up/down per booking site, new or removed sites, new lowest site, parity breaks, sold out. Leave empty for one-off checks.

## `onlyChanges` (type: `boolean`):

With a Monitor ID: skip the full rate rows and output only change rows (ideal for alerts via webhooks, Slack, email). Checks are still charged.

## `changeThresholdPct` (type: `integer`):

0 = report every price change.

## `reportSourceChanges` (type: `boolean`):

Off: report only price moves, new lowest site, parity and sold-out changes. Sites that Google shows only as ads never count as appearing or disappearing (Google rotates ads between searches).

## `speed` (type: `string`):

Standard is the cheapest. Live returns each check in seconds and is billed as a live check. New hotel-name lookups always use the standard queue (about 20 s), whatever the speed. Live speed needs a paid Apify plan (free-plan runs use standard).

## `maxChecks` (type: `integer`):

Safety cap on hotel/date combinations per run. On the free Apify plan, runs are capped at 50 checks and 50 new hotel-name lookups.

## `maxQueueWaitMinutes` (type: `integer`):

Checks still waiting in the standard queue after this are moved to the provider's priority queue automatically, at no extra charge to you (still billed as a normal standard check). Never switches to live.

## Actor input object example

```json
{
  "hotels": [
    "The Beach Bungalows Tamarindo",
    "Ten North Tamarindo Beach Hotel",
    "Tamarindo Diria"
  ],
  "maxHotels": 10,
  "fetchRates": true,
  "minRating": "",
  "sortBy": "relevance",
  "checkInDates": [
    "+14d",
    "+30d"
  ],
  "sweepDays": 0,
  "nights": 1,
  "adults": 2,
  "currency": "USD",
  "location": "United States",
  "language": "en",
  "calendarRange": "none",
  "includeAds": true,
  "parityTolerancePct": 1,
  "outputFormat": "hotel",
  "onlyChanges": false,
  "changeThresholdPct": 0,
  "reportSourceChanges": true,
  "speed": "standard",
  "maxChecks": 200,
  "maxQueueWaitMinutes": 10
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `calendar` (type: `string`):

No description

## `summary` (type: `string`):

Totals, provider timing and charges.

# 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 = {
    "hotels": [
        "The Beach Bungalows Tamarindo",
        "Ten North Tamarindo Beach Hotel",
        "Tamarindo Diria"
    ],
    "checkInDates": [
        "+14d",
        "+30d"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("steady-scrape/hotel-rate-monitor").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 = {
    "hotels": [
        "The Beach Bungalows Tamarindo",
        "Ten North Tamarindo Beach Hotel",
        "Tamarindo Diria",
    ],
    "checkInDates": [
        "+14d",
        "+30d",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("steady-scrape/hotel-rate-monitor").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 '{
  "hotels": [
    "The Beach Bungalows Tamarindo",
    "Ten North Tamarindo Beach Hotel",
    "Tamarindo Diria"
  ],
  "checkInDates": [
    "+14d",
    "+30d"
  ]
}' |
apify call steady-scrape/hotel-rate-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steady-scrape/hotel-rate-monitor"
        }
    }
}
```

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/Go5fqbAEaJDRA25RS/builds/uxFmmMks3P0h0Vw2Y/openapi.json
