# Hotels

Hotel search and booking over one stream of inventory. You search a destination, check the price of a rate, book, and read the booking back. The inventory comes from several suppliers, and the contract does not change when the mix does.

This guide explains the flow. Field-by-field detail is in the [Hotel reference](/developers/reference/v1/hotel).

| Step | Endpoint | Gives you |
|---|---|---|
| 1 | `GET /api/v1/hotel/content/destinations` | `id_location` to search |
| 2 | `POST /api/v1/hotel/distribution/hotelSearch` | Hotels and a `rate_key` for each rate |
| 3 | `POST /api/v1/hotel/distribution/hotelCheckPrice` | The current price and conditions of a rate |
| 4 | `POST /api/v1/hotel/distribution/bookingConfirm` | `id_orden`, the only identifier of the booking |
| 5 | `GET /api/v1/hotel/distribution/bookingDetail` | The state of the booking |
| 6 | `GET /api/v1/hotel/distribution/bookingHcn` | The hotel's own confirmation number and the final voucher |
| 7 | `POST /api/v1/hotel/distribution/bookingCancelation` | Cancels the booking |

Content endpoints (`content/hotelData`, `content/hotelDetails`, `content/locations`) give you descriptions, facilities and images for your own pages.

All calls need your Bearer credential. Send it only in the header. See [Authentication](/developers/guides/authentication).

## 1. Find a destination

```bash
curl -G "https://highstartravel.com/api/v1/hotel/content/destinations" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  --data-urlencode "q=orlando"
```

The `id` of each entry is the `id_location` you pass to `hotelSearch`. Entries of `type: "destination"` are places and entries of `type: "hotel"` are single hotels. With `q` you get type-ahead results. Without `q` you get the whole list, paginated with `page` and `limit` (up to 500).

The list depends on your account. Do not hard-code destination ids. The response can be cached for an hour.

`content/locations` returns the raw tree of countries, divisions and destinations, for partners who sync the geography once a day. Every id in it is ours. To drill down, pass the `id` of a country row as `country_id`, or the `id` of a division row as `division_id`. The `country_id` and `division_id` of each row hold those same ids.

## 2. Search

```bash
curl -X POST "https://highstartravel.com/api/v1/hotel/distribution/hotelSearch" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{
    "from_date": "2026-11-15",
    "to_date": "2026-11-20",
    "id_location": 1234,
    "occupancies": [
      { "adults": 2, "childrenAges": [10, 8] },
      { "adults": 2 }
    ]
  }'
```

- `from_date` is the check-in and `to_date` the check-out. The check-out must be after the check-in.
- There is one object in `occupancies` for each room. `adults` goes from 1 to 20.
- `childrenAges` is a list of ages, from 0 to 17, not a count. Declare every child, babies included.
- A check-in that is too close returns `400 Date not bookable`. The `message` has the earliest date. The minimum can change, so read it from the `message` and do not hard-code it.

### Two ways to receive results

A search can take a while. The default mode, `paged`, gives you results as they arrive:

1. The first call starts the search and returns what is ready after a second or two, with `finish: false` and an `id_search`.
2. Repeat the same call with the `id_search` added, every 1 to 3 seconds. Each answer has all the hotels found so far.
3. Stop when you get `finish: true`.

An `id_search` that does not exist or has expired returns `400 Unknown or expired id_search`. Start a new search.

The other mode, `"mode": "full"`, waits on the server until the search ends and returns everything in one answer. If the wait runs out, you get what was found with `finish: false` and an `id_search` to continue in `paged` mode. Use `paged` for a screen a person is waiting at, and `full` for batch jobs. A search typically finishes in 10 to 30 seconds.

A repeated search within about two hours can come back from cache, with `isCached: true`. Send `Accept-Encoding: gzip`. Responses can be several megabytes.

### Read the result

Each hotel has one entry in `occupancies` per room you asked for, in the same order. Choose one rate in each entry.

```json
{
  "hotels": [
    {
      "id_hotel": 5821,
      "hotel_name": "Example Hotel",
      "adults_only": false,
      "occupancies": [
        {
          "room": 1, "adults": 2, "childrenAges": [10, 8],
          "rates": [
            {
              "id_room": "123",
              "room_name": "Standard Room, 2 Queen Beds",
              "board": { "name": "Room Only" },
              "currency": "USD",
              "price": 612.50,
              "price_total": 612.50,
              "room_quantity": 1,
              "non_refundable": false,
              "cancelation_fees": [ { "from": "2026-11-08T00:00:00", "amount": 122.50, "amount_total": 122.50 } ],
              "rate_key": "opaque-string"
            }
          ]
        }
      ]
    }
  ],
  "finish": true,
  "isCached": false,
  "id_search": "84213",
  "request_id": "3f8a9b7c1d2e4f5a6b7c8d9e0f1a2b3c"
}
```

