kestrel actors docs

kestrel actors › Hotel & flight prices

Hotel Price Tracker — Google Hotels Price History & Alerts

Hotel price tracker built on Google Hotels prices: for every property and check-in date in a window you choose, one row with the lowest rate across every booking site Google lists — Booking.com, Expedia, Agoda, Trip.com, the property's own site — the source that has it, its booking link, the official-site rate next to it, and the date's rank within the window. Put it on a daily schedule with relative dates and it becomes hotel price tracking with a history you own; set maxPrice and it becomes a hotel price alert that only delivers (and only bills) the dates under your target. It is the Google Hotels price tracker Google's own "track prices" button does not give you: your threshold, every source, every date, as rows. No API key, no login, no browser.

Run Hotel Price Tracker on Apify →

Hotel price tracker pricing: what one row costs

EventWhat it isPriceCharged for
rateRate row$0.004One property and check-in date: the lowest nightly rate and stay total across every booking source Google Hotels lists that day, the cheapest source with its booking link, the official-site rate, free-cancellation flag and the date's rank in the window. Dates with no rate, dates a maxPrice filter drops, unresolved names, duplicates and status rows are free.

Pay per event: one rate event per delivered row, at the price on this page's pricing tab. Two properties over thirty check-in dates is sixty rows — cents. A daily alert run with maxPrice bills only the dates under the target, which most days is none. Dates with no rate, names that do not resolve, a property listed twice, rows the spending limit refused and status rows are free, and a property and date is billed once per run. Apify's platform usage is a fraction of a cent per run: one search per name, one offers call per property and date.

Compared with the parent Google Hotels Prices Scraper, which bills one row per source, a rate row is one event for the whole comparison on that date — the cheap way to track hotel prices over many dates.

Hotel price tracker input reference

Every field of Hotel Price Tracker — input, straight from the actor's input schema.

