Skip to main content
Search live hotel inventory and nightly rates for a destination and stay window. It handles requests like “three nights in Lisbon next weekend for two adults” or “family room in Rome with one child in August”. Each result includes an htl_* offer token you can hand to the trip tool to start a booking.
Search live hotel inventory and rates.

REQUIRED:
- destination: object — two distinct modes. Mode A (rate lookup): { hotel_name (+ optional country_code, city_name) } or { hotel_ids }. Mode B (hotel search): { query }, { city_name + country_code }, { latitude + longitude (+ radius_km) }, or { place_id }.
- checkin, checkout: YYYY-MM-DD
- occupancy: either { occupancies: [{ adults, children_ages? }, ...] } (one entry per room) OR shorthand { adults, children?, rooms? }

TWO MODES — pick deliberately:

MODE A (rate lookup — the user named a specific hotel):
- { hotel_name }: free-text hotel name ("Hotel Calimala", "The St. Regis Rome", "Hôtel Costes"). Server fuzzy-matches against a 1.74M-hotel catalog. ALWAYS pair with country_code AND city_name when known — lookup precision drops sharply on common names without scope. Returns 422 HOTEL_NAME_LOW_CONFIDENCE if no candidate scores ≥ 0.7; see "ERROR HANDLING" below.
- { hotel_ids }: re-shop a known set (from a prior search result).
In Mode A: filters are ignored (user named the property), and the response includes nearby_alternatives — up to 5 hotels within ~2km of the matched property, in the same response shape so the user can compare.

MODE B (hotel search — the user is exploring a destination):
- { query }: unambiguous cities or well-known POIs only ("Paris", "Times Square"). Provider AI search returns 0 for islands ("Menorca", "Santorini", "Mykonos"), regions ("Tuscany", "Provence", "Bavaria"), countries, archipelagos. Do NOT use { query } for those.
- { city_name + country_code }: when the user named a city, even if ambiguous. Best when the destination has a primary city ("Mahón, ES" for Menorca; "Florence, IT" for Tuscany).
- { latitude + longitude + radius_km }: when the destination is an area, island, or region with no obvious primary city. radius_km up to 50.
- { place_id }: when you already have an upstream Place ID.
If the user names something non-city (an island, region, archipelago, neighborhood), DO NOT pass it as { query } — pick { city_name+country_code } or { latitude+longitude+radius_km }.

ERROR HANDLING — 422 HOTEL_NAME_LOW_CONFIDENCE (Mode A only):
When { hotel_name } fuzzy lookup finds no candidate ≥ 0.7, the response body is:
  { "error": { "code": "HOTEL_NAME_LOW_CONFIDENCE", "message": "...", "top_candidates": [{hotel_id, name, city, score}], "suggested_retry": { "destination": {...} } } }
This is ACTIONABLE, not fatal. Pick one:
  1. Top candidate matches what the user meant (typo/spelling) → confirm with the user, then retry with { hotel_ids: ["<top.hotel_id>"] }.
  2. None of the candidates fit → ask the user "I couldn't pin down 'X' — want me to search all hotels in <city> instead?" then retry with suggested_retry.destination.
  3. User likely meant a different city → ask to clarify, then retry hotel_name with corrected country_code/city_name.
Don't silently auto-pick a low-confidence candidate — booking the wrong hotel is worse than asking one clarifying question.

OPTIONAL:
- currency (ISO 4217), guest_nationality (ISO 3166-1 alpha-2)
- filters: { min_rating, min_star_rating, max_star_rating, min_reviews, hotel_type_ids, chain_ids, facility_ids, max_results }

EXAMPLES:
1. Free-text city search:
   { "destination": { "query": "Paris" }, "checkin": "2026-07-15", "checkout": "2026-07-18", "adults": 2 }

2. Two-room family with kids:
   { "destination": { "city_name": "Barcelona", "country_code": "es" }, "checkin": "2026-08-01", "checkout": "2026-08-05",
     "occupancies": [{ "adults": 2 }, { "adults": 1, "children_ages": [5, 7] }] }

3. Island/region (use lat-lng + radius, NOT query):
   { "destination": { "latitude": 39.9496, "longitude": 4.1102, "radius_km": 30 }, "checkin": "2026-08-31", "checkout": "2026-09-09", "adults": 1 }

4. Geo-radius search around a POI:
   { "destination": { "latitude": 41.4036, "longitude": 2.1744, "radius_km": 3 }, "checkin": "2026-09-10", "checkout": "2026-09-12", "adults": 2 }

