Skip to main content
POST
Prebook sits between room selection and booking: search, rooms, prebook, create booking, then manage the booking. It asks the supplier, right now, whether the room the guest picked is still available and what it costs. The answer is a complete refreshed offer, a prebooking_id, and a verdict on whether anything changed.

Why prebook is mandatory

  • It is the only way to get a prebooking_id. Create Booking accepts nothing else. You cannot book straight from a rooms response.
  • Room-stage prices can change before the guest pays. Prebook is the live recheck with the supplier, so the guest never pays a price you did not show.
  • It returns the final terms. The refreshed offer has the confirmed price, cancellation terms, payment terms, notices and the exact guest information the supplier needs (booking_requirements). Build your checkout form from this response, not from the rooms response.
What a successful prebook guarantees: at the moment of the call the supplier confirmed the room and price (offer.lifecycle.price_guaranteed is true) and the offer stays bookable until offer.lifecycle.expires_at. It is not a reservation. The room is only yours once Create Booking succeeds.
Prebook the room when the guest starts checkout, not earlier. The prebooking_id is valid for a short time (see How long a prebook lasts).

Request

Optional header X-Unifystays-Currency (an ISO 4217 code such as EUR) sets the display currency of the refreshed offer and of difference_display. Without it your organization’s display currency is used (USD unless configured). Unknown fields in the body are rejected.

Response

The example is trimmed for reading. A real offer also carries room_combination, rate_plan, payment, commercial, nightly_prices, promotions, benefits, full provider_room details on each room, the usd_ledger and conversion blocks, and more. Every offer field is described on the room stream page; the prebook offer has the same shape.

What to do with the response

Branch on one field: data.revalidation.decision. data.offer is always the complete, authoritative offer. Replace the room-stage offer with it. Never merge individual values into the old offer. Prebook always issues a new offer_id, increments revision, and sets supersedes_offer_id to the offer you started from.

Comparing the prebook price with the quoted price

You do not need to compute the difference yourself. Each price change appears in revalidation.changes[] with a money_change block:
Amounts are integers in minor units: divide by 10 to the power of currency_exponent (46800 with exponent 2 is 468.00). previous is what the guest saw in the room stage and current is what the supplier now confirms. difference is current minus previous, so positive means dearer. There are two separate totals: collection_location SUPPLIER is the part settled with the supplier for this booking, PROPERTY is the part the guest pays at the hotel (resort fees, city tax and similar). How each kind of price change is classified: difference_display is difference converted to pricing.display_currency (your organization’s display currency, or the one sent in the X-Unifystays-Currency request header). It is null when no exchange rate is available. If the USD rate is missing, difference_usd is null. It is never a made-up zero. When revalidation.result is NOT_COMPARABLE, the prices could not be compared in one currency; treat that like a change and show the new price. The refreshed pricing also carries display_currency and display_ledger, the supplier ledger converted to your display currency, plus commercial_display and the display_amount fields on cancellation penalties, deposits and nightly prices. They work exactly as described in Get Room Options: null means no rate, and they are for showing only. Your own selling price is your decision. pricing.native_ledger is what the supplier charges and is authoritative. The commercial fields are informational; do not add them to the ledger.

What to show the guest when changes[] is not empty

1

Look at the highest impact

If any change has impact: "BLOCKING", the decision is NOT_BOOKABLE. If any has RECONFIRMATION_REQUIRED, the decision is RECONFIRMATION_REQUIRED. Otherwise it is READY_TO_BOOK.
2

Show the new terms, not a diff of everything

For RECONFIRMATION_REQUIRED, show the refreshed offer’s price, cancellation terms, meal plan and notices, and call out what changed. Use category to pick the message (PRICING, CANCELLATION, MEAL, BEDDING, PAYMENT, BOOKING_REQUIREMENT…) and money_change for the amounts. description is a short English sentence that is fine for logs and support screens.
3

Let the guest accept or reject

Accept: continue with the same prebooking_id and offer_id. Reject: go back to room selection; the unused prebooking_id simply expires.
4

Treat INFORMATIONAL changes as optional to show

Price drops, added inclusions and relaxed requirements need no approval. You can tell the guest or book straight away.
Change categories you may see: AVAILABILITY, PRICING, TAX, PAYMENT, ROOM, ROOM_COMBINATION, BEDDING, MEAL, INCLUSION, RATE_PLAN, CANCELLATION, BOOKING_REQUIREMENT, NOTICE, BENEFIT, PROMOTION and COMMERCIAL. The full list of codes is in the code description of the reference below. Branch on impact and decision, not on the code, because new codes can be added.
ROOM_IDENTITY_CHANGED (category ROOM, impact BLOCKING) means the supplier would book a different room than the one the guest chose. Typical reasons: the supplier names a different room, a different room order or a different number of rooms than the quote, or the beds or guest count shown on the room card contradict what the supplier states for the room it will book. No prebooking_id is returned. Send the guest back to room selection.
For the curious, the room-ID comparison applies only to suppliers that report room IDs at prebook (TripJack): RezLive’s prebook repeats the room names from search, so its room ID cannot be verified (and never blocks). The beds and guest-count comparison runs for every supplier, because it uses only the offer’s own data. Each room of the prebook offer carries unifystays_room_id and unifystays_family_id, and unifystays_room is null. unifystays_family_id is null for a tmp_ room.