FieldTypeDefaultWhat it does
hotelsarrayThe properties to track: Google Hotels URLs (https://www.google.com/travel/hotels/entity/ChkI…) or bare entity tokens, one per line. Find a URL by opening the property on google.com/travel/hotels; the entity_id of any earlier row works too. One offers call per property and check-in date — no search, so nothing unrelated is ever billed.
hotelNamesarray[]Property names, e.g. "Hyatt Regency Lisbon", "Brown's Avenue Hotel Lisbon". Each name costs one place search; the listing is kept only when its name matches most of the words you typed (at least two of them), otherwise the status row says not_found and nothing is billed. Add the city to a generic name.
checkInstringrequiredThe first check-in date of the window: absolute YYYY-MM-DD, or relative to the run date so a schedule never goes stale: "30 days", "6 weeks", "+30d", today, tomorrow. Past dates are rejected before anything is fetched.
daysinteger7How many consecutive check-in dates to price, starting at checkIn (1–60). 7 = a week of check-in dates per property, one rate row each; 30 = a month, the shape of a price calendar. Each date is one offers call. Range 1-60.
nightsinteger1Length of stay in nights (1–30) for every check-in date. Check-out = check-in + nights. Range 1-30.
adultsinteger2Adults per room. Rates scale with occupancy exactly as on Google, so track the occupancy you will book. Range 1-6.
childrenAgesarray[]Ages of children sharing the room, e.g. [4, 9]. Leave empty for none.
currencystring"USD"3-letter currency code for every price (USD, EUR, GBP, JPY, AED, …).
languagestring"en"Google UI language (hl); affects source labels.
countrystring"us"Google market (gl): which country's Google Hotels answers. Sources, prices and whether taxes are included differ by market — use the market you book from.
maxPricenumber00 = deliver every date. N = deliver only the check-in dates whose lowest nightly rate is at or under N in your currency — a price drop alert: a run that delivers nothing means nothing is under your target yet. Dates over the target are never billed; the status row still reports the cheapest rate and date it saw. Range 0-10000000.
proxyConfigurationobject{"useApifyProxy": true}Google accepts Apify datacenter proxies for these calls; keep the default. Residential is unnecessary.
sessionsinteger4Concurrent egress IPs; each is paced at perIp requests per second. Range 1-16.
perIpnumber0.5Pace per proxy session. 0.5 is safe; raise carefully. Range 0.1-2.

Example input

{
  "hotels": ["https://www.google.com/travel/hotels/entity/ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE"],
  "checkIn": "tomorrow",
  "days": 30,
  "nights": 1,
  "currency": "USD"
}
{
  "hotelNames": ["Hyatt Regency Lisbon", "Altis Grand Hotel Lisbon"],
  "checkIn": "2026-10-09",
  "days": 3,
  "nights": 2,
  "adults": 2,
  "currency": "EUR",
  "country": "pt",
  "maxPrice": 220
}

Hotel price tracker output fields

Two row types share one dataset: rate (one property and check-in date with the lowest rate across every booking source, the cheapest source and the date's rank in the window; charged) and status (one per property; never charged).

Dataset views in the Apify Console: Price history, Cheapest dates, Status.

FieldTypeWhat it holds
typestringRow type: rate (charged) or status (free).
querystringThe input this row came from: the Google Hotels URL, entity token or property name as given.
hotel_namestringProperty name as Google Hotels shows it.
entity_idstringGoogle Hotels entity token; reuse it in the hotels input.
hotel_idstringGoogle's numeric property id (name lookups only).
hotel_urlstringThe property's Google Hotels page.
starsintegerStar class (name lookups only).
ratingnumberGoogle review rating (name lookups only).
reviewsintegerGoogle review count (name lookups only).
check_instringCheck-in date (YYYY-MM-DD). On status rows: the first date of the window.
check_outstringCheck-out date (YYYY-MM-DD).
nightsintegerLength of stay.
adultsintegerAdults the rate was priced for.
childrenintegerChildren the rate was priced for.
currencystringCurrency of every price in the row.
pricenumberThe lowest nightly rate across every source that day.
price_displaystringThat rate as Google displays it.
totalnumberStay total at the cheapest source (nightly × nights when the source shows only a nightly rate).
sourcestringThe cheapest source: an OTA or the property's official site.
officialbooleanWhether the cheapest source is the property's official site.
source_urlstringBooking link at the cheapest source with the dates filled in.
free_cancellationbooleanWhether the cheapest rate is refundable, when Google says.
official_pricenumberThe official site's lowest nightly rate that day, null when Google lists no official site.
n_sourcesintegerHow many sources priced the stay that day.
cheapest_in_windowbooleanTrue on the cheapest check-in date of the property's window.
rank_in_windowinteger1 = cheapest check-in date of the window, 2 = next, … (the earlier date wins a tie).
daysintegerStatus rows: check-in dates in the window.
ratesintegerStatus rows: rate rows delivered (and billed) for the property.
filteredintegerStatus rows: dates dropped by maxPrice (free).
no_ratesintegerStatus rows: dates with no priced source (free).
errorsintegerStatus rows: dates whose offers call failed after retries (free).
cheapestnumberStatus rows: the lowest nightly rate seen in the window, filtered dates included.
cheapest_datestringStatus rows: the check-in date of that rate.
statusstringStatus rows: what happened to the property.
errorstringStatus rows: the first error message, if any.
fetched_atstringWhen the row was fetched (UTC, ISO 8601).

Example row from a real run

Taken from the actor's README — this is the shape your dataset holds.

{ "type": "rate", "query": "https://www.google.com/travel/hotels/entity/ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE", "hotel_name": "Brown's | Avenue Hotel", "entity_id": "ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE", "hotel_id": null, "hotel_url": "https://www.google.com/travel/hotels/entity/ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE", "stars": null, "rating": null, "reviews": null,
  "check_in": "2026-10-08", "check_out": "2026-10-09", "nights": 1, "adults": 2, "children": 0, "currency": "USD",
  "price": 337.96, "price_display": "$338", "total": 337.96, "source": "Super.com", "official": false, "source_url": "https://www.super.com/…", "free_cancellation": false,
  "official_price": 420.03, "n_sources": 5, "cheapest_in_window": true, "rank_in_window": 1,
  "fetched_at": "2026-08-29T06:30:12+00:00" }
{ "type": "status", "query": "Brown's Avenue Hotel Lisbon", "hotel_name": "Brown's | Avenue Hotel", "entity_id": "ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE", "check_in": "2026-10-08", "days": 7, "rates": 7, "filtered": 0, "no_rates": 0, "errors": 0, "cheapest": 337.96, "cheapest_date": "2026-10-08", "status": "ok", "error": null, "fetched_at": "2026-08-29T06:30:12+00:00" }

Hotel price tracker in Python, JavaScript and curl

Python

from apify_client import ApifyClient
client = ApifyClient("<TOKEN>")
run = client.actor("kestrel/hotel-price-tracker").call(run_input={
    "hotelNames": ["Hyatt Regency Lisbon", "Altis Grand Hotel Lisbon"], "checkIn": "30 days", "days": 14, "nights": 2, "currency": "EUR", "country": "pt"})
rows = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["type"] == "rate" and r["cheapest_in_window"]]
for r in rows: print(r["hotel_name"], r["check_in"], r["price"], r["currency"], "at", r["source"], "direct" if r["official"] else f'(direct {r["official_price"]})', r["source_url"])

JavaScript (Node)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<TOKEN>' });
const run = await client.actor('kestrel/hotel-price-tracker').call({ hotels: ['ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE'], checkIn: '2026-10-09', days: 3, nights: 2, maxPrice: 300 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const hits = items.filter(r => r.type === 'rate').map(r => `${r.hotel_name} ${r.check_in}: ${r.price} ${r.currency} at ${r.source} ${r.source_url}`);
console.log(hits.join('\n') || 'nothing under target yet');

curl

curl -X POST "https://api.apify.com/v2/acts/kestrel~hotel-price-tracker/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"hotels": ["ChkIg-b2ismUj7M1Gg0vZy8xMWg3MThreGg1EAE"], "checkIn": "30 days", "days": 7, "nights": 1, "currency": "USD"}'

The same three calls are runnable files in the examples repository: examples/hotel-price-tracker.py|.js|.sh.

Limits and honest notes

  • Rates are what Google Hotels displays for the market (country) and currency you choose. Whether taxes and fees are included follows Google's display rules for that market; compare within one market.
  • A property with nothing bookable on a date (sold out, closed) has no rate that day: the date counts in no_rates, no row is delivered, nothing is billed.
  • Names are resolved with one Google search and kept only when the listing's name matches most of the words you typed. A name that does not resolve gets a not_found status row and is free; reuse the entity_id from any row in hotels for exact, search-free runs.
  • Windows are capped at 60 dates and stays at 30 nights per run; run twice for more.
  • Google changes its internal payloads without notice. A daily canary run checks this actor; a failed date is reported in the status row's errors and error fields rather than delivered as a zero.

FAQ

Does it need a Google Hotels API key or a Google account?

No. There is no public Google Hotels API for reading prices — Google's hotel APIs are feeds for properties sending rates to Google. This actor reads the public comparison page through Apify's proxies. No account, no key, no browser on your side.

How do I get a price drop alert instead of a table?

Set maxPrice to your target nightly rate and schedule the run. Rows appear only for dates at or under the target; wire the dataset to an n8n, Make or Zapier step that posts each row to Slack, Telegram or email. Days with nothing under target deliver nothing and cost nothing, and the status row still tells you the cheapest rate seen.

Can it build hotel price history for a stay?

Yes — that is the scheduled use, and it is Google Hotels price tracking on your terms rather than Google's. Run the same input daily; each run appends rows with a new fetched_at. For one check_in, price over fetched_at is the history of that stay's best rate; source tells you who had it each day.

Which dates does a window cover?

days consecutive check-in dates starting at checkIn, each for nights nights. days: 7 from "30 days" prices check-ins 30 to 36 days from the run date. Relative checkIn values move with the calendar, so a schedule keeps looking the same distance ahead.

Does the price include taxes and fees?

It follows what Google Hotels shows for the market in country. Some markets display all-in rates, others exclude taxes and resort fees; the rows are consistent within one market, so keep comparisons inside it.

How is this different from the Google Hotels Prices Scraper?

The parent returns one row per booking source per property and stay, and can search places for many properties at once. This actor returns one row per property and date — the lowest rate and where it is — over a window of dates, ranked, with an alert filter. Use the parent when you want every source as its own row; use this one to track hotel prices over time or to find the cheapest date to stay.

Can I track a competitor set for my own property?

Yes. List your property and the competitors by URL, token or name; every run gives one row per property and date with the market's lowest rate and whether it is direct. For the per-source breakdown and undercut math, pair it with the Hotel Rate Parity Checker on the same properties.

Is there a free tier?

Every Apify account includes free platform credit, which covers many runs of this actor; the per-row price is on the pricing tab. Runs that deliver nothing — every date over maxPrice, unresolved names — cost nothing beyond a fraction of a cent of platform usage.

How to use Hotel Price Tracker

Run Hotel Price Tracker

The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/hotel-price-tracker