Skip to main content
POST
Hotel search is the shopping step of the flow: search → rooms → prebook → book → manage. It takes the destination from Destination Autocomplete, the guest’s nationality from Nationalities and the stay details, and returns one page of hotel cards. Each card has a price object that tells you what to show for that hotel. Prices usually are not all known when the first page returns. Search responds quickly, and suppliers are asked in the background. You then poll Get Search Price Updates to fill the prices in. When a guest picks a hotel, call Get Hotel Rooms for bookable offers.
Search prices are shopping data, not a booking confirmation. Always request fresh room options for the chosen hotel and prebook immediately before booking.
Sandbox only has hotels in 12 test cities, such as Dubai, Paris, Tokyo and Bangkok. Search with a destination that autocomplete returned in the same environment. A destination outside those cities, or an ID copied from Production, returns no hotels even when the request is valid.

The search flow

1

Search

Send POST /hotels/search. Keep data.search_id and render data.hotels in the order returned.
2

Render each card by price status

Show an amount for live and recent, a spinner for checking, “sold out” for no_price and “unavailable” for unpriced.
3

Poll while results are visible

If data.refresh_status is PENDING, call the price endpoint every 2 seconds and replace each card’s price. Stop on COMPLETE or EXPIRED.
4

Page and refine

Send data.next_cursor as cursor for the next page. Start from page 1 again when the guest changes a filter or the sort.

Request example

Request field guide

Destination, dates and guests

Paging and display

Filters

Filter values come from Get Hotel Filters. Filters apply to place searches only; a single-hotel search ignores them.

Pricing modes

Even in live mode some hotels can still be checking when the wait ends. Check data.refresh_status: if it is PENDING, keep polling.

Sorting

data.search_info.price_sort shows the sort actually applied. If a sort cannot be applied for a destination, the list falls back to recommended. price.sort_basis is quote when the card’s position in a price sort rests on a stored supplier quote and estimate when it rests on an internal estimate. It is null for other sorts. Never display the estimate. Keep the server’s order and update prices in place. Do not re-sort the loaded cards when prices arrive, or later pages will not line up with them.

Response example

Response field guide

Envelope

Hotel card (data.hotels[])

The price object

What to show for each status

price.reason for unpriced: not_mapped (no enabled supplier offers this hotel, do not retry), supplier_error, timeout, not_queried, no_result. Do not show these codes to guests. Estimates never leave the server. A card with amount: null has no number to show, ever, until a supplier quotes.

Several suppliers

supplier_prices lists one quote per supplier that has answered, for example Tripjack and RezLive. The same hotel_id can appear with several supplier_code values. price shows the lowest of them, but you may show or prefer any quote (lowest, latest, a preferred supplier). Key quotes by hotel_id plus supplier_code. The search quote is indicative. The price you charge is the offer you take from Get Hotel Rooms and confirm with Prebook a Room.

Rooms and children

  • rooms has one entry per room. Search prices cover all of them.
  • Each room holds at most 5 people (adults plus children). Split larger groups across rooms.
  • child_ages lists the age of each child. A 5-year-old and a 12-year-old can price differently, so never send only a count.
  • Use exactly the same rooms, dates and nationality for room availability. A different occupancy is a different price.

Currency

price.amount and every supplier_prices[].amount are integer minor units of the accompanying currency. The currency is your organization’s display currency (USD unless configured) and you can override it per request with the X-Unifystays-Currency header. Amounts are converted from USD with the stored active exchange rate; if a rate is unavailable the search returns HTTP 503 rather than a wrongly labelled price. supplier_amount and supplier_currency carry the supplier’s own figure and are never converted. Price filters (price_min_usd_cents, price_max_usd_cents) and typical_price_usd_cents are always USD cents, whatever display currency the response uses. Room availability, prebook and booking use their own currencies, so always read each response’s currency.

Search expiry

search_id is valid for about 5 minutes. Polling with refresh_ids extends it by 5 minutes. After that the price endpoint returns 404: run the search again to get fresh prices. Cursor paging keeps working while you page, but keep the same inputs.

Tenant-specific availability

Results are evaluated against the suppliers enabled for your account. A hotel already returned is never removed later when suppliers report no price: you see no_price instead and decide how to show it. A later search can leave out hotels that recently returned no price for your account and supplier set. An exact no-price result is remembered for 30 minutes. A hotel counts as chronically unavailable after no-price results on three distinct check-in dates in 14 days. A later live price resets that history. The history is isolated by account, environment and enabled supplier set. Supplier errors and timeouts do not count as no-price evidence.

Common mistakes

  • Showing 0 or a placeholder when amount is null. Show the state for status instead.
  • Dropping checking cards. Keep them and poll.
  • Editing or rebuilding next_cursor, or reusing it after changing a sort or filter. That returns 400. Start again without a cursor.
  • Sending autocomplete id as destination.id.
  • Using filters price_range (per night, per room) directly as a stay total.
  • Omitting nationality and silently searching as IN.
  • Using fields without price and then wondering why cards have no price.
  • Using the search amount as the booking price. Confirm with rooms and prebook.

Next step

Poll Get Search Price Updates while results are on screen. When the guest chooses a hotel, call Get Hotel Rooms. The OpenAPI section below documents every request field, response field and error.

Authorizations

x-api-key
string
header
required

Environment-specific API key created in the Unifystays portal

Headers

X-Unifystays-Contract-Version
enum<string>
default:2

Hotel API contract version. Optional: 2 is the current version and the default.

Available options:
2
X-Unifystays-Currency
string