- `price` is the price of that room, with your pricing applied. The total of the booking is the sum of the `price` of the rates you choose.
- Hotels that cannot cover every room you asked for are not listed.
- If you ask for several identical rooms, a supplier may return one grouped rate that covers them all. The response still gives you one entry per room. The grouped rate appears in each of its entries with the same `rate_key`. The `price` is already divided per room, and `price_total` has the total of the rate. `room_quantity` tells you how many rooms the rate covers.
- `non_refundable: true` marks a rate that cannot be refunded.

### Adults-only hotels

Some hotels do not accept children. They come with `adults_only: true` and, when known, `adults_min_age`. If your search includes children, these hotels are not returned. Show the restriction on your own pages. Always declare every child in `childrenAges`: the hotel sees them at check-in even if your booking did not list them.

## 3. The rate key

The `rate_key` is an opaque, short-lived string. Do not store it and do not change it. Use it in the same search session. If a later call says the key is expired or invalid, search again.

The field name changes at each step. The value is the same string:

| Where | Field |
|---|---|
| `hotelSearch` response | `rate_key` |
| `hotelCheckPrice` request | `rate_key` |
| `hotelCheckPrice` response | `rateKey` |
| `bookingConfirm` request | `ratekey`, a list |

## 4. Check the price

Call this right before you confirm. It asks the supplier again and applies the same rules as the booking, so a rate that passes here is not refused at confirmation for a reason this call could have shown.

```bash
curl -X POST "https://highstartravel.com/api/v1/hotel/distribution/hotelCheckPrice" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{ "rate_key": "opaque-string" }'
```

The price to show the customer is in `hotels[0].occupancies[].rooms[].rates[].prices.price` and in `total.price`. Use the same `rate_key` to confirm. This call does not return a new one.

| Status | `error` | Meaning |
|---|---|---|
| 502 | `External API error` | The rate is no longer available, or the key expired. Search again. |
| 400 | `Rate not bookable` | The rate cannot be booked. The `message` says why. Choose another rate. |

## 5. Book

```bash
curl -X POST "https://highstartravel.com/api/v1/hotel/distribution/bookingConfirm" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{
    "ratekey": ["opaque-string-room-1", "opaque-string-room-2"],
    "bookingHolder": { "name": "John", "surname": "Doe", "phonePrefix": "+54" },
    "external_reference": "MY-HOTEL-ORDER-123"
  }'
```

