Search DACH hotel properties, and price them for a stay in the same call.
ASK BEFORE YOU SEARCH. Four things are required and this tool answers nothing without them: `checkIn`, `checkOut`, `adults` and `maxPricePerNight`. "Hotels in Berlin for a couple" is not yet a search — it names a place and a party and nothing else, so ask which nights they want, who is coming — adults AND any children, by age — and roughly what they want to spend, in ONE message, and search once they answer. Never fill these in for them: dates you chose are not their dates, and a budget read off "cheap" or off a star rating hides the very hotels they could have afforded. A call missing any of them comes back with no rows and a `hint` naming what is still to be asked.
PLACES. Pass `city` with any DACH place name to get properties around it, nearest first, each with `distanceKm` — despite the name it takes a place of any size, so a village or resort ("Silvaplana", "Wals", "Zell am See") works as well as a city. `radius` is a value with its unit — {"value": 10, "unit": "mi"} — so you never have to convert; it defaults to 20 km and may not exceed 100 km. Prefer `city` over `query` for place-based searches: it understands ~105,000 places and returns neighbouring towns, whereas `query` only matches words in a name or description. An unresolvable place name returns `placeNotRecognised` — that means the NAME was not understood, not that the place has no hotels. When a radius returns nothing you get `nearestBeyondRadiusKm` so you can widen deliberately rather than guess.
A STAY. `checkIn`, `checkOut` and `adults` are required, and when you ask how many adults, ASK WHETHER ANY CHILDREN ARE COMING TOO and send their ages in `childrenAges` — ages, not a count. A child left out is priced as though they are not coming, so the guest can book a room that will not sleep them. The moment the guest mentions a child, put it in `childrenAges` even if you do not yet know the age — send `null` for that entry: THE SEARCH IS THEN REFUSED until you replace it with the real age, which is what stops a family being quoted as a couple. With those, the answer comes back in TWO groups. `priced` holds the hotels we read real rates for: every room and rate plan with its own `eurPrice` for the WHOLE stay, its `cancelTerms` where we hold any and null where we do not, its photos where we have them, a `bookingUrl` carrying the guest's dates, and `stayTotalEur` / `pricePerNightEur` for the cheapest offer. They come back in a deliberate order that is NOT nearest first and NOT cheapest first — show them in the order you are given. Show all the rooms you are handed, not just the cheapest. `unpriced` holds everything else: hotels that do not publish rates anywhere we can read, that did not answer us, or that were past the read ceiling. Those are the same thing to you — each carries `bookingUrl` where we hold one, with `datesApplied` and `partyApplied` saying whether the guest's stay is already in the link or has to be typed again on that page. WHEN YOU RECOMMEND A HOTEL, RECOMMEND A PRICED ONE. We hold no rate for anything in `unpriced`, so you cannot say what it costs and it may be well over what the guest wants to spend; naming one as your suggestion answers their budget with a number nobody has. Bring one up only as "I could not get a rate for X, here is their booking page". A `bookingUrl` of null means we hold no BOOKING link: say so, and do not invent one — but we still hold `websiteUrl`, the property's own site, so offer that as its website and never as a way to book or check rates, since we do not know it sells rooms online. An EMPTY `websiteUrl` is the opposite case: we know where to book this property but never found a site of its own. Offer the bookingUrl and say nothing about a website — do not go looking for one.
AN EMPTY `priced` IS NOT "SOLD OUT", AND CHECK `aboveBudget` BEFORE CALLING IT UNREADABLE. With a `maxPricePerNight`, an empty `priced` means every hotel we priced costs more than that, and those hotels are in `aboveBudget` with real prices — quote them. Only when `aboveBudget` is empty too does an empty `priced` mean no rates could be read, and even then we get the same empty answer for a connection that failed, a party the property will not sell to, and a genuinely full hotel, and it never says which — so report the price as unknown and hand over the links, NEVER that a property is full. No ROW in this response can tell you a hotel has no space. One thing can: the `hint`, which says how many properties were left out because they told us outright that they are full — that is why they are in no group at all, and it is the only "full" this tool ever reports. It is never about a hotel you can see.
A BUDGET IS A FILTER, NOT A LABEL — AND IT IS THE GUEST'S NUMBER, NEVER YOURS. `maxPricePerNight` is required, so ask them for a figure before searching rather than sending one of your own. Words like "budget", "cheap" or "affordable" are not amounts: read as one they filter out the very hotels the guest wanted, and the answer then tells them their limit was missed when they set no limit. A guest who says they have no limit still has a most-they-would-pay — ask for that and send it. `maxPricePerNight` decides what you are handed, at both levels. `priced` holds ONLY the hotels at or under it, each carrying ONLY its rooms at or under it — so every price you read out is one the guest already agreed to, and `priced` may be shorter than the `limit` you asked for. Dearer hotels are NOT in `priced`, NOT in `unpriced`, and not returned at all while any hotel fits: do not tell the guest this is every hotel in the area, and read the `hint`, which says how many were left out. Only when NOT ONE hotel fits do they come back, in `aboveBudget` — then say what the nearest options actually cost, never "nothing is available". A room in `priced` keeps ALL of its rate plans, including one dearer than the budget, so a breakfast or flexible rate is still there to offer as an upgrade. The comparison is `pricePerNightEur`, the stay total divided by nights — a stay AVERAGE, not a rate charged on every night, so say it that way.
HOW MUCH ONE SEARCH RETURNS. `limit` is the ceiling across BOTH groups, priced first — an answer can be shorter when properties were dropped as full, defaulting to 10 and capped at 20. `unpriced` carries AT MOST 5 however large a `limit` you ask for, so raising `limit` buys you more PRICED hotels and never a longer list of names we hold no price for. THERE IS NO PAGING: one search returns one set of rows and there is no second page to ask for, so to reach anything past 20 properties, narrow the search — a nearer `city`, a smaller `radius`, a `query`, `country` or `starRating`. Calling again with the same filters returns the same rows. No count of the whole match comes back either, so do not tell the guest how many properties exist in a place — say what you can show them.
Also supports country (de, at, ch), starRating (1-star, 2-star, 3-star, 4-star, 5-star) and style (boutique), which are independent of one another. Returns public fields only — no trust_score raw values.
BOOKING A ROOM FROM THIS ANSWER. Every priced room carries an `offerId`, and we can book it for the guest ourselves — call `hotel_reserve` with the `offerId` and the guest never leaves this conversation. These rooms also carry a `bookingUrl`, the property's own booking page, which works exactly as it always has: hand it over whenever that suits the guest better, and whenever you cannot call `hotel_reserve`. It needs the guest's full name, email, phone and address, so ask for those once they have chosen a room. It answers one of two ways: a `confirmationNumber` means the room is booked and the guest pays the hotel at the desk, and a `paymentUrl` means the room is not held yet and that link is where the guest pays to get it booked. Where a room's `cancelTerms` is null we hold no cancellation terms for it: say so plainly, say the hotel's own terms apply and come with its confirmation, and book once the guest agrees on that basis — a blank is not a refusal.