Google Hotels Price Scraper – Vendor Rates, Price History
Pricing
from $1.40 / 1,000 hotels
Google Hotels Price Scraper – Vendor Rates, Price History
Hotel prices from Google Hotels: per-vendor rates (OTA price ladder), rate comparison and price history for tracked stays. API and MCP-ready. Data-quality figures: data.yappman.com/data-quality
Pricing
from $1.40 / 1,000 hotels
Rating
0.0
(0)
Developer
Russell Yapp
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
9 hours ago
Last modified
Categories
Share
Google Hotels Scraper: Prices, Vendor Rates
Get Google Hotels prices for a destination (a city, area or landmark) and dates as structured data: one row per hotel with the nightly price, stay total, rating, star class (hotels only) and coordinates, where Google shows them.
A real row from a Lisbon search (the image URL is left out):
{"hotelId": "ChkI7drbt9vwh-JTGg0vZy8xMWI2cGMwcjE4EAE","name": "InterContinental Lisbon","propertyType": "hotel","hotelClass": 5,"rating": 4.6,"reviewCount": 2998,"address": null,"latitude": 38.726817,"longitude": -9.1552236,"checkIn": "2026-10-23","checkOut": "2026-10-25","nights": 2,"adults": 2,"rooms": 1,"leadPrice": {"perNight": 204.06, "total": 408, "currency": "USD", "vendor": null, "isSponsored": null},"vendorRates": null,"amenities": [],"amenityCodes": [165, 29, 15, 2, 18, 8, 4, 23, 24, 1, 31, 27, 7, 11, 25],"url": "https://www.google.com/travel/hotels/entity/ChkI7drbt9vwh-JTGg0vZy8xMWI2cGMwcjE4EAE","filtersApplied": {"google": [], "actor": []},"history": [],"scrapedAt": "2026-10-09T06:30:32.350585+00:00"}
A default run (the prefilled input: Lisbon, 2 adults, a two-night stay, 5 hotels) costs up to about $0.015 on the free tier: 5 hotel rows at $0.002 each, plus the $0.005 Actor start fee. It costs less if fewer hotels come back, and a blocked run costs only the start fee. Hotel rows cost less on higher Apify plans; the start fee is flat.
Run it with the default input.
16-second demo on YouTube: https://youtu.be/sURf-8czh8g
What it does
Get hotel prices from Google Hotels as structured data. Search a destination and a pair of dates and get one row per hotel: the lead nightly price Google shows, the total for the stay, star class, guest rating, review count, amenities and coordinates. In search mode you can add the per-vendor rate ladder (the rate each booking vendor and OTA shows, such as Booking.com, Expedia and Hotels.com) to each row, for rate comparison and price comparison; hotel mode returns no vendor rates in v0.1. You can also look up specific hotels by Google hotel id or Google Travel URL for one pair of dates, and read weekly lead-price history for the hotels in the cities the Actor tracks, for price tracking over time. It needs no browser and returns no personal data, and AI agents can call it through Apify's MCP server.
It is for travel-analytics and rate-intelligence teams, metasearch builders, researchers pricing destinations, and anyone who needs Google's hotel prices as structured data on a stable schema.
Every price belongs to the searched dates, and every row carries those dates.
Modes
| Mode | Status | What it does |
|---|---|---|
search | Available | Hotels for each destination and dates in searches. |
hotel | Available | The hotels in hotels, for the one pair of dates in dates. Returns no vendor rates in v0.1. |
history | Available | Weekly lead-price snapshots for hotels the Actor tracks, for the dates they are tracked on. See History mode. |
tracked | Available, free | Lists every tracked hotel id with its dates. See Tracked mode. |
Input
The default input (one search for Lisbon, two adults, a two-night stay starting 14 days after the run, 5 hotels in the prefilled input and 20 if maxItems is left out, no vendor rates) runs without changes.
| Field | Type | Default | Limits |
|---|---|---|---|
mode | search, hotel, history, tracked | search | One mode per run. tracked takes no other input and is free. |
searches | array of objects | one item: Lisbon, 2 adults, 1 room | search mode. Up to 20 items; at least 1 in search mode. |
searches[].destination | string | required | 1 to 200 characters; a city, area or landmark. No control characters. |
searches[].checkIn, searches[].checkOut | YYYY-MM-DD | see below | checkOut must be after checkIn. If left out, the stay is two nights starting 14 days after the run. |
searches[].adults | integer | 2 | 1 to 12. |
searches[].rooms | integer | 1 | Must be 1. Google ignores the rooms field, so other values are rejected rather than charged for a setting that changes nothing. |
hotels | array of strings | [] | hotel and history modes: Google hotel ids or the hotel's Google Travel page URL. Up to 1,000 entries in hotel mode (the maxItems cap) and 100 in history mode, each up to 500 characters. Required in both modes. A history run stops after 20 untracked hotels in a row; run tracked first to see what's covered. |
dates | object { "checkIn", "checkOut" } | none | hotel and history modes: required, YYYY-MM-DD, checkOut after checkIn. Both modes use 2 adults and 1 room; there is no adults input for them. |
includeVendorRates | boolean | false | Adds the per-vendor rate ladder to each row. search mode only for now: in hotel, history and tracked modes it is rejected with an input error, and hotel mode returns no vendor rates in v0.1. One extra request per hotel. Charged as its own event (see Pricing). |
filters | object | {} | See Filters. |
sortBy | relevance, price, rating, reviews | relevance | Order of results as Google sorts them. |
currency | string | USD | ISO 4217 code, three capital letters. |
language | string | en | Google's hl, e.g. en or en-GB. |
country | string | us | Two lowercase letters; Google's gl. |
maxItems | integer | 20 | 1 to 1,000. Hotels returned per run, across all searches. |
One search returns at most about 540 hotels (30 pages), whatever maxItems is.
There is no proxy input. The Actor manages its own network egress.
Filters
minPrice, maxPrice, minRating and hotelClass are applied by the Actor after the results are fetched, and each row's filtersApplied.actor lists the ones applied.
| Filter | Meaning | Notes |
|---|---|---|
minPrice | Minimum lead price per night, 0 or more | Hotels with no lead price are excluded when a price filter is set. |
maxPrice | Maximum lead price per night, 0 or more | As above. |
minRating | Minimum guest rating, 0 to 5 | Hotels with no rating are excluded. |
hotelClass | Array of star classes, each 1 to 5 | Hotels with no star class are excluded. |
freeCancellation | Rejected for now | Google's results carry no cancellation terms the Actor can check. |
amenities | Rejected for now | Most hotels carry Google's amenity codes without labels (see amenityCodes), so there is nothing reliable to match against. |
Because filters are applied after fetch, a run with a narrow filter can fetch many hotels to return few. If every hotel is filtered out, the run ends as blocked with the reason "no hotel matched the filters" and charges no hotel/vendor-rates/history event (only the platform's own start fee applies).
Very narrow filters can stop a search early (after 6 pages with no match, or once far more hotels were checked than returned); sorting by price or rating pages toward matches faster. The run summary's stoppedEarly says when this happened.
Output
One row per hotel per search (search), per looked-up hotel (hotel), or per tracked hotel (history).
Prices (leadPrice and vendorRates) are Google's pre-tax figures, nightly and for the stay, rounded half-up to the currency's minor unit (2 decimals for USD, 0 for JPY).
| Field | Type | Notes |
|---|---|---|
hotelId | string | Google's property id, stable across searches. |
name | string | |
propertyType | string or null | hotel when Google shows a star class; the vacation-rental label Google shows when present; otherwise null. |
hotelClass | integer or null | Star class, 1 to 5. |
rating | number or null | Google's aggregate guest rating, rounded to one decimal (half-up). |
reviewCount | integer or null | Number of reviews behind the rating. |
address | string or null | Filled in hotel mode. Null in search mode (a search result does not carry it), and normally null in history mode (stored rows come from search results). |
latitude, longitude | number, each nullable | |
checkIn, checkOut | date | The searched itinerary, on every row. |
nights, adults, rooms | integer | The searched itinerary. |
leadPrice | object or null | The price Google shows first: perNight, total, currency, vendor, isSponsored. Null when Google shows no price. Search results do not attribute the lead price to a vendor, so vendor and isSponsored are filled only when includeVendorRates is on and a vendor's offer matches the lead price; otherwise null. |
vendorRates | array or null | Null unless includeVendorRates is on. One entry per vendor, that vendor's cheapest total across Google's room groups: vendor, perNight, total, currency, guests, isSponsored, bookingUrl, bookingUrlType, vendorDomain, offerCount. |
amenities | array of strings | Google's own labels, only where Google serves them (vacation rentals carry inline labels). Empty otherwise. |
amenityCodes | array of integers | Google's numeric amenity codes as served, for hotel-type records whose labels are not in the response. Empty otherwise. No labels are guessed. |
imageUrl | string or null | One thumbnail. |
url | string | The hotel's Google Travel page. |
filtersApplied | object | google and actor: which filters each side applied. Today every filter is applied by the Actor. |
history | array | Empty in search and hotel modes. In history mode: { readAt, leadPrice, deltaPerNight } per weekly snapshot, oldest first. deltaPerNight is that snapshot's leadPrice.perNight minus the previous one's, null for the first snapshot or when either price is missing. |
scrapedAt | ISO 8601 date-time |
Within vendorRates:
offerCountis how many of Google's room-group offers were collapsed into that vendor's cheapest entry.bookingUrlandbookingUrlTypeare always null. Google's click links carry session identifiers, which the Actor never passes on.vendorDomainis the vendor site's registrable domain only (for examplebooking.com, notwww.booking.comorsecure.booking.com), or null when Google's link does not carry one. To book, usevendorDomaintogether with the row'surl(Google's hotel page).
Example search row without vendor rates (values illustrative):
{"hotelId": "ChIJ…","name": "Hotel Example Lisboa","propertyType": "hotel","hotelClass": 4,"rating": 4.6,"reviewCount": 2310,"address": "Rua Exemplo 1, Lisbon","latitude": 38.71,"longitude": -9.14,"checkIn": "2026-11-06","checkOut": "2026-11-08","nights": 2,"adults": 2,"rooms": 1,"leadPrice": { "perNight": 142, "total": 284, "currency": "EUR", "vendor": null, "isSponsored": null },"vendorRates": null,"amenities": [],"amenityCodes": [6, 29, 2, 4, 23, 1, 11],"imageUrl": "https://…","url": "https://www.google.com/travel/hotels/entity/ChIJ…","filtersApplied": { "google": [], "actor": ["minRating"] },"history": [],"scrapedAt": "2026-09-28T10:00:00Z"}
With includeVendorRates on, leadPrice.vendor and leadPrice.isSponsored are filled when a vendor's offer matches the lead price, and vendorRates holds entries like this one (illustrative):
{"vendor": "Booking.com","perNight": 142,"total": 284,"currency": "EUR","guests": 2,"isSponsored": false,"bookingUrl": null,"bookingUrlType": null,"vendorDomain": "booking.com","offerCount": 3}
History mode
history mode returns weekly lead-price snapshots for hotels the Actor tracks, for the dates they are tracked on. It makes no Google request: it reads stored snapshots.
- Input:
mode: "history",hotels(ids or Google Travel URLs) anddates. Adults are fixed at 2. - Tracked hotels currently come from a seed set of ten cities: New York, Los Angeles, Chicago, Miami, Las Vegas, London, Edinburgh, Lisbon, Paris and Barcelona. Stays are two nights, 2 adults, starting 2 and 6 weeks out, and are re-read weekly.
- Rows are returned as tracked: one fixed series (USD, US locale). The
currency,languageandcountryinputs do not change them. includeVendorRatesis rejected in this mode, and history rows carry no vendor rates.- One row per tracked hotel, with
historylisting its snapshots oldest first. - A hotel and dates that are not tracked return no row and are not charged. If none of the requested hotels is tracked, the run fails with "none of the requested hotels are tracked for these dates".
- A row with two or more snapshots is charged the
historyevent; a row with one snapshot is free.
No coverage beyond the seed set is promised. Run tracked first to see which hotels and dates history covers.
Tracked mode
tracked mode is free and makes no Google request. It lists every hotel currently tracked, and it writes the list to the run's key-value store record TRACKED_HOTELS, not to the dataset.
- Input:
mode: "tracked". Nothing else is needed;includeVendorRatesis rejected. - The record holds
generatedAt,countanditems. Each item hashotelId,checkIn,checkOut,adultsanddestination(the seed city), sorted by destination, then check-in, then hotel id. - Use an item's
hotelIdashotelsand itscheckIn/checkOutasdatesin ahistoryrun.historyuses 2 adults. - This code charges no
hotel/vendor-rates/historyevent for atrackedrun - only the platform's ownapify-actor-startlaunch fee applies, the same as any run.
Standby
On Apify Standby this Actor also answers two HTTP endpoints directly, instead of running as a single batch job:
GET /search?destination=&checkIn=&checkOut=&max=- the same validation assearchmode's input (destinationup to 200 characters,roomsmust be1if given at all). Optional:adults,sortBy,currency,language,country,includeVendorRates, and the filters as query parameters (minPrice,maxPrice,minRating,hotelClass, repeated or comma-separated).maxis 1 to 100 (narrower than batch'smaxItems, since one call answers in a single response) and defaults to 20; capped at 20 whenincludeVendorRatesis on (each vendor-rates lookup takes a few seconds and all of them must finish inside the request timeout).GET /hotel?id=&checkIn=&checkOut=- the same validation ashotelmode's input. Optional:currency,language,country.includeVendorRatesis rejected here, the same as in batchhotelmode.
Both use the same validation, normaliser, dedupe and filters as a batch run of this Actor, and return { "items": [...], "count", "blocked": null | "<reason>", "vendorRatesCharged": n, "chargeLimitReached": true | false } - items is exactly the Output row schema above; vendorRatesCharged is how many of the returned rows actually got the extra vendor-rates charge (normally equal to how many carry vendorRates, but can be lower if that one extra charge failed after the hotel charge already succeeded - the rows are still returned either way); chargeLimitReached is true when the run's own charge limit either trimmed max before fetching anything or was actually hit while charging, false otherwise. Nothing is pushed to this Actor's dataset from Standby; the response is the only place the rows go.
Only the parameters listed above are accepted per endpoint: an unknown query parameter, a single-value parameter repeated, or a value that does not parse in its documented format (a plain base-10 integer, a plain decimal, or true/false/1/0 for a boolean) is always rejected with 400, naming the parameter - never silently dropped or defaulted.
Each row returned is charged the hotel event (and vendor-rates too, for a row that ends up carrying vendor rates when includeVendorRates is on) before it is returned, so you are only ever billed for rows you actually got back. A request is charged before its response is written, so a dropped connection after that point is still charged. The platform's own apify-actor-start fee applies once per Standby run (the long-lived server process), not per request. In Standby mode you also pay the Apify platform usage of the run that serves your request - its compute units and any residential proxy traffic it uses - on top of these per-row events (Apify support, relayed 9 Oct 2026); a batch run instead absorbs that platform usage into the row price, as the Pricing section below describes. The platform sets each Standby run's own charge limit, and it can vary by caller - you cannot set or raise it yourself. This Actor reads the limit's remaining headroom from the run itself before fetching anything and never assumes a figure: a request can be capped below max, or refused outright, by that headroom, and chargeLimitReached says when it was. A request that found candidate rows but whose charge covered none of them gets 402 ("the run's charge limit allows no further rows"); a request that found nothing at all (or was blocked) gets 200 with items: [] and a blocked reason, and charges no hotel/vendor-rates event either way.
history and tracked mode are not available on Standby.
This server handles one request at a time: a second request made while one is already in progress gets 503 immediately (not queued), so you should not send concurrent requests to your own running instance. That run (including its platform usage above) stops after 60 idle seconds, so an idle endpoint costs you little. The first response from a run must arrive within 5 minutes including cold start (an Apify platform limit); this Actor's own request timeout shrinks accordingly for a run's very first request only, so that limit is always met.
See .actor/web_server_schema.json for the full OpenAPI description, including every parameter and error code.
Pricing
Pay per event. Prices below are the free-tier prices; paid Apify tiers are discounted: Bronze, Silver and Gold at 10%, 20% and 30% off the free-tier price; Platinum and Diamond at the Gold price.
Standby pays platform usage too. In Standby mode (see above) you also pay the Apify platform usage of the run that serves your request - its compute units and any residential proxy traffic it uses - on top of the events below (Apify support, relayed 9 Oct 2026). A batch run instead absorbs that platform usage into the row price; there is nothing extra to pay beyond the table below for a batch run.
| Event | Charged when | Price |
|---|---|---|
Actor start (apify-actor-start) | Charged by Apify itself, at launch - not by this Actor's own code. | $0.005 per GB of memory (1,024 MB default = one event) |
hotel | Each hotel row pushed to the dataset. | $0.002 ($2.00 per 1,000) |
vendor-rates | Each hotel row that carries vendor rates, and only when includeVendorRates is on. Never charged otherwise. | $0.0025 ($2.50 per 1,000) |
history | Each history row with two or more snapshots. A one-snapshot row is free. | $0.01 |
tracked mode charges no event of this Actor's own. history mode does not charge hotel. Blocked or empty runs charge no hotel/vendor-rates/history event: no row stored means no row charge - only the platform's own start fee applies, the same as any run. Filtered-out hotels are not pushed and not charged.
What to expect
- Blocked runs fail visibly. If Google refuses the requests or the responses no longer match the expected layout, the run ends with an error status ("Blocked:
- Currency is as requested. In
searchandhotelmodes prices are in the currency you set incurrency, as Google returns them. The Actor never converts. - No booking links.
bookingUrlis always null because Google's click links carry session identifiers, which the Actor never passes on and never follows. UsevendorDomainand the row'surl. - Network egress is managed by the Actor. There is no proxy setting to configure.
FAQ
How do I get prices in another currency, language or country?
Set currency (e.g. GBP), language (e.g. en-GB) and country (uk for the United Kingdom: it returned a normal Google results page when tested; gb has not been tested). They map to Google's curr, hl and gl, sent unchanged. Currency is as Google returns it; nothing is converted.
Why does a price differ from what I see on Google?
Every price is specific to the itinerary searched (dates, adults) and to the request as an anonymous visitor. Prices you see in your own browser can differ if you are logged in, have a personalised profile or different location, or searched a different number of guests. Google also changes prices frequently, and rooms is not honoured by Google, so it does not affect results.
What are the limits?
Up to 20 searches per run, up to 1,000 hotels per run (maxItems), and about 540 hotels per search. Requests are paced at one per two seconds, so large runs take time. As a guide, a 20-hotel search takes about 10 seconds, and 5 hotels with vendor rates take 20–30 seconds.
Can I filter by free cancellation or amenities? Not yet; see Filters.
Why did my search return fewer rows than maxItems?
Very narrow filters can stop a search early (after 6 pages with no match, or once far more hotels were checked than returned); sorting by price or rating pages toward matches faster. The run summary's stoppedEarly says when this happened.
Can I read rate history? Yes, for tracked hotels only; see History mode.
Are prices before or after tax? Pre-tax, as Google's nightly figure is.
Read more
- How to get Google Hotels prices with an API in 2026: a worked example with input and output, pricing, and limits.
Legal
This Actor is not affiliated with, endorsed by or sponsored by Google. Google and Google Hotels are trademarks of Google LLC and are named here only to say where the data comes from. Google's terms restrict automated access to its services; you are responsible for how you use the data and for complying with applicable law and terms. This is not legal advice.
The Actor collects no personal data: no reviewer names, no review text, no user data. It returns what Google shows any visitor: hotel details, prices and vendor names. For history mode it keeps a list of tracked hotel ids and stay dates, and weekly price snapshots for them; it records nothing about who runs the Actor or what they search for.