Search a destination, then price a hotel
List a destination's hotels with Google Hotels prices and filters, page through them, then price the one you pick.
Overview
You get | The hotels Google Hotels lists for a destination and stay, about 20 per page, each with its property token, lowest price for the stay, rating, class, amenities and location. |
Calls | POST/v1/hotels/search per page, then POST/v1/calendar or POST/v1/offers for the hotel you pick |
Cost | 3 credits per page (an empty page is free), then 5 for a calendar or 3 for offers |
Stay | Up to 30 nights; check-in today or later |
Already on SearchAPI's google_hotels engine? POST/v1/serp/google_hotels takes the same parameters and returns the same shape, at the same price.
Search the destination
q is what a traveller would type into Google Hotels: hotels in Montreal, Paris, hotels near JFK. gl sets the market and currency the price currency.
curl -X POST "https://api.scrapercompany.com/v1/hotels/search" \
-H "x-api-key: $SCRAPERCOMPANY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"q": "hotels in Montreal",
"check_in": "2026-11-30",
"check_out": "2026-12-02",
"adults": 2,
"currency": "CAD",
"gl": "ca"
}'Read the prices
Each property's rate is the lowest price Google shows for the stay, with the stay split four ways: base, taxes, fees and total (fees are never folded into taxes).
{
"type": "hotel",
"property_token": "ChgImtDg_rW5k8iWARoLL2cvMXRzamQ4NncQAQ",
"name": "Radisson Hotel Montreal Airport",
"hotel_class": "4-star hotel",
"extracted_hotel_class": 4,
"rating": 3.5,
"reviews": 2535,
"amenities": [
"Breakfast ($)",
"Free Wi-Fi",
"Parking ($)",
"Pools"
],
"deal": "27% less than usual",
"deal_description": "Great Deal",
"rate": {
"currency": "CAD",
"check_in": "2026-11-30",
"check_out": "2026-12-02",
"nights": 2,
"price_per_night": "$121",
"price_per_night_before_taxes": "$102",
"extracted_price_per_night": 121,
"extracted_price_per_night_before_taxes": 101.68,
"base": 203.36,
"taxes": 38.64,
"fees": 0,
"total": 242,
"before_taxes": 203.36,
"from_display_text": false
}
}| Field | Meaning |
|---|---|
total | The whole stay: base + taxes + fees. |
before_taxes | base + fees for the stay (SearchAPI’s before-taxes basis). |
extracted_price_per_night | total / nights, so it includes taxes and fees. |
extracted_price_per_night_before_taxes | Google’s exact (base + fees) / nights: the nightly price on the Google Hotels card. |
price_per_night, total_price, … | The same figures as Google displays them, rounded. |
from_display_text | true when only display strings came back: taxes and fees are then null and base holds the displayed before-taxes total. |
rate is null for a property Google showed without a price, or one priced for other dates (the price is removed and a warning says so). type is hotel or vacation_rental: Google sometimes blends rentals into the list, and they carry their seller rows in sources.
Filter and sort
Add any of these to the body; Google applies them before paging.
| Field | Values |
|---|---|
sort_by | relevance (default), lowest_price, highest_rating, most_reviewed |
price_min | Nightly price bounds in currency (with price_max) |
min_rating | 3.5, 4.0 or 4.5 |
hotel_class | Star classes, e.g. [4, 5] |
amenities | Amenity ids, e.g. 6 Pool, 35 Free Wi-Fi, 9 Free breakfast, 19 Pet-friendly (full list in the reference) |
property_types | Property-type ids, e.g. 13 Boutique hotels, 17 Resorts, 19 Bed and breakfasts |
free_cancellation, special_offers, eco_certified | true to keep only those properties |
children_ages | One age per child, e.g. [8]; the response is rejected if Google priced a different party |
{
"q": "hotels in Montreal",
"check_in": "2026-11-30",
"check_out": "2026-12-02",
"adults": 2,
"currency": "CAD",
"gl": "ca",
"sort_by": "lowest_price",
"hotel_class": [
4,
5
],
"amenities": [
6
],
"min_rating": 4,
"price_max": 400
}Page through results
Resend the same body with page_token set to the previous page's pagination.next_page_token. Each page is a new 3-credit call. Pages overlap by a few records (page 1 is records 1-20, page 2 starts at 19), so de-duplicate by property_token.
seen = {}
token = None
for _ in range(3): # three pages, 9 credits at most
r = requests.post(f"{API}/v1/hotels/search", headers=HEADERS, timeout=120,
json={**query, **({"page_token": token} if token else {})})
r.raise_for_status()
page = r.json()
for p in page["properties"]:
seen.setdefault(p["property_token"], p)
token = page["pagination"]["next_page_token"]
if not token:
break
print(len(seen), "unique properties")A token Google no longer accepts returns 422: restart from the first page.
Price the hotel you pick
property_token is the same Google property token every Google Hotels endpoint takes, so there is no separate lookup. Store it with the hotel.
- POST/v1/calendar (5 credits): nightly prices for the next 90 nights (up to 330) in one call. See Price a hotel's next 90 nights.
- POST/v1/offers (3 credits): every booking site's price for one stay.
hotel = min((p for p in seen.values() if p.get("rate") and p["rate"]["total"] is not None),
key=lambda p: p["rate"]["total"])
r = requests.post(f"{API}/v1/calendar", headers=HEADERS, timeout=120, json={
"token": hotel["property_token"], "days": 90, "currency": "CAD", "market": "CA",
})
r.raise_for_status()
calendar = r.json()
print(hotel["name"], len(calendar["rates"]), "nights priced")Good to know
- Prices follow the market Google sees the request from. When that market could not be verified the response says so in
warnings; keepglandcurrencyconstant between runs, and checksearch_information.currency_matches_request. - Google decides whether to blend vacation rentals into the list, so
total_resultscan change between identical requests. Each result'stypesays what it is. - Display strings follow
hl; hotel amenity names are always English.