curl "$UNIFYSTAYS_BASE_URL/hotels/book" \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: $UNIFYSTAYS_API_KEY" \
-H "language: en" \
-H "Idempotency-Key: order-2987349823-booking-1" \
-H "X-Unifystays-End-User-IP: 8.8.8.8" \
-d '{
"prebooking_id": "pbq7.bjNQb1Y5R2hRWEg.k6jQ3nW4cF1nL4MWvN7A9Q",
"accepted_offer_id": "off_65db5f20d91b47d3a73d834dfa44c50e",
"booking_mode": "INSTANT",
"rooms": [
{
"room_allocation_id": "ra_2c1fb39ed54742f28d95bd8bc5f4375d",
"guests": [
{
"guest_id": "guest_1",
"guest_type": "ADULT",
"is_lead_guest": true,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MR" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Shubham" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" },
{ "requirement_id": "br_email_lead_guest_4", "value": "shubham@example.com" },
{ "requirement_id": "br_phone_lead_guest_5", "value": "+919876543210" }
]
},
{
"guest_id": "guest_2",
"guest_type": "ADULT",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MRS" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Aarohi" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
}
]
},
{
"room_allocation_id": "ra_957cff0ac25243f29eb43bc32eb82136",
"guests": [
{
"guest_id": "guest_3",
"guest_type": "ADULT",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MS" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Meera" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
},
{
"guest_id": "guest_4",
"guest_type": "CHILD",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MASTER" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Arjun" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
}
]
}
]
}'
{
"success": true,
"message": "Booking request received and awaiting manual review. No supplier reservation has been created. Price and availability are not guaranteed.",
"data": {
"booking_id": "ubk_eGQ0dW9uV1Vi",
"accepted_offer_id": "off_65db5f20d91b47d3a73d834dfa44c50e",
"status": "AWAITING_REVIEW",
"is_terminal": false,
"review_expires_at": "2026-09-14T12:15:00.000Z",
"provider": {
"code": "TRIPJACK",
"booking_id": null,
"booking_code": null,
"hotel_confirmation_number": null,
"hotel_confirmation_status": null,
"hotel_confirmation_note": null,
"hotel_contact_phone": null,
"hotel_contact_name": null
},
"next_poll_after_ms": null,
"processing_reason": "MANUAL_REVIEW_REQUIRED",
"action_required": true,
"available_actions": ["GET_STATUS", "GET_DETAILS", "REVIEW_BOOKING"],
"hold_deadline_at": null,
"created_at": "2026-09-14T12:12:11.000Z",
"updated_at": "2026-09-14T12:12:11.000Z"
}
}
Create Booking
Create an idempotent hotel booking from a successful Unifystays prebooking response.
curl "$UNIFYSTAYS_BASE_URL/hotels/book" \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: $UNIFYSTAYS_API_KEY" \
-H "language: en" \
-H "Idempotency-Key: order-2987349823-booking-1" \
-H "X-Unifystays-End-User-IP: 8.8.8.8" \
-d '{
"prebooking_id": "pbq7.bjNQb1Y5R2hRWEg.k6jQ3nW4cF1nL4MWvN7A9Q",
"accepted_offer_id": "off_65db5f20d91b47d3a73d834dfa44c50e",
"booking_mode": "INSTANT",
"rooms": [
{
"room_allocation_id": "ra_2c1fb39ed54742f28d95bd8bc5f4375d",
"guests": [
{
"guest_id": "guest_1",
"guest_type": "ADULT",
"is_lead_guest": true,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MR" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Shubham" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" },
{ "requirement_id": "br_email_lead_guest_4", "value": "shubham@example.com" },
{ "requirement_id": "br_phone_lead_guest_5", "value": "+919876543210" }
]
},
{
"guest_id": "guest_2",
"guest_type": "ADULT",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MRS" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Aarohi" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
}
]
},
{
"room_allocation_id": "ra_957cff0ac25243f29eb43bc32eb82136",
"guests": [
{
"guest_id": "guest_3",
"guest_type": "ADULT",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MS" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Meera" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
},
{
"guest_id": "guest_4",
"guest_type": "CHILD",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MASTER" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Arjun" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
}
]
}
]
}'
{
"success": true,
"message": "Booking request received and awaiting manual review. No supplier reservation has been created. Price and availability are not guaranteed.",
"data": {
"booking_id": "ubk_eGQ0dW9uV1Vi",
"accepted_offer_id": "off_65db5f20d91b47d3a73d834dfa44c50e",
"status": "AWAITING_REVIEW",
"is_terminal": false,
"review_expires_at": "2026-09-14T12:15:00.000Z",
"provider": {
"code": "TRIPJACK",
"booking_id": null,
"booking_code": null,
"hotel_confirmation_number": null,
"hotel_confirmation_status": null,
"hotel_confirmation_note": null,
"hotel_contact_phone": null,
"hotel_contact_name": null
},
"next_poll_after_ms": null,
"processing_reason": "MANUAL_REVIEW_REQUIRED",
"action_required": true,
"available_actions": ["GET_STATUS", "GET_DETAILS", "REVIEW_BOOKING"],
"hold_deadline_at": null,
"created_at": "2026-09-14T12:12:11.000Z",
"updated_at": "2026-09-14T12:12:11.000Z"
}
}
Idempotency-Key header for every request.
The booking request assigns named guests to the exact room instances returned
by prebook. It does not use static catalogue room IDs, supplier room names, or
array position as identity.
Cancellation-based review
The request body and idempotency rules are unchanged. Bookings within the accepted offer’s free-cancellation window returnPROCESSING and queue supplier booking
immediately, without portal approval. Non-refundable bookings, offers whose free
window has ended, and unclear cancellation terms return AWAITING_REVIEW.
Neither status is a confirmed reservation or a supplier hold.
For AWAITING_REVIEW, show “Awaiting verification — room and price are not reserved”.
An organization owner/admin reviews the saved hotel, supplier rooms, guests,
price and cancellation terms in the portal, then chooses Approve and book.
REVIEW_BOOKING is a portal action; customer API keys cannot approve requests.
Free cancellation is checked again before supplier submission. If it ends while
the request is queued, the booking moves to AWAITING_REVIEW and requires approval.
data.review_expires_at is the current supplier quote deadline. Manual requests
remain AWAITING_REVIEW after quote expiry; do not cancel or refund based on this
timestamp. The reviewer can obtain a fresh quote in the portal and approve its
current price and terms under the same booking reference. The original agent
price stays unchanged; the reviewer accepts any supplier cost difference.
Approval and supplier submission must occur before the fresh quote expires.
Rejection produces FAILED / BOOKING_REVIEW_REJECTED. Already-terminal requests
cannot be refreshed. Expiry during direct free-cancellation submission can still
produce FAILED / BOOKING_REVIEW_EXPIRED before any reservation is submitted.
Request Example
curl "$UNIFYSTAYS_BASE_URL/hotels/book" \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: $UNIFYSTAYS_API_KEY" \
-H "language: en" \
-H "Idempotency-Key: order-2987349823-booking-1" \
-H "X-Unifystays-End-User-IP: 8.8.8.8" \
-d '{
"prebooking_id": "pbq7.bjNQb1Y5R2hRWEg.k6jQ3nW4cF1nL4MWvN7A9Q",
"accepted_offer_id": "off_65db5f20d91b47d3a73d834dfa44c50e",
"booking_mode": "INSTANT",
"rooms": [
{
"room_allocation_id": "ra_2c1fb39ed54742f28d95bd8bc5f4375d",
"guests": [
{
"guest_id": "guest_1",
"guest_type": "ADULT",
"is_lead_guest": true,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MR" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Shubham" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" },
{ "requirement_id": "br_email_lead_guest_4", "value": "shubham@example.com" },
{ "requirement_id": "br_phone_lead_guest_5", "value": "+919876543210" }
]
},
{
"guest_id": "guest_2",
"guest_type": "ADULT",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MRS" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Aarohi" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
}
]
},
{
"room_allocation_id": "ra_957cff0ac25243f29eb43bc32eb82136",
"guests": [
{
"guest_id": "guest_3",
"guest_type": "ADULT",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MS" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Meera" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
},
{
"guest_id": "guest_4",
"guest_type": "CHILD",
"is_lead_guest": false,
"answers": [
{ "requirement_id": "br_title_all_guests_1", "value": "MASTER" },
{ "requirement_id": "br_first_name_all_guests_2", "value": "Arjun" },
{ "requirement_id": "br_last_name_all_guests_3", "value": "Gupta" }
]
}
]
}
]
}'
Request Fields
| Field | Where to get it | How it is used |
|---|---|---|
Header Idempotency-Key | Generate in your backend for one customer booking attempt | Prevents an uncertain retry from creating a duplicate reservation. Persist it before the first request. |
Header X-Unifystays-End-User-IP | The traveler’s public IP at checkout | Required for RateHawk. Private, loopback, reserved, and malformed addresses are rejected. |
prebooking_id | data.prebooking_id from a successful prebook response | Identifies the confirmed supplier option and trusted booking context. Send it unchanged. |
accepted_offer_id | data.offer.offer_id from that same prebook response | Explicitly accepts the complete refreshed offer. A mismatch is rejected. |
booking_mode | data.offer.lifecycle.available_booking_modes | Use INSTANT normally. Use HOLD only when explicitly offered; currently this is TripJack-only. |
rooms[] | Build from every entry in prebook data.offer.rooms[] | Assigns guests to all quoted room instances. Do not omit, add, or combine allocations. |
rooms[].room_allocation_id | The matching prebook room’s room_allocation_id | Identifies which exact quoted room receives these guests. |
rooms[].guests | Your checkout guest form | Must contain the adults and children staying in that allocation. |
guests[].guest_id | Generate in your backend | Correlates validation errors and guest records; it must be unique within the request. |
guests[].guest_type | Your checkout guest form | Use ADULT, CHILD, or INFANT. Counts must match the allocation’s prebook occupancy. |
guests[].is_lead_guest | Your checkout guest selection | Exactly one guest across the complete booking must be marked true. |
guests[].answers[] | Build from prebook data.offer.booking_requirements[] | Echo each applicable requirement_id with its user-provided string value; undeclared and duplicate IDs fail. |
rooms array can be sent in any order. Unifystays matches each entry by
room_allocation_id and restores the supplier’s trusted room order
server-side.Building the Room Assignments
For every room in the prebook response:- Copy its
room_allocation_idinto one booking room object. - Read the room’s
occupancy.adultsandoccupancy.child_ages. - Attach exactly that number of adult and child guest objects.
- Repeat until every prebook allocation appears once.
{"adults": 1, "child_ages": [8]} requires one
guest with guest_type: "ADULT" and one guest with guest_type: "CHILD". The child’s age
was already captured in the shopping and prebook context; the booking guest
object supplies the child’s identity.
Never send the nested unifystays_room.room_id in this request. A room with
mapping_status: "UNMAPPED" is booked through its room_allocation_id in
exactly the same way as a mapped room.
Idempotency Rules
Build the Booking Body
- Send
prebooking_idandaccepted_offer_idfrom the same prebook response. - Send every room allocation returned by prebook exactly once. Array order is not significant.
- Use each prebook
room_allocation_idto attach the correct guests to that quoted room slot. - Render the prebook offer’s
booking_requirementsdynamically. Uselabel,value_type,allowed_values, andconstraintsto build and validate the form. - Copy each applicable
requirement_idexactly into the guest’sanswers. Never construct IDs fromcode, and do not send answers that were not declared by the accepted prebook offer. - Apply
LEAD_GUEST,ALL_GUESTS,ADULT,CHILD,INFANT, and optionalroom_allocation_idsscopes when deciding which guest must answer. - Do not send agency billing, arrival, or special-request sections. Agency tax details belong to organization/provider settings, not individual bookings.
Response Example
{
"success": true,
"message": "Booking request received and awaiting manual review. No supplier reservation has been created. Price and availability are not guaranteed.",
"data": {
"booking_id": "ubk_eGQ0dW9uV1Vi",
"accepted_offer_id": "off_65db5f20d91b47d3a73d834dfa44c50e",
"status": "AWAITING_REVIEW",
"is_terminal": false,
"review_expires_at": "2026-09-14T12:15:00.000Z",
"provider": {
"code": "TRIPJACK",
"booking_id": null,
"booking_code": null,
"hotel_confirmation_number": null,
"hotel_confirmation_status": null,
"hotel_confirmation_note": null,
"hotel_contact_phone": null,
"hotel_contact_name": null
},
"next_poll_after_ms": null,
"processing_reason": "MANUAL_REVIEW_REQUIRED",
"action_required": true,
"available_actions": ["GET_STATUS", "GET_DETAILS", "REVIEW_BOOKING"],
"hold_deadline_at": null,
"created_at": "2026-09-14T12:12:11.000Z",
"updated_at": "2026-09-14T12:12:11.000Z"
}
}
| Response key | What it means | What your integration should do |
|---|---|---|
data.booking_id | Stable Unifystays booking identity | Persist it immediately. Use it for status, details, and cancellation APIs. |
data.status | Current unified booking state | ON_HOLD means the hold was created, but confirmation may still be required. |
data.is_terminal | Whether booking processing has reached a final state | Stop polling only when this is true. |
data.action_required | Whether your integration must take an action | Read available_actions; for ON_HOLD, call CONFIRM_HOLD before the deadline. |
data.next_poll_after_ms | Recommended delay before the next status request | Wait at least this long before polling. It is null for terminal states. |
data.provider | Supplier-side identifiers and status, when available | Display or store for reconciliation; keep booking_id as your primary Unifystays identifier. |
Track the Outcome
The response contains a stablebooking_id and unified status. Use
Get Booking Status as the source of truth
after the request returns.
| Status | Meaning |
|---|---|
AWAITING_REVIEW | Awaiting portal approval; nothing reserved at the supplier. |
PROCESSING | Booking request accepted and still being finalized. |
CONFIRMED | Booking is confirmed. |
ON_HOLD | TripJack hold created; call the confirm-hold endpoint before hold_deadline_at. |
FAILED | Booking could not be completed. |
CANCELLATION_PENDING | A cancellation request is being processed. |
CANCELLED | Booking cancellation is complete. |
is_terminal to stop status polling, but always check action_required
and available_actions. An ON_HOLD response can be terminal for the hold
creation operation while still requiring CONFIRM_HOLD as the next business
action.
Authorizations
Environment-specific API key created in the Unifystays portal
Headers
Public IP of the traveler completing checkout. Required for RateHawk when the API request does not originate directly from that traveler.
"8.8.8.8"
Unique key per booking attempt from client. Reusing the same key with same payload returns the same booking object.
"order-2987349823-booking-1"
Hotel API contract version. Missing defaults to v1.
1 Body
Prebooking token from prebook response (data.prebooking_id).
"pbq7.bjNQb1Y5R2hRWEg.k6jQ3nW4cF1nL4MWvN7A9Q"
Exact prebook offer being accepted.
Guest assignments keyed by room_allocation_id. Array order is not significant.
Show child attributes
Show child attributes
Booking mode. HOLD is accepted only when the prebook lifecycle explicitly lists HOLD; currently only certified TripJack offers can do this.
INSTANT, HOLD