How long a prebook lasts

  • The offer is valid until data.offer.lifecycle.expires_at. That is the supplier’s own deadline when the supplier gives one; otherwise it is 15 minutes after the prebook call.
  • The prebooking_id expires at that same moment. After that Create Booking returns PREBOOKING_ID_EXPIRED. Run rooms again and prebook the new offer.
  • The prebooking_id can be used for one booking. A successful Create Booking consumes it. If Create Booking fails validation, the prebooking_id is not consumed and you can fix the body and resend.
  • Show the guest a countdown or re-prebook if checkout takes long.

Repeating a prebook (idempotency)

Prebook is idempotent per organization and booking_token. You do not send a key.
  • Calling again with the same booking_token after a success returns the stored result, with the same prebooking_id and offer_id. It does not contact the supplier again, so it will not show a newer price. For a fresh check, get a new booking_token from the rooms API.
  • If a call for the same token is still running, you get 409 PREBOOK_IN_PROGRESS and a Retry-After header (3 seconds). Wait and repeat the identical request. Do not start parallel prebooks for one token.
  • After a temporary supplier failure (HTTP 429 or 5xx), the same token can be tried again about 30 seconds later. Before that you get PREBOOK_IN_PROGRESS. A definite refusal, such as OFFER_UNAVAILABLE, is returned again for the same token.

What to build checkout from

  • offer.booking_requirements is a server-driven form. Display label, choose a control from value_type, apply allowed_values and constraints, and ask only the guests selected by scope and room_allocation_ids. At booking, echo the exact requirement_id with each answer. New requirement codes can appear without any change to the booking request format. Identity and tax-document requirements, such as passport or PAN, are part of this list too.
  • offer.notices holds legal and supplier messages. Show every notice with must_display: true to the guest before they pay. There is no separate compliance object.
  • offer.cancellation is the final cancellation policy. Show it and store a copy. See the Create Booking page for how it decides whether a booking needs approval.
  • offer.rooms[].room_allocation_id is the key for assigning guests at booking.
  • offer.lifecycle.available_booking_modes contains HOLD only when the offer can be held; see Confirm Hold. hold_deadline says until when.
  • offer.pricing.native_ledger is the authoritative supplier/property price. usd_ledger has the same lines in USD, or is null when a required rate is unavailable. Price and the structured provider-room fallback are replaced together as one atomic snapshot.
  • supplier_hotel is the supplier’s own hotel that the booking goes to: its provider_hotel_id, and the name, address and country_code as the supplier lists them (each null when the supplier sent none). Show it next to your hotel, the same way provider_room shows the supplier’s room, so agents can match it against the supplier’s voucher. Wording can differ from offer.quote_context.hotel.name; the hotel is the same. mapping_status is always VERIFIED, because prebook only succeeds for a supplier hotel that passed the UnifyStays mapping check (otherwise it fails with HOTEL_MAPPING_UNVERIFIED or HOTEL_MAPPING_CHECK_PENDING). verified_at is when that check last passed.

Errors to handle

Errors use the standard envelope with error.code; see Errors & Retries.

Common mistakes

Do not skip a RECONFIRMATION_REQUIRED verdict. If the guest never saw the higher price, they did not agree to it.
Do not reuse an old prebooking_id for a second booking, and do not build a booking from the rooms-stage offer’s IDs. Always send accepted_offer_id equal to the prebook data.offer.offer_id, together with the returned prebooking_id.

Next step

Collect guest details from booking_requirements, then call Create Booking.

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
hotel_id
string
required

Unifystays hotel ID the guest is looking at, as returned by search (hotel_id). It must be the hotel the booking_token was issued for; a different hotel is rejected with BOOKING_TOKEN_HOTEL_MISMATCH.

Example:

"13553916"

booking_token
string
required

Opaque booking_token of the offer the guest picked, from Get Room Options or the room stream. Send it unchanged and do not parse it. It expires (see the offer's lifecycle.expires_at); an expired token returns BOOKING_TOKEN_EXPIRED and you must fetch fresh rooms.

Example:

"bq7.Y0QxTmtKc21Wbmk.7.6zI4D6fUEw2jVv7KQdM4Kw"

language
string

Optional response language (2-10 letters, for example en). If you send it, it must equal the language of the room search that produced the booking_token; a different value is rejected with VALIDATION_ERROR. Omit it to use the quoted language.

Example:

"en"

Response

Returns one authoritative refreshed offer, its revalidation decision, and a prebooking_id when the offer remains bookable. A sold-out or changed-room offer is also HTTP 200, with success false, decision NOT_BOOKABLE and prebooking_id null.

contract_version
string
required

Response contract version. Always "2".

Example:

"2"

success
boolean
required

false when the offer is not bookable (revalidation.decision is NOT_BOOKABLE, for example sold out); true otherwise, including when reconfirmation is required. A sold-out or changed-room result is a normal HTTP 200 response with success false, not an error.

Example:

true

message
string
required

Human-readable summary for logs. Do not parse it; use data.revalidation.decision.

Example:

"Pre-booking completed. Option is available and ready for booking."

data
object
required

The prebook result.