# Hotels API overview

> Geo search around a point, name search, hotel content, rates, pre-book, bookings that confirm at once, vouchers and cancellation.

Source: https://fx-port.com/docs/hotels

Search, price and book hotels worldwide through one API. Hotel content comes as compact **mini** cards for result lists and as a **full** document for the hotel page: [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md).

For a summary of what the API covers, who it is for and how to get access, see the [hotel booking API for travel agencies](https://fx-port.com/b2b-hotels-api).

**The flow: [locations](https://fx-port.com/docs/hotels/locations.md) → [geo search](https://fx-port.com/docs/hotels/search.md) → [rates](https://fx-port.com/docs/hotels/rates.md) → [pre_book](https://fx-port.com/docs/hotels/pre-book.md) → [book](https://fx-port.com/docs/hotels/book.md).** Turn a typed city into coordinates, run a **geo search** (`POST /hotels/search_advanced` with `latitude`, `longitude` and `radius`) to list the hotels around them with their prices, read the rooms and the cancellation terms of the hotel the user opens, lock the rate and book it. Then read the booking, get the voucher, or cancel. **Booking a hotel confirms it: there is no hold or pay-later option for API keys, and your agency is debited as part of the booking, when it is confirmed** (the sandbox debits nothing).


> [!CAUTION]
> **In the sandbox you can search and price any hotel, but you can only book the test hotels**
>
> A sandbox key searches and prices **any** hotel on real supplier inventory, but it can **book only the supplier's test hotels**: `test_hotel_do_not_book` (every residency: use this one) and `test_hotel` (residency `dz`, `ru` or `us` only). `pre_book` on any other hotel answers `409 hotel_not_bookable_in_sandbox` and lists the test hotels; `book_hotel` on it would fail with `order_not_found`. Every sandbox booking is a real order on the supplier's test contract: **cancel it**. [Sandbox testing](https://fx-port.com/docs/hotels/sandbox-testing.md) has the ids, the date rules and a script that books and cancels.

A hotel booking, end to end. Actors: Your server, FX-Port API, Hotel supplier.

1. Your server to FX-Port API: GET /hotels/locations/search
2. FX-Port API to Your server: Coordinates of the destination
3. Your server to FX-Port API: POST /hotels/search_advanced (geo search)
4. FX-Port API to Your server: Mini hotel cards with prices
5. Your server to FX-Port API: POST /hotels/get_hotel_info (hotel page)
6. Your server to FX-Port API: POST /hotels/get_rates
7. FX-Port API to Your server: Rooms, rates, book_hash
8. Your server to FX-Port API: POST /hotels/pre_book
9. FX-Port API to Your server: Locked price, partnerOrderId
10. Your server to FX-Port API: POST /hotels/book_hotel
11. FX-Port API to Hotel supplier: Book the room
12. Your server to FX-Port API: GET /bookings/{id}?product=hotels
13. FX-Port API to Your server: Confirmed booking with the voucher


## Endpoints

All paths are below `https://api.fx-port.com`. **Read** works with any key; **Read+Write** needs a key with read_write permission (a read key answers `403 read_write_permission_required`).

| Method | Path | Permission | What it does |
|---|---|---|---|
| `GET` | `/api/v1/hotels/locations/search` | Read | Turn a city, an area or an airport typed by a user into coordinates for the geo search. [Search locations](https://fx-port.com/docs/hotels/locations.md) |
| `POST` | `/api/v1/hotels/search_advanced` | Read | **Geo search**: the hotels around a point (or one hotel by id), as mini cards with prices. [Geo search](https://fx-port.com/docs/hotels/search.md) |
| `GET` / `POST` | `/api/v1/hotels/name-search` / `/api/v1/hotels/search-by-name` | Read | Find a hotel by name. [Hotel name search](https://fx-port.com/docs/hotels/name-search.md) |
| `POST` | `/api/v1/hotels/search`, `/api/v1/hotels/details` | Read | Content of up to 200 hotels by id, as mini cards or full documents. [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md) |
| `POST` | `/api/v1/hotels/get_hotel_info` | Read | The full content of one hotel for its page, and the content of a hotel FX-Port does not hold yet. [Hotel details](https://fx-port.com/docs/hotels/hotel-details.md) |
| `POST` | `/api/v1/hotels/get_rates` | Read | Every room and rate of a hotel for a stay, with cancellation terms. [Get hotel rates](https://fx-port.com/docs/hotels/rates.md) |
| `POST` | `/api/v1/hotels/pre_book` | **Read+Write** | Lock the rate you chose. [Pre-book a rate](https://fx-port.com/docs/hotels/pre-book.md) |
| `POST` | `/api/v1/hotels/book_hotel` | **Read+Write** | Book the locked rate for the guests of each room. [Book a hotel](https://fx-port.com/docs/hotels/book.md) |
| `GET` | `/api/v1/bookings/{booking_id}?product=hotels` | Read | The status of a booking, with its price and voucher links. [Get a hotel booking](https://fx-port.com/docs/hotels/get-booking.md) |
| `GET` | `/api/v1/history?product=hotels` | Read | The hotel bookings of your agency, newest first. [Booking history](https://fx-port.com/docs/account/history.md) |
| `GET` | `/api/v1/hotels/get_voucher` | Read | The links of the voucher PDFs. [Get the hotel voucher](https://fx-port.com/docs/hotels/voucher.md) |
| `POST` | `/api/v1/hotels/cancel_hotel` | **Read+Write** | Cancel a confirmed booking. [Cancel a hotel booking](https://fx-port.com/docs/hotels/cancel.md) |

The [API reference](https://fx-port.com/docs/api.md) has every field, parameter and error of each call.

## The flow, step by step


**Step 1: Find the place**

[`locations/search`](https://fx-port.com/docs/hotels/locations.md) turns a typed city into `latitude` and `longitude`, the input of the geo search. [`name-search`](https://fx-port.com/docs/hotels/name-search.md) finds a hotel by name and gives its `id`.

**Step 2: Geo search**

[`search_advanced`](https://fx-port.com/docs/hotels/search.md) is the **geo search**: send `latitude`, `longitude` and a `radius` (or a `hotel_id`) with the dates and the guests. It answers the **mini cards** in `hotels_details` and the price of each hotel in `hotels_pricing`. Join them by hotel id. Hotels that have a price but no card are in `missing_hotel_ids`: see [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md).

**Step 3: Show the hotel and its rooms**

When the user opens a hotel, call [`get_hotel_info`](https://fx-port.com/docs/hotels/hotel-details.md) for its full page and [`get_rates`](https://fx-port.com/docs/hotels/rates.md) for its rooms. Each rate has a `bookHash` and a cancellation schedule.

**Step 4: Lock the rate**

[`pre_book`](https://fx-port.com/docs/hotels/pre-book.md) with the `bookHash` re-checks the price with the supplier and answers a `partnerOrderId`: the id of the booking you are about to make.

**Step 5: Book**

[`book_hotel`](https://fx-port.com/docs/hotels/book.md) with that id as `booking_id`, the guests of each room and the contact. It answers `pending`.

**Step 6: Wait for the confirmation**

Poll [`GET /bookings/{booking_id}?product=hotels`](https://fx-port.com/docs/hotels/get-booking.md) every 2 seconds until `status` is `confirmed` (4 to 14 seconds). FX-Port sends **no webhooks**.

**Step 7: Voucher, or cancel**

[`get_voucher`](https://fx-port.com/docs/hotels/voucher.md) gives the voucher links. [`cancel_hotel`](https://fx-port.com/docs/hotels/cancel.md) cancels a confirmed booking.


What you carry from one call to the next:

| From | You keep | You send it to |
|---|---|---|
| `locations/search` | `latitude`, `longitude` | `search_advanced` (the geo search) |
| `name-search`, `search_advanced` | the hotel `id` | `hotel_id` of `get_rates` and `get_hotel_info` |
| `get_rates` | `rates[].bookHash` (twin `book_hash`) | `book_hash` of `pre_book` |
| `pre_book` | `partnerOrderId` (twin `booking_id`) | `booking_id` of `book_hotel`, `get_voucher`, `cancel_hotel` and the path of `GET /bookings/{booking_id}` |

## Before you start

| | |
|---|---|
| **Base URL and key** | `https://api.fx-port.com/api/v1`. A key starting with `fxp_test_` works on the sandbox and `fxp_live_` on live: [Authentication](https://fx-port.com/docs/authentication.md). |
| **Permissions** | Searches, content, rates, vouchers and booking reads need **Read**. `pre_book`, `book_hotel` and `cancel_hotel` need **Read+Write**. |
| **Names** | Fields you send are `snake_case` (`hotel_id`, `book_hash`, `booking_id`). The older spellings (`id`, `hash`, `partner_order_id`, `hotelId` ...) still work and are answered with a `warnings` array. Answers keep their keys, mostly camelCase after `get_rates`, and add snake_case twins of the keys you feed back; `?naming=snake` returns snake_case only. See [Conventions](https://fx-port.com/docs/conventions.md#naming). |
| **Currency** | Prices come in the currency of your agency, resolved from your key. **Never send `currency`**: it is refused. See [Hotel pricing](https://fx-port.com/docs/hotels/pricing.md). |
| **Money** | The traveler pays `totalPrice`; your agency is debited `totalPrice - agencyCommission`. There is no `b2bPrice` in the camelCase price blocks. |
| **Stays** | `checkin` and `checkout` as `YYYY-MM-DD`, 1 to 30 nights, check-in up to 400 days ahead. [Guests and rooms](https://fx-port.com/docs/hotels/guests-and-rooms.md): 9 rooms at most, one `{ adults, children }` object per room. |
| **Values** | A value written in another well-defined way is read, and the answer says so in `warnings` (`coerced_value`): a number as text, a date with a midnight time, `GB-SCT` as residency, a radius outside 100 to 70,000 meters. Only what cannot be read is refused. See [Values the API reads for you](https://fx-port.com/docs/hotels/search.md#values-the-api-reads-for-you). |
| **Rate limits** | Per key and per minute: 6,000 for searches, rates, content and reads (a booking, a voucher), 1,200 for `pre_book`, `book_hotel` and `cancel_hotel`. Searches also have a ceiling on the calls that run at the same time. [Rate limits](https://fx-port.com/docs/rate-limits.md). |
| **Webhooks** | None. Poll the booking. |
| **Time** | A search around a point takes 4 to 20 seconds, `get_rates` 0.5 to 5, `pre_book` 4 to 5, `book_hotel` 3 to 6, `cancel_hotel` 1 to 4. A booking is confirmed 4 to 14 seconds after `book_hotel`. |

## Concepts


- [Sandbox testing](https://fx-port.com/docs/hotels/sandbox-testing.md): The test hotels, what happens with any other hotel, and a script that books and cancels.
- [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md): Compact cards for lists, the full document for the hotel page, and the hotels that have a price but no card yet.
- [Guests and rooms](https://fx-port.com/docs/hotels/guests-and-rooms.md): Several rooms, adults, children and their ages, residency and guest names.
- [Hotel pricing](https://fx-port.com/docs/hotels/pricing.md): What each price field means and what your agency is debited.


## Errors

Every failed call answers one body, whatever the endpoint: `errors[]` with a stable `code`, next to the older keys. Branch on the `code`: see [Errors](https://fx-port.com/docs/errors.md). The ones you meet in the hotel flow:

| HTTP | Code | What to do |
|---|---|---|
| `400` | `missing_field`, `invalid_date`, `stay_too_long`, `invalid_guests`, `invalid_residency` ... | The request is wrong. A missing required field is `missing_field`: `errors[0].field` names it (the current name) and `errors[0].details.missing_fields` lists all of them when several are missing. Read `errors[0].field` and `message`, fix it. |
| `400` | `invalid_status` | The booking is not `confirmed`: wait, or it is already cancelled. |
| `400` | `rate_not_found` | The rate expired or its price rose: call `get_rates` again. |
| `403` | `read_write_permission_required` | Use a Read+Write key. |
| `404` | `booking_not_found`, `pdf_not_found` | No such booking of your agency; or the voucher is not made yet (`get_voucher`): ask again in 2 seconds. |
| `409` | `hotel_not_bookable_in_sandbox` | Sandbox: book `test_hotel_do_not_book`. |
| `409` | `duplicate_booking` | The booking exists. Do not call `book_hotel` again: read its status. |
| `409` | `cached_offer_required` | The offer of `pre_book` was not found (older than one hour on live, or locked by another agency): `pre_book` again. |
| `422` | `supplier_rejected_request` | The supplier refused well formed dates, guests, residency or radius (`search_advanced`): change them. |
| `429` | `rate_limit_exceeded` | Wait for `Retry-After`, then back off. |
| `502` | `supplier_error`, `supplier_invalid_response`, `bad_gateway` | The supplier could not answer, or answered in a form that could not be used. Retry a **read** with backoff. For `book_hotel` and `cancel_hotel`, read the booking first. |
| `503` | `supplier_busy`, `service_overloaded` | The supplier, or the service, is at capacity. The call was **refused before anything was sent**: nothing was searched, locked, booked or debited. Wait the seconds of `Retry-After`, then repeat the same call. |
| `503` | `supplier_unavailable`, `supplier_timeout` | The supplier could not be reached or did not answer. Retry a **read** with backoff. For `cancel_hotel`, read the booking first. |

A `200` is not always a hotel: **`get_rates` answers `200` with an empty `rates`** when a hotel is unknown, closed, not available for the dates or not available for the residency, and `search_advanced` answers `200` with `no_hotels_found`. Check the content of a `200`.

### What is safe to repeat

| Call | Safe to repeat? |
|---|---|
| Locations, name search, `search_advanced`, content, `get_rates`, `get_voucher`, reading a booking | Yes. |
| `pre_book` | Yes: the same rate and the same `price_increase_percent` within a minute get the same answer. |
| `book_hotel` | **No.** A second call answers `409 duplicate_booking` and does not reach the supplier. After a timeout or a `5xx` (except `503 supplier_busy`, which was refused before anything was sent), read `GET /bookings/{booking_id}`: if it exists, the booking was made. |
| `cancel_hotel` | Yes after a timeout: a second call settles it, also when the supplier had already cancelled. Once the cancellation is recorded, a further call answers `400 invalid_status`. |

## Questions?

Email the FX-Port developer team at [dev@fx-port.com](mailto:dev@fx-port.com) for integration help, feature requests, or to report an API error. Quote the `request_id` of the failed call.