- `ratekey` is a list with one entry per room: the key of the rate you chose in each room. If a grouped rate covers several rooms, send its key once for each room. Sending it once is also accepted. A grouped key repeated a different number of times returns `400 Invalid rate_key repetition` and nothing is created.
- `bookingHolder` needs `name` and `surname`.
- Always send `external_reference`. See [Conventions](/developers/guides/conventions#idempotency).

Before it creates anything, the call asks the supplier for every rate again. If a rate is gone you get `400 RateKey expired`. If a rate fails a booking rule you get `400 Rate not bookable`. A booking is made at the price valid at that moment, which can differ from the search. That is why you call `hotelCheckPrice` first and show the customer that price.

The response has the order id:

```json
{
  "statusCode": "SUCCESS",
  "id_orden": 91783,
  "booking": {
    "status": "CO",
    "checkIn": "2026-11-15",
    "checkOut": "2026-11-20",
    "total": { "price": 1225.00, "currency": "USD" }
  },
  "pdf_voucher": "https://example.com/voucher.pdf",
  "request_id": "3f8a9b7c1d2e4f5a6b7c8d9e0f1a2b3c"
}
```

- Keep `id_orden`. It is the only identifier of the booking, and the one you use to cancel, read the detail and ask for the confirmation number.
- `booking.hotels[].rooms[]` has one entry per physical room. `hotelLocator` is always `pending` here. The hotel's number comes from `bookingHcn`.
- `pdf_voucher` is the voucher without the hotel's number. It can be missing if voucher delivery by API is switched off for the service.

If the supplier refuses, you get `400 Booking rejected`. Nothing was reserved and nothing was charged. The `message` gives the reason in a fixed sentence. If any room of a multi-room booking cannot be booked, the whole booking is refused. A failed call never returns `id_orden`.

If `bookingConfirm` times out, repeat it with the same `external_reference`. The API looks up the reference before it checks the rates again. If the first booking exists, you get it back with `200` and `"idempotent_replay": true`, in the same shape as the first answer, even if the `rate_key` has expired in the meantime. This shortcut does not apply to a credential in test mode. Never repeat with another reference. See [Conventions](/developers/guides/conventions#idempotency).

## 6. Read the booking and the confirmation number

```bash
curl -G "https://highstartravel.com/api/v1/hotel/distribution/bookingDetail" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  --data-urlencode "id_orden=91783"
```

`booking.status` is `confirmed`, `cancelled` or `error`. `error` means a cancellation failed on the supplier side. A booking that does not exist, or belongs to another account, returns `404 Booking not found` and nothing tells you which of the two it is.

`bookingHcn` returns the Hotel Confirmation Number, the number the hotel itself gives to the room. `hcn_status` is `pending` until the number is final, `available` when it is, and `cancelled` when every room is cancelled. `rooms[]` has one entry per room, with `hcn: null` for a room whose number has not arrived yet. The `pdf_voucher` that comes with `available` includes the number. Do not poll this call often. The status changes at most a few times in the life of a booking. Ask for it when you need the number for the traveler.

## 7. Cancel

```bash
curl -X POST "https://highstartravel.com/api/v1/hotel/distribution/bookingCancelation" \
  -H "Authorization: Bearer YOUR_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{ "id_orden": 91783 }'
```

The whole booking is cancelled. The cancellation charges are the `cancelation_fees` you saw at search and check-price time.

| Status | `error` | Meaning |
|---|---|---|
| 400 | `No active reservation found.` | No active hotel booking of your account has that `id_orden`, or it is already cancelled. |
| 400 | `Cancellation not allowed` | The booking does not belong to your credential. |
| 400 | `Cancellation rejected` | The supplier refused. The booking is still active and unchanged. |

## Walt Disney World resorts

Some accounts can also sell Walt Disney World resorts through the same endpoints. If your account does not have them, asking for one returns `403 Access forbidden`. Ask your account manager to enable them.

The flow is the same, with these differences:

- Find "Walt Disney World Resorts - All Hotels" or a single resort in `content/destinations`.
- A Disney search takes exactly one occupancy, which means one room. It cannot be mixed with another kind of location. It returns all results in the first call, so there is nothing to poll and `finish` is always `true`.
- Each rate can carry `promo`, `package` and `inclusions`, which describe what is included, such as the room and a park ticket.
- The `rate_key` of a Disney rate lives only for a short time. Call `hotelCheckPrice` right after the search. Its answer has `expires_at`: confirm before that time, or quote again. It also has `ticket` (the ticket already included, or `null`) and `ticket_options[]`: the same room with other ticket packages, each with its own `rate_key`, price and cancellation terms.
- A Disney booking is one `rate_key` for one room. The supplier needs every guest with an age, so you send the holder in `bookingHolder` and all the other guests in `guests`:

```json
{
  "ratekey": ["rate-key-from-checkprice-or-ticket-options"],
  "bookingHolder": { "name": "Ana", "surname": "Perez", "email": "ana@example.com", "phone": "+54 9 11 5555-1234", "country": "AR" },
  "guests": [
    { "name": "Juan", "surname": "Perez", "age": 40 },
    { "name": "Leo", "surname": "Perez", "age": 7 }
  ],
  "external_reference": "MY-HOTEL-ORDER-124"
}
```

The number of guests, and how many of them are under 18, must match the occupancy you searched. If not, you get `400 Invalid guests` and the message says what is missing.

## Accounts with a limited catalog

An account can be limited to Universal Orlando hotels. If you ask for a location or hotel outside your catalog you get `403 Catalog restricted`. `content/destinations` already returns only what you can sell.

## What not to do

- Do not store a `rate_key` for later. It is short-lived.
- Do not confirm without calling `hotelCheckPrice` first.
- Do not leave a child out of `childrenAges`. Declare every child, babies included.
- Do not retry a timed-out `bookingConfirm` with a new `external_reference`.

## Next

- [Hotel reference](/developers/reference/v1/hotel) has every field and response.
- [Sandbox](/developers/guides/sandbox) explains how to test a booking.
- [Errors](/developers/guides/errors) explains every status code.
