Skip to main content
GET
This is where the bookable offers arrive. Get Room Options starts the search and returns an sse_stream_url. You open that URL and read a Server-Sent Events (SSE) stream. Each supplier sends its offers as soon as it answers, so the first offers usually show up well before the slowest supplier has finished. A final done event tells you the search is over. Search → Rooms (this page) → Prebook → Book.

How a search runs

1

Open the stream right away

Use data.sse_stream_url from the POST response with the same x-api-key. Open it immediately: the search starts when the POST is accepted, and the stream only keeps recent events.
2

Read events as they arrive

Add each provider_options event’s rooms[] (family objects) to your room store and each options[] entry to your offer list. Draw the room cards from them.
3

Stop at done

The done event is the last event. Close the connection yourself when you get it. The server keeps a finished stream readable for about 2 minutes so late or reconnecting clients can still read it, so the connection is not closed for you.
4

Let the guest choose

Send the chosen offer’s booking_token unchanged to Prebook a Room.
Browser EventSource cannot send an x-api-key header. Open the stream from your server and forward the events to the browser, or use a fetch-based SSE client that can set headers. Never put your API key in front-end code.

Events

Every event has an SSE id (a whole number that starts at 1 and increases by one for each event of the stream), an event name and a JSON data body. The body of every event also carries these envelope fields:

provider_options

A single event carries at most 50 offers and about 256 KB of JSON. A larger result is split into several events with the same provider_code. A supplier can also send its offers in more than one batch: offers whose rooms are already known first, the rest shortly after.

done

Time limits and partial results

  • The whole search is capped at 45 seconds from the POST. When the cap is reached, suppliers that have not answered are counted as timed out, and done is sent with PARTIAL (or FAILED if nobody answered).
  • Each supplier also has its own time limit (about 20 seconds by default). A supplier that exceeds it produces a provider_error with code: "PROVIDER_TIMEOUT" and never delays the others.
  • Show offers as they arrive and keep a “still searching” state until done. A result list that was complete at done is stable: later events never remove an offer, but a later provider_options event can replace a family object.
  • Offers are valid for a limited time. See lifecycle.expires_at. If the guest waits too long, run a new room search instead of sending an old booking_token.

Reconnect with Last-Event-ID

The stream keeps the latest 250 events. If your connection drops, reconnect to the same sse_stream_url and send the id of the last event you processed as the Last-Event-ID header (browser EventSource does this for you). The API replays the later events and then continues live. Without the header, or with 0, you get every retained event from the start.
A reconnect never loses offers when you replay from your last processed id. Make your handler idempotent anyway: store offers by offer_id and family objects by unifystays_room_id. A fetch-based client that reconnects until done:

Family objects on provider_options

Offers from different suppliers for the same physical room are grouped under one family object, and each family splits into variants by bed type:
  • rooms[] on a provider_options event holds the family objects that event’s offers use, new or updated. Store them by unifystays_room_id and replace an earlier copy: a family’s variants, bed_assignment and details can change as more suppliers send offers for it.
  • A family object has unifystays_room_id, name, details, suppliers, variants[] and bed_assignment. Each variant has unifystays_room_id, beds, bedrooms, max_guests, smoking and suppliers. A variant’s facts are non-null only when every supplier selling that variant in this stream stated exactly that value. variants[] lists only variants with at least one seller in this stream so far.
  • bed_assignment.suppliers lists the suppliers whose offers belong to the family but state no bed type (the hotel assigns it). Such offers are in no variant, and a family-level seller never contributes beds to a variant.
  • Every slot in options[].rooms[] carries unifystays_room_id (the variant id, or the family id for an offer listed under bed_assignment.suppliers) and unifystays_family_id (the family). unifystays_room is null.
  • offer_family_id is filled and is computed over family ids: offers whose slots are in the same families in the same order share it. It is null when any slot is a tmp_ room (a room valid for this search only).
  • A supplier’s offers can arrive in more than one provider_options batch: offers whose rooms are already known first, the rest after a short, bounded wait. option_count, chunk_index and chunk_count describe one batch; done ends the stream.
