Skip to content

Searching availability

roost search is the core command: what’s available in a location, for a stay, at what rate.

Terminal window
roost search "kyoto" --check-in 2026-10-02 --nights 3 --json
{
"schemaVersion": "1",
"query": {
"location": "kyoto",
"checkIn": "2026-10-02",
"checkOut": "2026-10-05",
"nights": 3,
"guests": { "adults": 2, "children": 0 },
"currency": "USD"
},
"scope": {
"backend": "google",
"brandsFilter": [],
"partial": false,
"note": "lead-in rates from Google Hotels metasearch; not a booking guarantee — confirm on the property's own site"
},
"properties": [
{
"id": "CgoI3Ni2rIz7-6BSGgkyNXpycmR3NncQAQ",
"name": "Sakura Cross Hotel",
"type": "hotel",
"class": null,
"rating": 4.9,
"reviews": 2500,
"nights": 3,
"perNight": { "amount": 92.0, "currency": "USD" },
"total": { "amount": 304.0, "currency": "USD" },
"taxesIncluded": true,
"deal": { "percentLessThanUsual": 60, "label": "GREAT DEAL" },
"amenities": ["Sauna", "Free Wi-Fi", "Parking"],
"url": "https://www.google.com/travel/hotels/entity/CgoI3Ni2rIz7-6BSGgkyNXpycmR3NncQAQ"
}
]
}

location is free text — a city, a neighborhood, whatever you’d type into Google Hotels yourself: "kyoto", "chicago", "lisbon, portugal". roost resolves it to a Google place id on the first request and caches that id for 30 days, so repeat searches for the same city cost one upstream request instead of two. If the location can’t be resolved to a place, the command fails with exit code 5 (not found).

Two ways to say how long you’re staying, and one rule for when you give both:

Terminal window
# nights (default 2) — check-out is computed
roost search "chicago" --check-in 2026-11-10 --nights 3 --json
# explicit check-out — wins over --nights when both are given
roost search "chicago" --check-in 2026-11-10 --check-out 2026-11-14 --json

--check-in defaults to today if omitted. If you pass both --check-out and --nights, --check-out wins — --nights is silently ignored, not an error. --check-out must be after --check-in, or the command fails with exit code 2 (usage).

Terminal window
roost search "lisbon" --adults 4 --children 2 --currency EUR --json

--adults defaults to 2 and must be at least 1. --children defaults to 0. --currency is an ISO code, default USD — it’s normalized to uppercase and drives the Money shape in every result ({"amount": ..., "currency": ...}), never a bare number.

Every filter below runs client-side on the google backend: roost fetches one page of results from Google Hotels and narrows/sorts that page in-process. It does not search deeper or issue another request per filter. Whenever a filter narrows the set, the envelope’s scope.partial is set to true and scope.note explains why — so an agent never mistakes a filtered page for the whole corpus.

--class — minimum hotel star class:

Terminal window
roost search "chicago" --class 4 --json

--min-rating — minimum guest rating:

Terminal window
roost search "chicago" --min-rating 4.5 --json

--max-price — maximum nightly rate, compared against perNight.amount:

Terminal window
roost search "chicago" --max-price 200 --json

--free-cancellation — only properties where freeCancellation is true:

Terminal window
roost search "chicago" --backend serpapi --free-cancellation --json

--sort orders the (already filtered) result set: relevance (default, Google’s own order), price, rating, or reviews.

Terminal window
roost search "chicago" --class 4 --min-rating 4.5 --sort price --json

Two global flags, useful once a query returns more rows than you want to read:

Terminal window
# cap the number of rows
roost search "chicago" --limit 5 --json
# project down to specific fields (dot-path, comma-separated)
roost search "chicago" --select name,perNight.amount,rating --json

--limit defaults to 50; when it truncates the list, a note goes to stderr (note: output truncated to N of M items (use --limit to change)) so nothing is dropped silently. --select takes comma-separated dot-paths and applies per-row — pass perNight.amount to reach inside the Money object rather than getting the whole thing.

In plain or tsv format (the default is plain), search prints the properties rows directly as a table, not the envelope — the scope.note still goes to stderr so you don’t lose it, and --select’s dot-paths become the column headers:

Terminal window
roost search "chicago" --limit 3 --select name,perNight.amount,rating
scope: lead-in rates from Google Hotels metasearch; not a booking guarantee — confirm on the property's own site
name perNight.amount rating
The Drake Hotel 189.0 4.6
...

Without --select, the table falls back to every field present across the rows, alphabetized — readable, but wide. Money fields (perNight, total) that aren’t projected down to a sub-path render as their raw JSON object in the cell.

Each row in properties is one card from the Google Hotels results page:

fieldmeaning
idGoogle entity token — pass this (or name) as the property argument to roost rates
nameproperty name, sanitized (control characters stripped, whitespace collapsed, capped at 200 chars unless --no-wrap-untrusted)
typehotel or vacation_rental — the two are priced and cancelled very differently, so filter on this before comparing rates
classstar class, 1-5, or null if Google didn’t state one
rating / reviewsguest rating and review count, or null
nightsthe stay length Google’s own card text states
perNight / totalMoney objects — always {"amount": ..., "currency": ...}, never a bare number or formatted string, and never interchangeable
taxesIncludedwhether the stated price already includes taxes and fees
deal{"percentLessThanUsual": N, "label": "GREAT DEAL" or "DEAL"}, or null
amenitiesup to 12 sanitized strings
urlthe property’s Google Hotels page
nightsMismatchonly present, and true, when Google’s card states a different stay length than the one you asked for — see below

To go deeper on one property’s per-source rate breakdown, see comparing rates across backends. To sweep a date range for the cheapest check-in, see finding cheap dates.