search_stays
unverifiedv0.1Multi-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 usable answer for those dates: either the request carried no dates, or the operators returned nothing conclusive for them. `offers` is empty and `price` is null. This is the most frequent of the three states, and it is NOT evidence that the property is full — it means the availability was not established. Say "I could not confirm availability", not "it is unavailable". 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 `offers[0]`, which is the best value for money as Blue Pillow ranks it — price weighed against what is included (breakfast, free cancellation) and the operator's historical reliability, with a small commercial component. It is not necessarily the cheapest: pass `sort=price_asc` for pure price order, and compare `offers[]` for the per-operator spread. 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`. `rating` is coarse in practice — upstream scores arrive rounded, so in the field it takes whole points, and `min_rating` behaves like a filter with a handful of steps rather than a continuous threshold. Always read `rating` together with `rating_count`: a 4 from 6 reviews and a 4 from 2,803 are not the same judgement, and a rounded 4 can sit on either side of "good". Prefer properties with a substantial `rating_count` when recommending, and say how many reviews back the score. 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.
Updated 9/25/2026 · Created 9/25/2026
Input Schema
{
"type": "object",
"required": [
"location",
"dates",
"guests"
],
"properties": {
"page": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
},
"description": "Pagination. `limit` caps results per page (1-100). To fetch the next page pass `cursor` with the `next_cursor` value from the previous response — cursors are opaque and are never constructed by hand.",
"additionalProperties": false
},
"sort": {
"enum": [
"score_desc",
"price_asc",
"rating_desc",
"conversion_desc"
],
"type": "string",
"description": "Result ordering. score_desc (default) is the balanced relevance ranking and suits most requests; price_asc when the user asked for the cheapest; rating_desc when they asked for the best-reviewed; conversion_desc favours what comparable travellers actually booked."
},
"dates": {
"type": "object",
"required": [
"check_in",
"check_out"
],
"properties": {
"check_in": {
"type": "string",
"format": "date"
},
"check_out": {
"type": "string",
"format": "date"
}
},
"description": "Check-in and check-out dates (ISO 8601, YYYY-MM-DD). Natural-language references — 'tonight', 'this 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. The comparator only serves future stays: check_in is a date in the real-time calendar that is today or later. A reference like 'the weekend of June 2' resolves to the next occurrence after today, never a past anniversary; if the current year is unclear, confirm with the user before calling. Clock calibration: this schema was published in August 2026, so today is August 2026 OR LATER (likely later — schemas outlive their publish date). An assumed 'today' in 2024/2025 is a stale training-time clock; years before 2026 are always in the past. Past check_in values are rejected at the API boundary with a hint carrying the actual current date.",
"additionalProperties": false
},
"guests": {
"type": "object",
"required": [
"adults"
],
"properties": {
"rooms": {
"type": "integer",
"maximum": 9,
"minimum": 1
},
"adults": {
"type": "integer",
"maximum": 16,
"minimum": 1
},
"children_ages": {
"type": "array",
"items": {
"type": "integer",
"maximum": 17,
"minimum": 0
}
}
},
"description": "Party composition. `adults` is required; give `children_ages` (one age per child, 0-17) rather than a child count — operators price by age. `rooms` defaults to 1; raise it when the party needs separate rooms rather than one shared.",
"additionalProperties": false
},
"filters": {
"type": "object",
"properties": {
"amenities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Preferred amenity codes. This ranks, it does not filter: properties declaring every code listed here come first and nothing is dropped, because upstream declarations are incomplete — an absent code is not evidence the service is missing. Read each result's `amenities` to see what is actually declared. Common: wi-fi, parking, pool, air-conditioning, kitchen, garden, pets-allowed, for-families, facilities-for-disabled, non-smoking-only."
},
"min_rating": {
"type": "number",
"maximum": 5,
"minimum": 0
},
"price_max_eur": {
"type": "number",
"minimum": 0
},
"property_types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Canonical tokens: hotel, apartment, house, villa, bb, hostel, farmstay, holiday-home. Italian/English synonyms (agriturismo, bnb, appartamento, casa, ...) are accepted and normalized server-side."
}
},
"description": "Structured narrowing applied server-side, before ranking. Prefer these over filtering the results yourself: they run across the whole inventory, not just the page you received. Qualitative criteria that do not map here (\"quiet\", \"great breakfast\") are better served by include=['reviews_sample'].",
"additionalProperties": false
},
"include": {
"type": "array",
"items": {
"enum": [
"reviews_sample"
],
"type": "string"
},
"description": "Optional enrichments, always a list — pass [\"reviews_sample\"], never the bare string. 'reviews_sample' attaches the 5 most recent individual reviews per property — use for qualitative queries (breakfast, service, ...). One extra Mongo round-trip per page; omit by default."
},
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$",
"description": "Currency of the returned prices (ISO-4217, 3-letter uppercase, e.g. 'USD', 'EUR', 'GBP', 'CAD'). SET THIS (or `user_country`) to price in the user's currency — if you set NEITHER, prices default to USD, which may not be the user's. Prices come straight from the booking sources in this currency; never convert them yourself. Each offer reflects the currency its operator actually quoted."
},
"language": {
"type": "string",
"pattern": "^[a-z]{2}$",
"description": "User's UI language (2-letter lowercase). Drives the booking link language and any server-rendered narrative content. Pass the language the user is currently speaking. Falls back to 'en' when omitted."
},
"location": {
"type": "object",
"required": [
"type",
"value"
],
"properties": {
"type": {
"enum": [
"destination_id",
"poi_id",
"coordinates",
"bbox"
],
"type": "string",
"description": "Selects the shape of value: destination_id and poi_id take an id string, coordinates takes {lat, lon, radius_km?}, bbox takes {nw: [lat,lon], se: [lat,lon]}. property_id is NOT supported here; use get_property_details."
},
"value": {
"anyOf": [
{
"type": "string",
"description": "destination_id/poi_id: opaque id string from resolve_destination or discover_destinations_near (e.g. 'dest_590c54056664cf2c60c5c2f6'). Pass it verbatim — the 'dest_'/'poi_' prefix is accepted. NEVER pass a free-form name like 'Ancona'; the API rejects bad identifiers with 400 invalid_request."
},
{
"type": "object",
"required": [
"lat",
"lon"
],
"properties": {
"lat": {
"type": "number",
"maximum": 90,
"minimum": -90,
"description": "Latitude in decimal degrees."
},
"lon": {
"type": "number",
"maximum": 180,
"minimum": -180,
"description": "Longitude in decimal degrees."
},
"radius_km": {
"type": "number",
"description": "Search radius in km (default 5).",
"exclusiveMinimum": 0
}
},
"description": "coordinates: a known point, searched within radius_km.",
"additionalProperties": false
},
{
"type": "object",
"required": [
"nw",
"se"
],
"properties": {
"nw": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "North-west corner as [lat, lon]."
},
"se": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "South-east corner as [lat, lon]."
}
},
"description": "bbox: an explicit map rectangle, given by its north-west and south-east corners.",
"additionalProperties": false
}
],
"description": "Shape depends on type — see each option below."
}
},
"description": "Where to search, as a {type, value} pair. Use destination_id for a place resolved via resolve_destination, poi_id for a point of interest, coordinates for a known lat/lon, or bbox for an explicit map rectangle. A property is NOT a location — use get_property_details for a known property.",
"additionalProperties": false
},
"user_country": {
"type": "string",
"pattern": "^[A-Z]{2}$",
"description": "User's country (ISO-3166 alpha-2 uppercase). Drives the booking-link locale (the landing page rendered when the user clicks `web_url`) AND, when `currency` is not set, the pricing currency (US->USD, CA->CAD, euro-area->EUR). Pass the user's own country, not the destination's. Falls back to 'US' when omitted."
},
"availability_mode": {
"enum": [
"strict",
"include_unavailable"
],
"type": "string",
"description": "strict (default): return ONLY properties available for the requested dates. include_unavailable: also return properties with no availability (each tagged availability_status). Use strict unless the user explicitly wants to see sold-out options."
},
"include_overbudget": {
"type": "boolean",
"description": "Opt-in. When few available results fit the budget, also return (in alternatives.overbudget) available properties in the same area just above price_max_eur. Requires filters.price_max_eur."
},
"include_out_of_bounds": {
"type": "boolean",
"description": "Opt-in. When the requested area yields few available results, also return (in alternatives.out_of_bounds) properties just outside the area, within the original budget. Present these explicitly as alternatives, never mixed with primary results."
}
},
"additionalProperties": false
}Full Contract
{
"name": "search_stays",
"description": "Multi-operator accommodation comparator for a geographic area against\nthe user's stay parameters — dates, guest count, optional filters.\nReturns a ranked list of properties together with the booking sources\nthat offer each one and, when dates are passed, their live availability\nand per-operator price for the requested window.\n\nNatural-language date references — \"tonight\", \"this weekend\", \"next\nweekend\", \"the weekend of July 4\", \"Memorial Day weekend\", \"long\nweekend in May\" — translate to concrete check_in / check_out values\nat the call site; concrete ISO dates also work.\n\n`user_country`, `currency`, and `language` carry the **user's** locale,\nnot the destination's. IMPORTANT — currency: prices are returned in\n`currency` if you set it, otherwise in the currency derived from\n`user_country` (US→USD, CA→CAD, GB→GBP, euro-area→EUR); if you set\nNEITHER, prices default to **USD**, which may not be the user's currency.\nSo whenever you know where the user is (or what currency they want), pass\n`user_country` and/or `currency` — do not rely on the default. Prices are\nnever converted client-side; each offer is quoted by the operator in that\ncurrency. `user_country` and `language` also localize the booking link\n(`web_url`). The user's own residence/billing country is the right\n`user_country` (not the destination's), and their interface language the\nright `language`.\n\nEach result is shaped for downstream presentation without extra\ncalls:\n- `location.lat` and `location.lon` carry per-property coordinates,\n suitable for plotting all results on a single map so the user can\n compare spatial alternatives at a glance. The map widget reads\n these fields directly from this response — no separate lookup\n needed for visualization.\n- `thumbnail_url` carries the property's first photo URL when\n available (null when no image is on file); useful for embedding\n inline or showing on the map alongside the pin.\n- `images` on search results is capped to the first photo to keep\n the comparison payload compact; each item has a `url` field, and\n `thumbnail_url` mirrors `images[0].url`. Call `get_property_details`\n for a single property to retrieve its full photo gallery.\n- `web_url` is a ready-to-open booking link for the property,\n already encoded with the user's check-in/check-out, language,\n currency, and guest count. Pass it to the user verbatim when they\n ask for a booking link — never reconstruct the URL from individual\n parameters, the query-string format is not guaranteed to match\n generic booking-URL conventions.\n- Price is **live and date-specific only**. There is no\n date-agnostic \"from\" figure: a meaningful price only exists for a\n concrete query (property + dates + occupancy).\n - `price` and `offers[]` — the **live quote for the requested\n dates**, populated only when dates were passed and the\n comparator confirmed availability. `offers[0]` is the curated\n best; each offer carries `amount` (total stay),\n `amount_per_night` (per-night), `currency`, `breakfast_included`,\n `refundable`, `rooms_left`, and `deeplink_url`. `price` mirrors\n `offers[0]`.\n - With no dates (or when nothing is available) `price` is null and\n `offers` is empty — surface the property without a price rather\n than inventing a starting figure.\n- `availability_status` per result encodes the live state:\n - `available` — bookable rooms confirmed at the operator level.\n `offers` and `price` carry the live date-specific quotes. Quote\n the rate via `offers[i].amount_per_night` (per-night) and\n `offers[i].amount` (total stay) and use the deeplinks for the\n booking handoff.\n - `unavailable` — no rooms reported for those dates. `offers` is\n empty and `price` is null (no price for these dates). Useful to\n decide whether to suggest alternate dates, drop the property from\n the recommendation, or offer it as a backup.\n - `unknown` — no usable answer for those dates: either the request\n carried no dates, or the operators returned nothing conclusive for\n them. `offers` is empty and `price` is null. This is the most\n frequent of the three states, and it is NOT evidence that the\n property is full — it means the availability was not established.\n Say \"I could not confirm availability\", not \"it is unavailable\".\n\nPer-night vs total — never confuse them in the user-facing prose.\n`amount_per_night` is per-night; `amount` on each offer is the total\nstay (sum across nights, in `currency`). When quoting to the user,\nprefer phrasings like *\"€X/night via Booking, breakfast included, €Y\ntotal for the stay\"* over bare numbers — bare numbers without a unit\nget misread.\n- When dates are present and `available` properties are in the\n results, the rate can be quoted and `rooms_left` surfaces scarcity\n (low values like 1-3 are useful signals — \"1 room left at $X on\n Booking\" reads well).\n- When dates are present and ALL results are `unavailable`, that's\n the signal to say so explicitly to the user and offer to widen the\n dates, location, or filters.\n- `offers[]` is the per-operator breakdown for the requested dates:\n each entry includes `ota`, `amount`, `amount_per_night`,\n `currency`, `breakfast_included`, `refundable`, and a\n `deeplink_url`. The deeplink is a **BluePillow tracked-redirect\n URL** (bluepillow.com/…) that records the click for attribution\n and then forwards the user to the operator's booking page. Pass\n it to the user verbatim — never reconstruct it or replace it with\n a raw operator URL; our APIs never emit direct OTA links.\n `price` mirrors `offers[0]`, which is the best value for money as\n Blue Pillow ranks it — price weighed against what is included\n (breakfast, free cancellation) and the operator's historical\n reliability, with a small commercial component. It is not\n necessarily the cheapest: pass `sort=price_asc` for pure price\n order, and compare `offers[]` for the per-operator spread. When no\n dates were passed (or nothing is available) `offers` is an empty\n list and `price` is null — there is no price to show.\n- Free cancellation is a meaningful decision factor and surfaces\n proactively in the user-facing summary. When a property has\n `price.refundable=true` (or any `offers[i].refundable=true`), it\n reads naturally as a property feature: \"Hotel X — $120/night,\n free cancellation available\", or \"Booking offers a refundable rate\n at $130 (vs $110 non-refundable)\". Refundable rates let the user\n lock in a price now and adjust the booking later, which is often\n the differentiator between otherwise-similar properties. The same\n proactive surfacing applies to `breakfast_included` when it's true\n for some offers but not all.\n- Prices in `offers`/`price` reflect the requested dates and guests;\n with no dates there is no price. For a final bookable confirmation,\n the corresponding `deeplink_url` (or the property's `web_url`) is\n the canonical handoff — booking URLs are not reconstructed by hand.\n- All rating-like fields are on a 0-5 scale (Google Places-compatible):\n `rating`, `reviews_aggregate.score_0_5`, the per-OTA scores under\n `distribution_by_ota`, each `reviews_sample[*].score`, and the\n `filters.min_rating` input. A user asking \"rating at least 8 out\n of 10\" maps to `min_rating: 4.0`; \"at least 4 stars on Google\"\n maps to `min_rating: 4.0`.\n `rating` is coarse in practice — upstream scores arrive rounded, so\n in the field it takes whole points, and `min_rating` behaves like a\n filter with a handful of steps rather than a continuous threshold.\n Always read `rating` together with `rating_count`: a 4 from 6\n reviews and a 4 from 2,803 are not the same judgement, and a\n rounded 4 can sit on either side of \"good\". Prefer properties with\n a substantial `rating_count` when recommending, and say how many\n reviews back the score.\n Note: `rating`, `stars`, and `rating_count` come from the\n comparator's list payload and **may be 0 or absent for some\n properties** even when the property has reviews or a star\n classification — this is a comparator list-payload limitation, not\n a data error. When those fields are 0/absent, or when the per-OTA\n review breakdown (`distribution_by_ota`) is needed, call\n `get_property_details` to get the fuller `reviews_aggregate`.\n On the search path, `reviews_aggregate` carries the top-line\n `score_0_5`, `rating_count` (reviews backing the score) and\n `comment_count` (readable review TEXTS available) when the comparator\n returned a non-zero review count; `distribution_by_ota` is always empty\n on this path (per-OTA breakdown requires `get_property_details`).\n `rating_count` and `comment_count` are DIFFERENT magnitudes — most\n guests leave a rating, far fewer write text. Quote `rating_count` for\n \"how many reviewed it\" and `comment_count` for \"how many opinions you\n can actually read\".\n- Pass `include=[\"reviews_sample\"]` to attach a sample of up to 5\n recent guest review texts per property. Useful when the user's\n question involves qualitative criteria that don't map to structured\n filters (\"a place with excellent breakfast\", \"quiet area\",\n \"family-friendly atmosphere\"); review texts can be searched\n textually to corroborate or rule out matches.\n For a DEEPER read on ONE specific property — more review texts\n (up to 20) or the per-OTA breakdown — call `get_property_details`\n with `include=[\"reviews_extended\"]` (and/or `reviews_aggregate`).\n `comment_count` on each result tells you how many review texts exist,\n so you can decide whether escalating to the detail call is worth it.\n\n`filters.property_types`, `filters.amenities`, `filters.min_rating`,\nand `filters.price_max_eur` narrow on structured criteria first;\nreview-based reasoning is one extra round-trip per page and is\ntypically reserved for fallback.\n\nLocation modes:\n- `coordinates`: when lat/lon is already known from world knowledge\n or a prior call in this session (default radius 5 km; widen up to\n 50 km for broader queries; beyond that `bbox` or a parent\n destination is the right shape).\n- `destination_id`: opaque id obtained from `resolve_destination`,\n passed verbatim — values are not constructed or guessed.\n- `bbox`: explicit map rectangle.\n\nProperty type tokens (canonical): hotel, apartment, house, villa, bb,\nhostel, farmstay, holiday-home. Common multi-language synonyms map\nserver-side to the canonical set.\n\nAmenities filter is set-AND — each result has ALL listed codes.\nCommon codes: wi-fi, parking, pool, air-conditioning, kitchen, garden,\npets-allowed, for-families, facilities-for-disabled, non-smoking-only.\n\nResults are cursor-paginated; the `next_cursor` from a previous\nresponse goes into `page.cursor` for the next page.\n`location.type=property_id` is not accepted here —\n`get_property_details` is the path for a known property.\n",
"inputSchema": {
"type": "object",
"required": [
"location",
"dates",
"guests"
],
"properties": {
"page": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
},
"description": "Pagination. `limit` caps results per page (1-100). To fetch the next page pass `cursor` with the `next_cursor` value from the previous response — cursors are opaque and are never constructed by hand.",
"additionalProperties": false
},
"sort": {
"enum": [
"score_desc",
"price_asc",
"rating_desc",
"conversion_desc"
],
"type": "string",
"description": "Result ordering. score_desc (default) is the balanced relevance ranking and suits most requests; price_asc when the user asked for the cheapest; rating_desc when they asked for the best-reviewed; conversion_desc favours what comparable travellers actually booked."
},
"dates": {
"type": "object",
"required": [
"check_in",
"check_out"
],
"properties": {
"check_in": {
"type": "string",
"format": "date"
},
"check_out": {
"type": "string",
"format": "date"
}
},
"description": "Check-in and check-out dates (ISO 8601, YYYY-MM-DD). Natural-language references — 'tonight', 'this 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. The comparator only serves future stays: check_in is a date in the real-time calendar that is today or later. A reference like 'the weekend of June 2' resolves to the next occurrence after today, never a past anniversary; if the current year is unclear, confirm with the user before calling. Clock calibration: this schema was published in August 2026, so today is August 2026 OR LATER (likely later — schemas outlive their publish date). An assumed 'today' in 2024/2025 is a stale training-time clock; years before 2026 are always in the past. Past check_in values are rejected at the API boundary with a hint carrying the actual current date.",
"additionalProperties": false
},
"guests": {
"type": "object",
"required": [
"adults"
],
"properties": {
"rooms": {
"type": "integer",
"maximum": 9,
"minimum": 1
},
"adults": {
"type": "integer",
"maximum": 16,
"minimum": 1
},
"children_ages": {
"type": "array",
"items": {
"type": "integer",
"maximum": 17,
"minimum": 0
}
}
},
"description": "Party composition. `adults` is required; give `children_ages` (one age per child, 0-17) rather than a child count — operators price by age. `rooms` defaults to 1; raise it when the party needs separate rooms rather than one shared.",
"additionalProperties": false
},
"filters": {
"type": "object",
"properties": {
"amenities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Preferred amenity codes. This ranks, it does not filter: properties declaring every code listed here come first and nothing is dropped, because upstream declarations are incomplete — an absent code is not evidence the service is missing. Read each result's `amenities` to see what is actually declared. Common: wi-fi, parking, pool, air-conditioning, kitchen, garden, pets-allowed, for-families, facilities-for-disabled, non-smoking-only."
},
"min_rating": {
"type": "number",
"maximum": 5,
"minimum": 0
},
"price_max_eur": {
"type": "number",
"minimum": 0
},
"property_types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Canonical tokens: hotel, apartment, house, villa, bb, hostel, farmstay, holiday-home. Italian/English synonyms (agriturismo, bnb, appartamento, casa, ...) are accepted and normalized server-side."
}
},
"description": "Structured narrowing applied server-side, before ranking. Prefer these over filtering the results yourself: they run across the whole inventory, not just the page you received. Qualitative criteria that do not map here (\"quiet\", \"great breakfast\") are better served by include=['reviews_sample'].",
"additionalProperties": false
},
"include": {
"type": "array",
"items": {
"enum": [
"reviews_sample"
],
"type": "string"
},
"description": "Optional enrichments, always a list — pass [\"reviews_sample\"], never the bare string. 'reviews_sample' attaches the 5 most recent individual reviews per property — use for qualitative queries (breakfast, service, ...). One extra Mongo round-trip per page; omit by default."
},
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$",
"description": "Currency of the returned prices (ISO-4217, 3-letter uppercase, e.g. 'USD', 'EUR', 'GBP', 'CAD'). SET THIS (or `user_country`) to price in the user's currency — if you set NEITHER, prices default to USD, which may not be the user's. Prices come straight from the booking sources in this currency; never convert them yourself. Each offer reflects the currency its operator actually quoted."
},
"language": {
"type": "string",
"pattern": "^[a-z]{2}$",
"description": "User's UI language (2-letter lowercase). Drives the booking link language and any server-rendered narrative content. Pass the language the user is currently speaking. Falls back to 'en' when omitted."
},
"location": {
"type": "object",
"required": [
"type",
"value"
],
"properties": {
"type": {
"enum": [
"destination_id",
"poi_id",
"coordinates",
"bbox"
],
"type": "string",
"description": "Selects the shape of value: destination_id and poi_id take an id string, coordinates takes {lat, lon, radius_km?}, bbox takes {nw: [lat,lon], se: [lat,lon]}. property_id is NOT supported here; use get_property_details."
},
"value": {
"anyOf": [
{
"type": "string",
"description": "destination_id/poi_id: opaque id string from resolve_destination or discover_destinations_near (e.g. 'dest_590c54056664cf2c60c5c2f6'). Pass it verbatim — the 'dest_'/'poi_' prefix is accepted. NEVER pass a free-form name like 'Ancona'; the API rejects bad identifiers with 400 invalid_request."
},
{
"type": "object",
"required": [
"lat",
"lon"
],
"properties": {
"lat": {
"type": "number",
"maximum": 90,
"minimum": -90,
"description": "Latitude in decimal degrees."
},
"lon": {
"type": "number",
"maximum": 180,
"minimum": -180,
"description": "Longitude in decimal degrees."
},
"radius_km": {
"type": "number",
"description": "Search radius in km (default 5).",
"exclusiveMinimum": 0
}
},
"description": "coordinates: a known point, searched within radius_km.",
"additionalProperties": false
},
{
"type": "object",
"required": [
"nw",
"se"
],
"properties": {
"nw": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "North-west corner as [lat, lon]."
},
"se": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "South-east corner as [lat, lon]."
}
},
"description": "bbox: an explicit map rectangle, given by its north-west and south-east corners.",
"additionalProperties": false
}
],
"description": "Shape depends on type — see each option below."
}
},
"description": "Where to search, as a {type, value} pair. Use destination_id for a place resolved via resolve_destination, poi_id for a point of interest, coordinates for a known lat/lon, or bbox for an explicit map rectangle. A property is NOT a location — use get_property_details for a known property.",
"additionalProperties": false
},
"user_country": {
"type": "string",
"pattern": "^[A-Z]{2}$",
"description": "User's country (ISO-3166 alpha-2 uppercase). Drives the booking-link locale (the landing page rendered when the user clicks `web_url`) AND, when `currency` is not set, the pricing currency (US->USD, CA->CAD, euro-area->EUR). Pass the user's own country, not the destination's. Falls back to 'US' when omitted."
},
"availability_mode": {
"enum": [
"strict",
"include_unavailable"
],
"type": "string",
"description": "strict (default): return ONLY properties available for the requested dates. include_unavailable: also return properties with no availability (each tagged availability_status). Use strict unless the user explicitly wants to see sold-out options."
},
"include_overbudget": {
"type": "boolean",
"description": "Opt-in. When few available results fit the budget, also return (in alternatives.overbudget) available properties in the same area just above price_max_eur. Requires filters.price_max_eur."
},
"include_out_of_bounds": {
"type": "boolean",
"description": "Opt-in. When the requested area yields few available results, also return (in alternatives.out_of_bounds) properties just outside the area, within the original budget. Present these explicitly as alternatives, never mixed with primary results."
}
},
"additionalProperties": false
}
}GET /api/tool/b2a.bluepillow.com/webmcp/search_stays