Client rule: Show the family header (name, details with its source). Show each variant’s beds, bedrooms, max guests and smoking only from the variant object. Show offers listed under bed_assignment.suppliers with the label Bed type assigned by the hotel. Always show each offer row’s own provider_room. details (photos, size, description, amenities) come from the one supplier named in details.source. A tmp_ room is a temporary family object: no variants and bed_assignment.suppliers holding the seller when it stated no beds, otherwise one variant.
The example is shortened; every offer carries all the fields described in the offer. The king offer sits under variant 4120553. The second REZLIVE offer states no bed type, so its slot carries the family id as unifystays_room_id and REZLIVE is listed in bed_assignment.suppliers: show it as Bed type assigned by the hotel. REZLIVE appears both as a variant seller (its king offer, which states one king bed and 2 guests, like TRIPJACK) and under bed_assignment.suppliers (its “Double or Twin” offer). Each row still shows its own provider_room.
Room grouping only decides where an offer is drawn. It never changes what is booked. Every offer is the supplier’s own offer, identified by its booking_token and the room_allocation_id of each slot.

The offer (HotelOffer)

Each entry of options[] is one complete, bookable offer: a price, a cancellation policy and a meal plan for all requested rooms from one supplier. A multi-room search returns offers whose rooms[] has one entry per requested room. The customer chooses one whole offer; never mix rooms from different offers.

Identity and booking

Rooms (one slot per requested room)

The price ledger (pricing)

All amounts are objects { "amount_minor", "currency", "currency_exponent" }. amount_minor is an integer in minor units: divide by 10^currency_exponent (17346 with exponent 2 is 173.46; a currency with exponent 0, such as JPY, is already whole units). Never treat it as a decimal.
Never add commercial values to the ledger, and never add a ledger line to payable_to_supplier yourself. If you show one price to the guest, show total and explain which part is paid at the hotel.
Commercial values. commercial holds the supplier’s own reference amounts: supplier_charge_total (what the supplier expects to invoice you), display_total, gross_total, net_total, commission_total, agent_reward, recommended_selling_total, minimum_selling_total, original_total, discount_total and vat. They are informational views and must never be added to any ledger. null means not supplied; zero means the supplier explicitly returned zero. Respect minimum_selling_total when it is set.

Cancellation

Payment

Booking requirements

booking_requirements[] lists the guest and booking fields that will be needed at booking. Build your checkout form from it now, so the guest is asked once. Prebook returns the requirements again. Use the list from the prebook response for the final form.

Lifecycle

Notices and terms

Passport, tax ID, date of birth and nationality rules are declared through booking_requirements; mandatory legal text appears in notices. There is no separate compliance object.

The seller’s own room

rooms[].provider_room is always the seller’s own room, whichever variant or family it is shown under. Show its beds and maximum guests on every offer row, grouped or not. It contains every structured detail the supplier sent. null means not known. An empty array means none were supplied. Neither means “no”. bed_configurations: [] means the supplier gave no dependable bedding data; it does not mean the room has no bed. New fields and enum values may be added: ignore what you do not recognise.

Common mistakes

  • Do not wait for done before showing offers. Render as events arrive.
  • Do not book with a unifystays_room_id. Book with booking_token.
  • Do not add commercial fields, or ledger lines, to a price you compute.
  • Do not reuse a booking_token after lifecycle.expires_at.
  • Do not keep the connection open after done.
  • Do not store tmp_ room IDs, or keep any room ID as a long-lived key.

Next step

When the guest picks an offer, call Prebook a Room with its booking_token.

Authorizations

x-api-key
string
header
required

Environment-specific API key created in the Unifystays portal

Headers

Last-Event-ID
string

Optional SSE resume cursor. Omit it (or use 0) for the first connection. When reconnecting, send the ID of the last event successfully processed. If that event is no longer retained, the API returns ROOM_STREAM_REPLAY_GAP and a new room search is required.

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"

Path Parameters

hotel_id
string
required

Unifystays hotel ID used to start the room search. It must match the hotel the stream was created for, otherwise the stream is reported as not found.

Example:

"123456"

stream_id
string
required

Room stream ID (data.stream_id) returned by POST /hotels/{hotel_id}/rooms. A stream lives for about 45 seconds of searching and is kept for about 2 minutes after its done event.

Example:

"stream_8f4b9d1f6d9e"

Response

Room stream not found, expired, or belongs to another hotel.