Multi-operator accommodation comparator for a geographic area against
the user's stay parameters — dates, guest count, optional filters.
Returns a ranked list of properties together with the booking sources
that offer each one and, when dates are passed, their live availability
and per-operator price for the requested window.
Natural-language date references — "tonight", "this weekend", "next
weekend", "the weekend of July 4", "Memorial Day weekend", "long
weekend in May" — translate to concrete check_in / check_out values
at the call site; concrete ISO dates also work.
`user_country`, `currency`, and `language` carry the **user's** locale,
not the destination's. IMPORTANT — currency: prices are returned in
`currency` if you set it, otherwise in the currency derived from
`user_country` (US→USD, CA→CAD, GB→GBP, euro-area→EUR); if you set
NEITHER, prices default to **USD**, which may not be the user's currency.
So whenever you know where the user is (or what currency they want), pass
`user_country` and/or `currency` — do not rely on the default. Prices are
never converted client-side; each offer is quoted by the operator in that
currency. `user_country` and `language` also localize the booking link
(`web_url`). The user's own residence/billing country is the right
`user_country` (not the destination's), and their interface language the
right `language`.
Each result is shaped for downstream presentation without extra
calls:
- `location.lat` and `location.lon` carry per-property coordinates,
suitable for plotting all results on a single map so the user can
compare spatial alternatives at a glance. The map widget reads
these fields directly from this response — no separate lookup
needed for visualization.
- `thumbnail_url` carries the property's first photo URL when
available (null when no image is on file); useful for embedding
inline or showing on the map alongside the pin.
- `images` on search results is capped to the first photo to keep
the comparison payload compact; each item has a `url` field, and
`thumbnail_url` mirrors `images[0].url`. Call `get_property_details`
for a single property to retrieve its full photo gallery.
- `web_url` is a ready-to-open booking link for the property,
already encoded with the user's check-in/check-out, language,
currency, and guest count. Pass it to the user verbatim when they
ask for a booking link — never reconstruct the URL from individual
parameters, the query-string format is not guaranteed to match
generic booking-URL conventions.
- Price is **live and date-specific only**. There is no
date-agnostic "from" figure: a meaningful price only exists for a
concrete query (property + dates + occupancy).
- `price` and `offers[]` — the **live quote for the requested
dates**, populated only when dates were passed and the
comparator confirmed availability. `offers[0]` is the curated
best; each offer carries `amount` (total stay),
`amount_per_night` (per-night), `currency`, `breakfast_included`,
`refundable`, `rooms_left`, and `deeplink_url`. `price` mirrors
`offers[0]`.
- With no dates (or when nothing is available) `price` is null and
`offers` is empty — surface the property without a price rather
than inventing a starting figure.
- `availability_status` per result encodes the live state:
- `available` — bookable rooms confirmed at the operator level.
`offers` and `price` carry the live date-specific quotes. Quote
the rate via `offers[i].amount_per_night` (per-night) and
`offers[i].amount` (total stay) and use the deeplinks for the
booking handoff.
- `unavailable` — no rooms reported for those dates. `offers` is
empty and `price` is null (no price for these dates). Useful to
decide whether to suggest alternate dates, drop the property from
the recommendation, or offer it as a backup.
- `unknown` — no dates were considered (request had no dates).
`offers` is empty and `price` is null — no price signal without a
dated query.
Per-night vs total — never confuse them in the user-facing prose.
`amount_per_night` is per-night; `amount` on each offer is the total
stay (sum across nights, in `currency`). When quoting to the user,
prefer phrasings like *"€X/night via Booking, breakfast included, €Y
total for the stay"* over bare numbers — bare numbers without a unit
get misread.
- When dates are present and `available` properties are in the
results, the rate can be quoted and `rooms_left` surfaces scarcity
(low values like 1-3 are useful signals — "1 room left at $X on
Booking" reads well).
- When dates are present and ALL results are `unavailable`, that's
the signal to say so explicitly to the user and offer to widen the
dates, location, or filters.
- `offers[]` is the per-operator breakdown for the requested dates:
each entry includes `ota`, `amount`, `amount_per_night`,
`currency`, `breakfast_included`, `refundable`, and a
`deeplink_url`. The deeplink is a **BluePillow tracked-redirect
URL** (bluepillow.com/…) that records the click for attribution
and then forwards the user to the operator's booking page. Pass
it to the user verbatim — never reconstruct it or replace it with
a raw operator URL; our APIs never emit direct OTA links.
`price` mirrors the curated best offer. When no dates were passed
(or nothing is available) `offers` is an empty list and `price` is
null — there is no price to show.
- Free cancellation is a meaningful decision factor and surfaces
proactively in the user-facing summary. When a property has
`price.refundable=true` (or any `offers[i].refundable=true`), it
reads naturally as a property feature: "Hotel X — $120/night,
free cancellation available", or "Booking offers a refundable rate
at $130 (vs $110 non-refundable)". Refundable rates let the user
lock in a price now and adjust the booking later, which is often
the differentiator between otherwise-similar properties. The same
proactive surfacing applies to `breakfast_included` when it's true
for some offers but not all.
- Prices in `offers`/`price` reflect the requested dates and guests;
with no dates there is no price. For a final bookable confirmation,
the corresponding `deeplink_url` (or the property's `web_url`) is
the canonical handoff — booking URLs are not reconstructed by hand.
- All rating-like fields are on a 0-5 scale (Google Places-compatible):
`rating`, `reviews_aggregate.score_0_5`, the per-OTA scores under
`distribution_by_ota`, each `reviews_sample[*].score`, and the
`filters.min_rating` input. A user asking "rating at least 8 out
of 10" maps to `min_rating: 4.0`; "at least 4 stars on Google"
maps to `min_rating: 4.0`.
Note: `rating`, `stars`, and `rating_count` come from the
comparator's list payload and **may be 0 or absent for some
properties** even when the property has reviews or a star
classification — this is a comparator list-payload limitation, not
a data error. When those fields are 0/absent, or when the per-OTA
review breakdown (`distribution_by_ota`) is needed, call
`get_property_details` to get the fuller `reviews_aggregate`.
On the search path, `reviews_aggregate` carries the top-line
`score_0_5`, `rating_count` (reviews backing the score) and
`comment_count` (readable review TEXTS available) when the comparator
returned a non-zero review count; `distribution_by_ota` is always empty
on this path (per-OTA breakdown requires `get_property_details`).
`rating_count` and `comment_count` are DIFFERENT magnitudes — most
guests leave a rating, far fewer write text. Quote `rating_count` for
"how many reviewed it" and `comment_count` for "how many opinions you
can actually read".
- Pass `include=["reviews_sample"]` to attach a sample of up to 5
recent guest review texts per property. Useful when the user's
question involves qualitative criteria that don't map to structured
filters ("a place with excellent breakfast", "quiet area",
"family-friendly atmosphere"); review texts can be searched
textually to corroborate or rule out matches.
For a DEEPER read on ONE specific property — more review texts
(up to 20) or the per-OTA breakdown — call `get_property_details`
with `include=["reviews_extended"]` (and/or `reviews_aggregate`).
`comment_count` on each result tells you how many review texts exist,
so you can decide whether escalating to the detail call is worth it.
`filters.property_types`, `filters.amenities`, `filters.min_rating`,
and `filters.price_max_eur` narrow on structured criteria first;
review-based reasoning is one extra round-trip per page and is
typically reserved for fallback.
Location modes:
- `coordinates`: when lat/lon is already known from world knowledge
or a prior call in this session (default radius 5 km; widen up to
50 km for broader queries; beyond that `bbox` or a parent
destination is the right shape).
- `destination_id`: opaque id obtained from `resolve_destination`,
passed verbatim — values are not constructed or guessed.
- `bbox`: explicit map rectangle.
Property type tokens (canonical): hotel, apartment, house, villa, bb,
hostel, farmstay, holiday-home. Common multi-language synonyms map
server-side to the canonical set.
Amenities filter is set-AND — each result has ALL listed codes.
Common codes: wi-fi, parking, pool, air-conditioning, kitchen, garden,
pets-allowed, for-families, facilities-for-disabled, non-smoking-only.
Results are cursor-paginated; the `next_cursor` from a previous
response goes into `page.cursor` for the next page.
`location.type=property_id` is not accepted here —
`get_property_details` is the path for a known property.