5. Mode A — user named a specific hotel:
   { "destination": { "hotel_name": "Hotel Calimala", "country_code": "it", "city_name": "Florence" }, "checkin": "2026-07-15", "checkout": "2026-07-18", "adults": 2, "currency": "EUR" }

RESPONSE:
- Each hotel includes basic metadata (name, address, star_rating, rating, main_photo) and a rooms[] list.
- Each room has a rates[] list. Each rate has an offer_id (the htl_* token), board type, refund policy, and total_amount + currency.

NEXT STEP: Use the trip tool with add_item and the offer_id of the rate the user picks. Hotel items work alongside flight items in the same cart.

Flow: hotel_search → trip(add_item) → trip(upsert_travelers) → book

Parameters

NameTypeRequiredDescription
destinationobjectYesDestination — provide exactly one shape. Two distinct modes: MODE A (rate lookup — you know which hotel): • { hotel_ids } to re-shop a known set. • { hotel_name, country_code?, city_name? } when the user named a specific property. Server resolves via fuzzy lookup; returns 422 HOTEL_NAME_LOW_CONFIDENCE if no candidate scores ≥ 0.7. MODE B (hotel search — you’re exploring): • { query } for unambiguous cities/POIs only (“Paris”, “Times Square”). Provider AI search returns 0 for islands/regions/countries/archipelagos. • { city_name + country_code } for ambiguous city names or when the user named a primary city (“Mahón, ES” for Menorca). • { latitude + longitude + radius_km } for islands, regions, neighborhoods — anywhere { query } returns 0. • { place_id } when you already have an upstream Place ID. In Mode A, filters (min_rating / star / facility / chain / hotel_type / max_results) are ignored — the user already named the property. Best practice: don’t combine filters with hotel_ids or hotel_name.
destination.querystringNoFree-text destination — use ONLY for unambiguous cities or well-known POIs (e.g. “Paris”, “Times Square”). Provider-side AI search reliably returns 0 results for islands (“Menorca”, “Santorini”), regions (“Tuscany”, “Provence”), countries, archipelagos, and other non-city geographies. For those: prefer { city_name + country_code } when a primary city is implied (e.g. “Mahón, ES” for Menorca), or { latitude + longitude + radius_km } to cover an area. Max 200 chars.
destination.city_namestringNoCity name (e.g. “Paris”). In Mode B: pair with country_code as the destination. In Mode A (with hotel_name): pair with hotel_name to narrow the fuzzy-lookup scope.
destination.country_codestringNoISO 3166-1 alpha-2 country code (lowercase preferred, e.g. “fr”). Must be paired with city_name OR hotel_name. In Mode B it disambiguates the city; in Mode A it narrows the fuzzy lookup.
destination.latitudenumberNoLatitude for geo-radius search. Pair with longitude.
destination.longitudenumberNoLongitude for geo-radius search. Pair with latitude.
destination.radius_kmnumberNoGeo-radius in km (default 5, max 50). Requires latitude+longitude — ignored with other destination shapes.
destination.place_idstringNoGoogle Places ID or upstream place identifier.
destination.hotel_idsarray<string>NoMode A — re-shop a known set of hotel IDs (skips destination resolution).
destination.hotel_namestringNoMode A — free-text hotel name when the user named a specific property they want to book (e.g. “Hotel Calimala”, “The St. Regis Rome”, “Hôtel Costes”). Server runs a fuzzy trigram match over the 1.74M-hotel catalog; uses the top hit when its confidence is ≥ 0.7 (auto-pick safe). If no candidate clears that threshold, returns HTTP 422 with error.code = HOTEL_NAME_LOW_CONFIDENCE + top_candidates + suggested_retry — the agent should confirm a candidate with the user or fall back to the suggested city destination. ALWAYS pair with country_code and city_name when known; lookup precision drops sharply on common names without scope.
checkinstringYesCheck-in date (YYYY-MM-DD).
checkoutstringYesCheck-out date (YYYY-MM-DD).
occupanciesarray<object>NoOne entry per room (structured). PREFERRED whenever the party is larger than 2 adults or has children — it removes ambiguity about how guests are split across rooms. Example: [{ “adults”: 2 }, { “adults”: 1, “children_ages”: [5] }]. Do NOT combine with the shorthand fields (adults/children/rooms) — use one or the other.
adultsintegerNoShorthand: total adults across all rooms. Use ONLY when there is no ambiguity (1 or 2 adults = single room). For 3+ adults, or odd splits, ask the user how they want to split rooms and pass occupancies[] instead — e.g. 4 adults → [{adults:2},{adults:2}] (double + double) vs a single 4-sleeper is a meaningfully different search. If the user does not clarify, the server defaults to 2-adults-per-room (remainder in the last room), so every 4-adult request becomes two double rooms. Do NOT combine with occupancies[] — use one or the other.
childrenarray<integer>NoShorthand: ages of all children across all rooms. If children are present, prefer occupancies[] so the caller controls which room each child goes in (ages affect pricing and some providers reject invalid age/room combinations).
roomsintegerNoShorthand: number of rooms (BFF auto-distributes adults + children). Use when the user named a room count but not the per-room split.
currencystringNoISO 4217 currency code (e.g. “EUR”, “USD”, “GBP”, “JPY”). ALWAYS set this — omitting it falls back to USD which is rarely what users actually want. Infer from the strongest signal available: (1) the user’s explicit ask (“show me prices in pounds”), (2) the conversation language/locale (French → EUR, Japanese → JPY, German → EUR, Spanish/Catalan/Italian/Portuguese → EUR, English UK → GBP, English US → USD), (3) the destination country (Italy/France/Germany → EUR, UK → GBP, Japan → JPY, US → USD). When in doubt between two plausible currencies, prefer the one matching the user’s likely home country.
guest_nationalitystringNoGuest nationality (ISO 3166-1 alpha-2, uppercase, e.g. “FR”). Affects rate availability + tax handling at search time. Separate from traveler nationality used for booking documents.
filtersobjectNoOptional filter overrides applied on top of the tenant default filter set.
filters.min_ratingnumberNoMinimum guest review rating (0-10 scale).
filters.min_star_ratingintegerNoMinimum star rating (1-5). Pair with max_star_rating to bound a range.
filters.max_star_ratingintegerNoMaximum star rating (1-5). Pair with min_star_rating to bound a range.
filters.min_reviewsintegerNoMinimum number of guest reviews.
filters.hotel_type_idsarray<string>NoFilter by hotel type IDs (e.g. boutique, resort).
filters.chain_idsarray<string>NoFilter by hotel chain IDs (e.g. “hilton”, “marriott”, “accor”, “ihg”).
filters.facility_idsarray<string>NoFilter by facility IDs (e.g. pool, gym, spa). Applied at connector search time, not post-filtered.
filters.max_resultsintegerNoMaximum number of hotels to return per page (default 50).
filters.offsetintegerNoPagination offset — skip this many hotels at the upstream search (default 0).
trip_idstringNoExisting trip_id to associate this search with. Unified (cart-widget) tool only — the DevPlatform variant accepts the field for schema consistency but ignores it (no widget surface). Forward whenever the user is mid-trip-build (you have seen a trip_id in a recent trip(…) tool result and the user has NOT pivoted to a different trip context, e.g. a new origin OR destination city or unrelated request). Drop on pivot. Echoed back in the result so the cart widget can append the next selection to the same trip.

Examples

Simple city search, 2 adults, 3 nights in Paris:
{
  "name": "hotel_search",
  "arguments": {
    "destination": { "query": "Paris" },
    "checkin": "2026-07-15",
    "checkout": "2026-07-18",
    "adults": 2
  }
}
Two rooms, kids in one:
{
  "name": "hotel_search",
  "arguments": {
    "destination": { "city_name": "Barcelona", "country_code": "ES" },
    "checkin": "2026-08-01",
    "checkout": "2026-08-05",
    "occupancies": [
      { "adults": 2 },
      { "adults": 1, "children_ages": [5, 7] }
    ]
  }
}
Near a coordinate with filters:
{
  "name": "hotel_search",
  "arguments": {
    "destination": {
      "latitude": 48.8566,
      "longitude": 2.3522,
      "radius_km": 3
    },
    "checkin": "2026-07-15",
    "checkout": "2026-07-18",
    "adults": 2,
    "filters": {
      "min_rating": 8,
      "min_star_rating": 4,
      "facility_ids": ["pool", "wifi"],
      "max_results": 20
    }
  }
}
Each hotel’s rooms[].rates[] includes a offer_id starting with htl_. Pass it as a trip_item_token to trip(add_item), hotels coexist with flight items in the same trip.