Skip to content

Booking Flow

Book

Redeem a rate key, name the guests, state the price you expect — and hold the reservation.

POST /api/v1/bookings x-api-key

Booking spends a rateKey from search. The rate key already carries the property, the dates, the occupancy, the board and the price, so the request body is mostly about people and money.

Request Body

Required

leadGuest
object required
The person the booking is in the name of. Requires name.first, name.last, contact.email and contact.phone. Optionally title, birthDate and the address fields contact.address, city, country, zip.
rooms
object[] required
One entry per room, each with its own rateKey and the guests staying in it. A guest is { title?, name: { first, last }, age? }. specialRequests is an optional list of strings passed to the hotel. Every rate key in one request must be for the same property.
expectedPrice
{ amount, currency } required
What you expect the total to be — the price.sell figure from search. A live total above this is rejected with 409 price_changed rather than booked.

Optional

reference.client
string (≤100) optional
Your own reference for this booking. Returned on every read as clientReference, which makes reconciliation against your orders straightforward.
payment
object optional
{ mode: "gws_collected", transactionRef? } — who collected the guest’s money. Recorded, not charged: the API never takes card details.
isTest
boolean default false
Asks the supplier for a test booking. Forced on in sandbox, so it only has an effect with a live key.
Body
{
  "reference": { "client": "ACME-2026-00417" },
  "leadGuest": {
    "title": "Ms",
    "name": { "first": "Test", "last": "Guest" },
    "contact": {
      "email": "test@example.invalid",
      "phone": "+263700000000",
      "country": "ZW"
    }
  },
  "rooms": [
    {
      "rateKey": "rk_01JD8Q5T6H2V8ZCN4PQ1RMB7XE",
      "guests": [
        { "name": { "first": "Test", "last": "Guest" }, "age": 30 }
      ],
      "specialRequests": ["Late arrival, around 22:00"]
    }
  ],
  "expectedPrice": { "amount": 164.00, "currency": "USD" },
  "payment": { "mode": "gws_collected", "transactionRef": "pay_8812f0" },
  "isTest": true
}

Response

Success returns a booking view — the same shape GET /bookings/:id serves — and it says what was bought: the hotel, each room with the guests in it, and the rate it was sold at. Keep it; showing the guest their reservation never needs another search.

201 Created
{
  "mode": "sandbox",
  "bookingId": "0b5e1a92-6c3f-4a7d-8e21-9b4f0d3c5a18",
  "status": "CONFIRMED",
  "clientReference": "ACME-2026-00417",
  "checkIn": "2026-12-05",
  "checkOut": "2026-12-07",
  "nights": 2,
  "property": {
    "propertyId": "a9702286-9b22-498d-8bc9-97471f68d798",
    "name": "Kadoma Hotel & Conference Centre",
    "stars": 4,
    "country": "Zimbabwe",
    "city": "Kadoma",
    "image": "https://cdn.gatewaystream.com/properties/kadoma/exterior.jpg"
  },
  "rooms": [
    {
      "roomCode": "DBL-STD",
      "roomName": "Double Standard",
      "guests": [
        { "name": { "first": "Test", "last": "Guest" }, "age": 30 }
      ],
      "price": { "amount": 164.00, "currency": "USD" },
      "rate": {
        "rateCode": "BAR_USD",
        "rateName": "Best Available Rate",
        "board": "RO",
        "nightly": [
          { "date": "2026-12-05", "amount": 82.00 },
          { "date": "2026-12-06", "amount": 82.00 }
        ],
        "taxesAndFees": [
          { "name": "City tax", "amount": 4.00, "included": false, "payAtHotel": true }
        ],
        "refundable": true,
        "cancelPolicy": [
          { "from": "2026-11-30T00:00:00Z", "amount": 82.00, "currency": "USD" },
          { "from": "2026-12-04T00:00:00Z", "amount": 164.00, "currency": "USD" }
        ],
        "paymentType": "PREPAID",
        "remarks": "Check-in from 14:00."
      }
    }
  ],
  "price": { "amount": 164.00, "currency": "USD" },
  "cancelPolicy": [
    { "from": "2026-11-30T00:00:00Z", "amount": 82.00, "currency": "USD" },
    { "from": "2026-12-04T00:00:00Z", "amount": 164.00, "currency": "USD" }
  ],
  "hotelConfirmationNumber": "KHC-558213",
  "createdAt": "2026-11-02T09:41:07.221Z"
}
bookingId
uuid optional
Store this. It is how you read or cancel the booking.
status
BookingStatus optional
CONFIRMED, ON_REQUEST, UNKNOWN or FAILED at this point in the lifecycle.
mode
"sandbox" | "live" optional
Which environment the booking lives in.
clientReference
string | null optional
The reference.client you sent, or null.
price
{ amount, currency } optional
The total actually booked.
cancelPolicy
object[] optional
The cancellation ladder as it stood when the booking was made. Keep it — it is what a cancellation preview is computed from.
hotelConfirmationNumber
string | null optional
The hotel’s own reference, when the supplier gave one.
checkIn / checkOut
string (YYYY-MM-DD) optional
The stay dates.
nights
integer optional
The length of the stay.
property
object | null optional
The hotel booked, in the same shape as on GET /properties. null only if the hotel has since been removed.
rooms
object[] optional
One entry per room booked. See below.
createdAt
string (ISO 8601) optional
When the booking was created.

