GET /v1/hotels/searches/{searchId}/results
Lean list cards; default 10, max 50 per page. searchComplete:true terminal, false advisory; poll snapshotCandidateCount to a steady non-zero. totalCandidates===0 = filters only if totalBeforeFilters>0, else none bookable once complete. ?refundable=true = witnessed.
Operation ID: getHotelSearchResults
Parameters
| Name | In | Type | Required | Constraints | Description |
|---|---|---|---|---|---|
searchId |
path | string | yes | pattern ^[A-Za-z0-9._:~=-]{1,256}$ |
The opaque searchId returned by createHotelSearch. Ids expire; a 404 means the search is unknown or gone – create a new one. |
page |
query | integer | no | 1..100; default 1 |
Page number, 1-based (max 100). Defaults to 1. |
pageSize |
query | integer | no | 1..50; default 10 |
Results per page (1-50). Defaults to 10. |
sort |
query | relevance | price_asc | price_desc | star_desc | review_score_desc | guest_rating_desc | distance_asc |
no | default "relevance" |
Sort order. relevance (default) is the metasearch ranking; price_asc / price_desc by cheapest per-night rate; star_desc by star; review_score_desc by guest score; guest_rating_desc by the ?guest-type= cohort’s own score, which requires that param (a hotel upstream did not score for the cohort sorts last, never zero-filled); distance_asc by distance to the city’s place-record coordinate (see distanceToCityCentre). With ?refundable=true the price sorts key off the cheapest refundable rate. |
currency |
query | string | no | pattern ^[A-Z]{3}$; default "USD" |
Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Defaults to USD. |
locale |
query | string | no | length 1..35; default "en" |
Response language tag (e.g. en, ar). Defaults to en. |
min-star |
query | integer | no | 1..5 | Keep hotels with at least this star rating (1-5). |
max-star |
query | integer | no | 1..5 | Keep hotels with at most this star rating (1-5). |
min-review-score |
query | number | no | 0..10 | Keep hotels whose ALL-GUESTS review score is at least this (0-10). This is the everybody-rated-it-well question; for a named guest cohort use ?guest-type= with ?min-guest-rating=, which is a different number on most hotels. |
guest-type |
query | business | couple | family | solo |
no | - | The guest cohort ?min-guest-rating= and sort=guest_rating_desc judge a hotel by: business, couple, family or solo. Read the vocabulary and this snapshot’s per-cohort hotel counts from metadata.filterOptions.guestTypes. Must be sent with ?min-guest-rating= or sort=guest_rating_desc, and both of those require it - a cohort with nothing to apply it to is rejected rather than silently ignored. Spelled differently from the /reviews ?guest-type= vocabulary (family here, family_with_children there) because the two upstreams segment guests differently; business exists only here and extended_group only there. |
min-guest-rating |
query | number | no | 0..10 | Keep hotels the ?guest-type= cohort rates at least this (0-10). Requires ?guest-type=. Judged on that cohort’s own score, not the all-guests one: on a settled 420-hotel snapshot, of the 299 hotels scored for both, 58% had a family score at least 3 points from their overall one. A hotel upstream did not score for the cohort is DROPPED, and that absence means too little cohort data rather than a low score - upstream publishes no thin cohort rows, so a cohort score it does publish rests on more reviews than the all-guests figure sometimes does. |
min-price |
query | number | no | 0..1000000 | Minimum price (inclusive), in the response currency. Bounds the all-in nightly figure: amountPerNight plus every per-night charge the card publishes beside it, localTaxPerNight and taxAmountPerNight where present. That figure covers every room in the search, so multiply a per-room budget by the room count in stay.occupancy.rooms. Read metadata.filterOptions.priceRange for the bounds this snapshot spans. With ?refundable=true it bounds the cheapest refundable rate. |
max-price |
query | number | no | 0..1000000 | Maximum price (inclusive), in the response currency. Bounds the all-in nightly figure: amountPerNight plus every per-night charge the card publishes beside it, localTaxPerNight and taxAmountPerNight where present. That figure covers every room in the search, so multiply a per-room budget by the room count in stay.occupancy.rooms. Read metadata.filterOptions.priceRange for the bounds this snapshot spans. With ?refundable=true it bounds the cheapest refundable rate. |
refundable |
query | 0 | 1 | true | false |
no | - | Keep only hotels with a witnessed refundable Book-on-Wego rate, so a ‘cheapest refundable’ answer needs no per-hotel /rates calls. Exactly equivalent to ?rate-types=free_cancellation, and combines with it: this is the one rate type that has its own param. This is an UNDER-approximation: the results envelope is a rate sample, so a true keeps hotels with a seen refundable rate and a hotel’s absence is not authoritative – only GET /v1/hotels/{hotelId}/rates can prove a hotel has no refundable rate. Accepts true or false. |
rate-types |
query | string[] | no | items 1..* | Keep hotels with a witnessed Book-on-Wego rate carrying ALL listed rate types (AND across terms; repeat or comma-separate), so ‘only rooms with breakfast’ needs no per-hotel /rates calls. Read the vocabulary from metadata.filterOptions.rateTypes (e.g. breakfast_included, free_cancellation) – unlike every other list filter these are matched EXACTLY (case-insensitively), not as substrings, because they are stable upstream codes rather than localized display names. Several terms mean ONE rate carrying all of them, and while any rate-type filter is active the card price, the price bounds, the price sorts and the cheapest badge all key off that matching rate. Like refundable, an UNDER-approximation: the results envelope is a rate sample, so a hotel’s absence is not proof it lacks the type – only GET /v1/hotels/{hotelId}/rates can prove that. |
deals-only |
query | 0 | 1 | true | false |
no | - | Keep only hotels whose card carries a price.deal, mirroring the ‘today’s deals’ filter on wego.com. Judged on the very price object the card publishes, so the kept hotels and the deals shown always agree – with ?refundable=true that means the refundable rate must be the discounted one. Like refundable, this is an UNDER-approximation: the results envelope is a rate sample that grows while searchComplete is false, so a hotel’s absence is not proof it has no discount. Accepts true or false. |
amenities |
query | string[] | no | items 1..* | Keep hotels offering ALL listed amenities (AND across terms; repeat or comma-separate). Each term is matched case-insensitively as a substring against the name field in metadata.filterOptions.amenities – pick terms from there (e.g. Fitness Centre), not a guessed synonym (gym). One term can span several values (Pool also matches Indoor Pool). |
property-types |
query | string[] | no | items 1..* | Keep hotels whose property type matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.propertyTypes – pick from there rather than guessing. |
brands |
query | string[] | no | items 1..* | Keep hotels whose brand matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.brands – pick from there rather than guessing. Names parent companies as well as individual brands, and a term matches an entry name rather than a corporate relationship, so a group filed under several sibling brands needs each of those names listed. |
chains |
query | string[] | no | items 1..* | Keep hotels whose chain matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.chains – pick from there rather than guessing. Most hotels carry no chain and some entries name a loyalty programme, so a hotel group may be reachable only through brands, or split across both vocabularies. |
districts |
query | string[] | no | items 1..* | Keep hotels whose district matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.districts – pick from there rather than guessing. |
view |
query | card |
no | default "card" |
Response projection. card is the only value: the lean results-list projection (price summary, refundability witness, star/review, location names). The former default projection was removed in issue #1308 – read GET /v1/hotels/{hotelId} for a hotel’s amenities, images and address. |
Responses
| Status | Description |
|---|---|
200 |
Ranked hotels, as lean list cards. Amenities, the full image list, the address and brand/chain are not on a card – read the hotel for the one row you picked. |
400 |
Invalid request parameters. |
401 |
Missing or invalid bearer token. |
404 |
Unknown or expired search. |
429 |
Rate limit exceeded; retry after the Retry-After seconds. |
502 |
The upstream hotels service returned an invalid response. |
503 |
The hotels service is unavailable (upstream_unavailable) or rate-limited upstream (upstream_rate_limited); retry after the Retry-After seconds. |
200 body
| Field | Type | Always present | Description |
|---|---|---|---|
searchId |
string | yes | The id of the search this snapshot belongs to. |
currencyCode |
string | yes | Currency the prices in this snapshot are quoted in. |
searchComplete |
boolean | yes | Upstream aggregation flag. true is authoritative/terminal; conclude NO BOOK-ON-WEGO BOOKABLE INVENTORY only when true AND metadata.totalBeforeFilters === 0 – never that no hotel exists, since only Book-on-Wego inventory was requested. A zero totalCandidates on its own means only that this read’s filters matched nothing, and an empty page with totalCandidates > 0 is pagination. false is inconclusive, so watch metadata.snapshotCandidateCount convergence to stop sooner. |
stay |
object | no | What upstream priced: the dates and occupancy every price on this read covers. Present when upstream states them. |
↳ checkIn |
string | yes | Check-in date priced upstream, YYYY-MM-DD. |
↳ checkOut |
string | yes | Check-out date priced upstream, YYYY-MM-DD. |
↳ nights |
integer | yes | Nights between checkIn and checkOut. price.total covers this many nights of price.amountPerNight. |
↳ occupancy |
object | yes | The occupancy priced upstream for this search (ages resolved, incl. fallback). |
metadata |
object | yes | Pagination, the snapshot’s filter vocabulary, the settle counters for this read, and what it resolved currency and locale to. |
↳ page |
integer | yes | 1-based page number of this snapshot. |
↳ pageSize |
integer | yes | Hotels requested per page. |
↳ resultCount |
integer | yes | Hotels on this page. The page only – judge a filter on totalCandidates. |
↳ totalCandidates |
integer | yes | Hotels matching this read’s filters across the snapshot – the count that judges a filter, not the page. A filter that matched nothing is totalCandidates 0 with totalBeforeFilters above 0. |
↳ totalBeforeFilters |
integer | yes | Hotels that survived the Book-on-Wego join, before this read’s filters ran. A filter that matched nothing is totalCandidates === 0 with totalBeforeFilters > 0. Zero means no Book-on-Wego-bookable inventory surfaced for these dates – it is NOT proof that no hotel exists, since the join runs over a sampled rate list. Equal to totalCandidates on an unfiltered read. |
↳ filterOptions |
object | yes | The filterable vocabulary of the hotels in this snapshot, ordered by count, over the same population as totalBeforeFilters. The amenities / property-types / brands / chains / districts query params take an entry’s name field VERBATIM (matched case-insensitively as a substring), so pick from here rather than guessing a synonym. A term listed here matches AT LEAST its count on an unfiltered read; the count is a lower bound, since one term can span several values (Pool also matches Indoor Pool). rateTypes and guestTypes are the exceptions: their names are stable codes, matched exactly rather than as substrings, so their counts are precise rather than a lower bound. guestTypes counts a smaller population than the rest for a different reason - it counts only the hotels upstream scored for that cohort, and upstream publishes no thin cohort rows, so a hotel absent from a cohort has too little data for it rather than a poor rating. Still growing while searchComplete is false. priceRange does the same job for the numeric bounds: it states the span min-price / max-price are measured on, so read it before choosing either. |
↳ hasMore |
boolean | yes | Another page of hotels follows. |
↳ snapshotCandidateCount |
integer | yes | Upstream aggregation counter – the practical early convergence signal. Two spaced (not back-to-back), equal, non-zero reads ≈ settled enough to render; it stabilizes well before searchComplete flips, so use it to stop polling sooner. A heuristic, not proof of completion (searchComplete:true is that). Not the same as totalCandidates (the post-Book-on-Wego-join hotel count). |
↳ createdAt |
string | no | When this search was created (ISO 8601) – the freshness anchor for these prices. A search older than about 10 minutes may answer 404 as expired. Absent when the search service omits it. |
↳ currencyCode |
string | yes | The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice. |
↳ currencyCodeSource |
explicit | default |
yes | How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant. |
↳ locale |
string | yes | The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose). |
↳ localeSource |
explicit | default |
yes | How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for. |
results |
object[] | yes | The requested page of ranked hotels, as list cards. |
↳ hotelId |
number | yes | The hotel’s numeric id; read its detail with GET /v1/hotels/{hotelId}. |
↳ name |
string | yes | Hotel display name. |
↳ pageUrl |
string | yes | The hotel’s page on wego.com. It is not tied to a search, so it keeps working after this search expires. Give it to a traveller who wants to look at the hotel, and use it in anything that is saved or sent on. It opens with no dates set. |
↳ star |
number | no | Star rating (1-5), when classified. |
↳ review |
object | no | Aggregate guest review score and count. |
↳ reviewsByGuestType |
object | no | Guest review score and count per cohort, on the same 0-10 scale as review (which is the all-guests figure). These are the exact values ?guest-type= accepts and the same vocabulary metadata.filterOptions.guestTypes counts. A cohort is present only when upstream scored it, and an ABSENT cohort means too little data for that cohort, never a low score - upstream publishes no thin cohort rows. Read these to explain a pick as well as make one: a hotel rated 8.5 overall and 7.6 by families is a different recommendation than one rated 8.5 by both. Omitted when no cohort was scored. |
↳ price |
object | yes | A card’s per-night and stay price. Every amount covers the whole booking, all rooms in the search, so quote these figures as they stand. Upstream rounds per booking, so total is the exact stay figure and amountPerNight is one night of it. Any per-night figure published beside amountPerNight is charged ON TOP of it: add localTaxPerNight, and taxAmountPerNight when it appears, to reach what a guest pays and what the price sorts and bounds rank on. |
↳ refundable |
available | unknown |
yes | Refundability WITNESS, not a boolean. ‘available’ = a free-cancellation Book-on-Wego rate was seen in this snapshot. ‘unknown’ = none was seen, which is NOT evidence that none exists – this envelope carries only a sample of each hotel’s rates, so a negative is not computable here. To answer ‘does this hotel have a refundable room’, read the hotel’s rooms/rates. |
↳ lowestRefundablePrice |
object | no | Lowest refundable Book-on-Wego rate. Present exactly when refundable is ‘available’. |
↳ rateTypes |
string[] | no | Rate types WITNESSED on this hotel’s Book-on-Wego rates in this snapshot (e.g. breakfast_included, free_cancellation), sorted. A witness on the same terms as refundable, never a negative: this envelope carries only a sample of each hotel’s rates, so an absent type is not evidence the hotel lacks it. These are the exact values ?rate-types= accepts, and the same vocabulary metadata.filterOptions.rateTypes counts. Omitted when no rate type was witnessed. |
↳ cityName |
string | no | City the hotel is in. |
↳ districtName |
string | no | District or neighbourhood the hotel is in. |
↳ lat |
number | no | Hotel latitude in decimal degrees. Use with lng to compute distance to any landmark you choose. |
↳ lng |
number | no | Hotel longitude in decimal degrees. Use with lat to compute distance to any landmark you choose. |
↳ distanceToCityCentre |
number | no | Kilometres from the city’s place-record coordinate (the point GET /v1/places reports for the city), and the ?sort=distance_asc key. Where a city record covers an island, a city-state or a whole administrative area, that point can sit far from the commercial centre. For locality prefer the districts filter, or compute distance from lat/lng to a landmark you choose. Present when upstream reports it. |
↳ image |
string | no | Primary image URL, if any. |
↳ badges |
string[] | yes | Full-result-set badges this hotel wins (e.g. cheapest). |
Example
curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.wego.com/v1/hotels/searches/$SEARCH_ID/results"