Skip to content

Booking Flow

Search

Availability and prices for a hotel, a city, a country or an area, in one call, with a bookable rate key on each rate.

POST /api/v1/search x-api-key

A search names when (check-in plus either nights or checkOut), who (rooms) and where — and where can be given three different ways. Everything else has a sensible default.

Saying Where to Look

One of these three is required.

propertyIds
string[] optional
Specific hotels, each a Gateway Stream propertyId (uuid) as GET /properties returns it. Several hotels in one request are priced together.
destination
object optional
A place: { city, country }, or just { country } for a whole country. Take them from GET /destinations — country takes an ISO-2 code or a name. Every hotel there is priced, up to a per-search ceiling (see below).
geo
object optional
{ lat, lng, radiusKm } — a real distance filter against stored coordinates. It covers hotels Gateway Stream already holds a position for: a hotel nobody has listed or searched yet is not found this way.

Request Body

Required

checkIn
string (YYYY-MM-DD) required
First night of the stay.
rooms
object[] required
One entry per room. Each is { adults, childAges } — adults a positive integer, childAges a list of ages from 0 to 17 (empty for no children).
nights | checkOut
integer | string required
How long the stay is. Send nights (a positive integer) or checkOut (YYYY-MM-DD); one of the two is required.

Optional

currency
string (ISO 4217) default "USD"
The currency rates should be quoted in.
nationality
string (ISO 3166-1 alpha-2) optional
The guests’ nationality. Some rates are priced differently by nationality, and some are restricted by it.
language
string optional
Preferred language for room names, descriptions and remarks.
filters
object optional
refundableOnly (boolean), boards (a list of board codes), minStars (number), maxPrice (number). Applied to the results before they are returned.
sort
"price_asc" | "price_desc" default "price_asc"
Ordering of the rates within the response.
timeoutMs
integer default 2500
How long to wait for the sources, 1–8000 ms. Whatever has answered when it elapses is what comes back.

Examples

By property
{
  "checkIn": "2026-12-05",
  "nights": 2,
  "rooms": [
    { "adults": 1, "childAges": [] }
  ],
  "currency": "USD",
  "propertyIds": [
    "a9702286-9b22-498d-8bc9-97471f68d798"
  ]
}

Response

searchId
string optional
Identifies this search. Worth logging next to the rate keys it produced — it is what Gateway Stream traces a pricing question by.
currency
string optional
The currency every price in the response is quoted in.
properties
object[] optional
One entry per hotel that returned rates. See Objects for the full shape.
meta.complete
boolean optional
false when a source did not answer within timeoutMs (or failed), so rates may be missing. It does not say which source, or why.
200 OK
{
  "searchId": "srch_01JD8Q5T4K9N2W7Y3X6B0MHE4A",
  "currency": "USD",
  "properties": [
    {
      "propertyId": "a9702286-9b22-498d-8bc9-97471f68d798",
      "name": "Kadoma Hotel & Conference Centre",
      "stars": 4,
      "location": {
        "city": "Kadoma",
        "country": "Zimbabwe",
        "lat": -18.3333,
        "lng": 29.9167
      },
      "image": "https://cdn.gatewaystream.com/properties/kadoma/exterior.jpg",
      "mapped": true,
      "fromPrice": { "amount": 164.00, "currency": "USD" },
      "rooms": [
        {
          "roomCode": "DBL-STD",
          "roomName": "Double Standard",
          "occupancyRef": 0,
          "rates": [
            {
              "rateKey": "rk_01JD8Q5T6H2V8ZCN4PQ1RMB7XE",
              "rateCode": "BAR_USD",
              "rateName": "Best Available Rate",
              "board": "RO",
              "price": {
                "net": 150.00,
                "sell": 164.00,
                "currency": "USD",
                "binding": false
              },
              "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."
            },
            {
              "rateKey": "rk_01JD8Q5T6H2V8ZCN4PQ1RMB8YF",
              "rateCode": "BAR_BB_USD",
              "rateName": "Bed & Breakfast",
              "board": "BB",
              "price": {
                "net": 178.00,
                "sell": 194.00,
                "currency": "USD",
                "binding": false
              },
              "nightly": [
                { "date": "2026-12-05", "amount": 97.00 },
                { "date": "2026-12-06", "amount": 97.00 }
              ],
              "taxesAndFees": [],
              "refundable": false,
              "cancelPolicy": [
                { "from": "2026-11-20T00:00:00Z", "amount": 194.00, "currency": "USD" }
              ],
              "paymentType": "PREPAID"
            }
          ]
        }
      ]
    }
  ],
  "meta": { "complete": true }
}

Reading the Result

Each property carries its rooms, each room its rates, and each rate the rateKey that books it. The fields that matter most on a rate:

rateKey
string optional
What POST /bookings redeems. Opaque, single-use, good for about 30 minutes.
price.sell
number optional
Your price for the whole stay, markup already applied. This is the figure to display and the figure to send as expectedPrice.
price.net
number optional
What Gateway Stream pays for the room. Shown for reconciliation, not for display.
price.binding
boolean optional
true means the price is guaranteed. A binding rate is never marked down below its quoted gross.
board
"RO" | "BB" | "HB" | "FB" | "AI" optional
Room only, bed & breakfast, half board, full board, all inclusive.
refundable
boolean optional
Whether any cancellation window exists at all. The deadlines and amounts are in cancelPolicy.
cancelPolicy
object[] optional
The cancellation ladder: each entry is { from, amount, currency }, meaning “from this moment, cancelling costs this much”. An empty list means no penalty was quoted.
nightly
object[] optional
Per-night breakdown, { date, amount }. May be empty.
taxesAndFees
object[] optional
{ name, amount, included, payAtHotel }. Anything with payAtHotel: true is collected by the hotel and is not part of price.sell.
paymentType
"PREPAID" | "PAY_AT_HOTEL" optional
Who collects the room charge.

Status Codes

200
The search ran. properties may be empty, and a source may have been slow or failed — read meta.complete.
400 validation_error
A malformed body: a bad date, no occupancy, neither nights nor checkOut, none of propertyIds / destination / geo, a propertyIds entry that is not a uuid, or a destination with neither a city nor a country.
401 unauthorized
Missing or invalid x-api-key.
429 rate_limited
Too many requests. Back off for details.retryAfterSeconds.
503 mode_unavailable
This deployment has no configuration for the environment your key points at.

Practical Notes

  • Search close to the booking. Rate keys expire in about half an hour. If a guest lingers, search again rather than risking a 410 at the worst moment.
  • Give slow sources room. The default timeoutMs of 2500 favours a fast page. For a results page that can fill in progressively, 6000–8000 catches the slower sources that the default misses.
  • Don't cache rates. They move, and a cached rate key is a key that is already closer to expiry than you think.
  • One hotel may appear twice. The same property reached more than one way is two entries with two propertyId values. Deduplicate on your side if that matters to your UI.
  • A request field Gateway Stream does not know is ignored, not rejected. Check spelling against this page: a misspelt optional field does nothing.