Each room

roomCode
string | null optional
The room’s code, the same one search gave it.
roomName
string | null optional
The room’s name.
guests
object[] optional
Who is in this room, as you sent them: { title?, name: { first, last }, age? }.
price
{ amount, currency } optional
What this room cost. The rooms add up to the booking’s price.
rate
object | null optional
The rate it was sold at, as search showed it: rateCode, rateName, board, nightly, taxesAndFees, refundable, cancelPolicy, paymentType and any remarks. The rateKey is not repeated — it was spent by booking. null on a booking made before rates were kept with it.

Status Codes

201
Booked. A new reservation exists.
200
A replay. This Idempotency-Key was already used, and the booking it created is returned unchanged. Nothing new happened.
202 status: UNKNOWN
The supplier gave no definite answer. The reservation may or may not exist — see below.
400 validation_error
A malformed body, or a missing / too-short Idempotency-Key header.
401 unauthorized
Missing or invalid x-api-key.
409 conflict · price_changed
The total is higher than expectedPrice (details carries expected, actual and currency), or the rate itself was re-priced since it was quoted (details then carries just the currency). Nothing was booked: search again for the new price.
409 rate_unavailable
The rate is gone — sold out, stop-sold or withdrawn since it was quoted. Search again.
410 rate_key_expired
The rate key is unknown, has expired, or has already been used. Search again for a fresh one.
424 distributor_unavailable
The rate can no longer be booked: its source was switched off between search and book. Search again.
502 supplier_error
The booking was refused outright. Nothing was reserved. details.bookingId is the record kept of the attempt; quote the requestId if you contact support.
409 price_changed
{
  "error": {
    "code": "conflict",
    "message": "price_changed: the total is higher than expectedPrice",
    "requestId": "req_01JD8QA1V4C7M0S9",
    "details": {
      "expected": 164.00,
      "actual": 178.50,
      "currency": "USD"
    }
  }
}

The 202 UNKNOWN Case

Sometimes a supplier accepts a booking request and then fails to say whether it worked — a timeout at their end, a dropped connection, an ambiguous status. Gateway Stream does not guess. The booking is recorded with status: "UNKNOWN" and returned with 202.

202 Accepted
{
  "mode": "live",
  "bookingId": "6d2a7f41-0c88-4b39-91ad-3e5207cb9f64",
  "status": "UNKNOWN",
  "clientReference": "ACME-2026-00418",
  "checkIn": "2026-12-05",
  "checkOut": "2026-12-07",
  "nights": 2,
  "property": {
    "propertyId": "5d7d51fc-5b63-460e-83b8-560c52f81b0b",
    "name": "Horizon Inn",
    "stars": 3,
    "country": "Zimbabwe",
    "city": "Harare",
    "image": null
  },
  "rooms": [
    {
      "roomCode": "STD-DBL",
      "roomName": "Standard Double",
      "guests": [{ "name": { "first": "Test", "last": "Guest" }, "age": 30 }],
      "price": { "amount": 212.50, "currency": "USD" },
      "rate": null
    }
  ],
  "price": { "amount": 212.50, "currency": "USD" },
  "cancelPolicy": [],
  "hotelConfirmationNumber": null,
  "createdAt": "2026-11-02T09:44:55.104Z"
}

ON_REQUEST Bookings

ON_REQUEST means the supplier has taken the request but not confirmed it. The room is not guaranteed. Show it to the guest as pending, poll GET /bookings/:id rather than rebooking, and be ready for it to end as either CONFIRMED or FAILED.

Retrying Safely

  • Network error, no response at all — resend the identical body with the identical Idempotency-Key. Safe, and the only correct move.
  • 409 or 410 — nothing was booked. Search again and present the new price to the guest before trying once more.
  • 202 or 502 — stop. Do not book again with a new key; reconcile first.