ISO 4217 currency for display amounts (display_ledger, display_amount, commercial_display). Optional: defaults to your organization's display currency, USD unless configured. Read pricing.display_currency in the response.

Required string length: 3
Example:

"EUR"

Body

application/json
destination
object
required

Destination chosen from GET /destinations/autocomplete: its type plus the matching place_id or hotel_id. Use an ID from the same environment you are searching in.

check_in
string
required

Check-in date, YYYY-MM-DD (the hotel's local date). Must be today or later.

Example:

"2026-12-10"

check_out
string
required

Check-out date, YYYY-MM-DD. Must be after check_in. The stay can be at most 366 nights.

Example:

"2026-12-13"

rooms
object[]
required

One entry per room the guest needs: 1-8 rooms, each with adults and optional child ages (max 5 occupants per room). Prices cover all rooms together. Send the same rooms when you request room options and prebook.

Required array length: 1 - 8 elements
star_ratings
array[]

Only return hotels with one of these star ratings (1-5, up to 5 values). Values come from GET /hotels/filters -> stars. Unrated hotels are excluded when this filter is used.

Example:
price_min_usd_cents
number

Minimum price for the WHOLE stay (all nights and rooms) in USD cents. Always USD cents, whatever display currency the response uses. The filter uses the catalogue's typical prices, so a live quote can fall slightly outside the range. GET /hotels/filters -> price_range is per night for one room; multiply by nights and rooms before using it here.

Required range: x >= 0
Example:

5000

price_max_usd_cents
number

Maximum price for the WHOLE stay (all nights and rooms) in USD cents. Always USD cents, whatever display currency the response uses. See price_min_usd_cents for how the range is applied.

Required range: x >= 1
Example:

500000

property_types
array[]

Only return these property types, as strings exactly as listed in GET /hotels/filters -> property_types (for example "Hotel", "Resort"). Up to 30 values; a hotel matches if it has any of them.

Example:
chain_ids
array[]

Only return hotels of these chains or brands. Numeric values come from GET /hotels/filters -> chains. Up to 50 values; a hotel matches if it belongs to any of them.

Example:
facility_ids
array[]

Only return hotels that have ALL of these facilities (ids are ANDed, so each extra id narrows the list). Numeric values come from GET /hotels/filters -> facilities. Up to 50 values.

Example:
hotel_name
string

Only return hotels whose name contains this text (case-insensitive, 1-120 characters). Use it for a "search within results" box.

Example:

"marriott"

hotel_ids
array[]

Restrict a place search to these hotel IDs (numbers, up to 50). Use it to compare a shortlist; hotels outside the destination are not added.

Example:
nationality
string
default:IN

Guest nationality as an ISO 3166-1 alpha-2 code from GET /master-data/nationalities (iso_code). Suppliers can return different rates and availability by nationality, so send the traveller's real nationality. Defaults to IN when omitted, which is a real value, not a neutral one. An unknown code returns 400. Send the same value when you request room availability.

Example:

"IN"

language
string

Requested response language as a language code such as en (2-10 characters). Optional; forwarded to suppliers that support it.

Example:

"en"

limit
number
default:20

Hotels per page, 1-100. Default 20. Values above 100 are capped at 100. Fewer hotels can be returned on the last page.

Required range: 1 <= x <= 100
Example:

20

cursor
string

Paging token. Send data.next_cursor from the previous page, unchanged, and keep every other search input the same. Treat it as opaque: do not build or edit it. Changing the sort or any filter makes the cursor invalid (400); restart from page 1 without a cursor.

Example:

"1037.5_1892345"

price_sort
enum<string>
default:recommended

Older way to ask for a price sort (low_to_high or high_to_low). Prefer sort, which wins when both are sent. When neither is sent the list uses recommended ranking.

Available options:
low_to_high,
high_to_low
Example:

"low_to_high"

pricing_mode
enum<string>
default:instant

'instant' (default): responds quickly with the hotel list. Cards whose price is not known yet have price.status = checking; poll GET /hotels/search/{searchId}/prices every 2 seconds to fill them in. 'live': the request waits up to about 15 seconds for the supplier refresh and returns the quotes collected so far in price and supplier_prices. Hotels still unanswered after the wait stay in checking, so keep polling if refresh_status is PENDING.

Available options:
instant,
live
Example:

"instant"

sort
enum<string>
default:recommended

Result order. Supersedes price_sort when both are sent. recommended (default): relevance ranking. price_low_to_high / price_high_to_low: by total stay price; the order may rest on estimates for cards still checking (see price.sort_basis). stars: highest star rating first. distance_from_center: closest to the searched place centre first. value: biggest value_pct first. If a sort cannot be applied for the destination, the list falls back to recommended; data.search_info.price_sort shows the sort actually used. Keep the same sort while paging.

Available options:
recommended,
price_low_to_high,
price_high_to_low,
stars,
distance_from_center,
value
Example:

"distance_from_center"

fields
string[]

Return only these hotel card fields to shrink the payload (up to 30 names; hotel_id is always included, unknown names are ignored). Omit it for the full card. If you leave out "price" the cards carry no price, so include it when you show prices. Example: ["hotel_name","price","stars"].

Example:

Response

contract_version
string
required

Response contract version. Always 2.

Example:

"2"

success
boolean
required

True when the request succeeded.

Example:

true

message
string
required

Human-readable status. "No priced hotels found for this destination" when hotels is empty. Do not parse.

Example:

"Hotels fetched successfully"

data
object
required

Search results payload.