# Hotel details

> The full page of one hotel, or its compact card with detail_level. Reads the cache and fetches an unknown hotel live.

Endpoint: `POST /api/v1/hotels/get_hotel_info`

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

Read **one hotel** for the page the user opens before checkout: the description, every room type, the photos, the policies, the facts and how to reach the hotel. It is the only content call that asks the supplier when FX-Port does not hold the hotel yet.

**Permission:** Read


> [!TIP]
> **Use the right size**
>
> This answer is the **full** document: 5 to 143 KB for one hotel (26 KB in the middle, 38 KB on average in our measurement). Ask for it for the **one** hotel the user opened. For a list of hotels use the compact card: `search_advanced` already returns it, and this call returns it too with `detail_level: "minimal"`. See [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md).


## Request body

`POST /api/v1/hotels/get_hotel_info`

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `hotel_id` | string or integer | One of the two | | One hotel: its FX-Port id (`hotels_details[].id`, a hit of the name search) or its numeric supplier id (`hid`). The only form that asks the supplier when the hotel is not cached. |
| `hotel_ids` | array | One of the two | | Several hotels, at most 200. **Cached hotels only**: the others are left out. Wins over `hotel_id` when both are sent. |
| `detail_level` | string | No | `full` | `full`: the whole document, in camelCase. `minimal` (alias `mini`): the compact card, in snake_case. |
| `images_limit` | integer | No | | With `detail_level: "minimal"`: keep only the first N photos of each card, 0 to 30. With `full` it is ignored and the answer carries a `coerced_value` warning. |
| `language` | string | No | `en` | Language of the live fetch of a hotel that is not cached. Cached content is English whatever you send. |

### Legacy names

These older spellings still work. The answer carries a `deprecated_field` warning that names the spelling to use (see [Legacy names](https://fx-port.com/docs/legacy-names.md)).

| Older name | Use instead |
|---|---|
| `hotelId`, `id`, `hid` | `hotel_id` |
| `hotelIds`, `ids` | `hotel_ids` |
| `detailLevel` | `detail_level` |
| `imagesLimit` | `images_limit` |

## Request examples


**One hotel**

**Request**

```bash
curl https://api.fx-port.com/api/v1/hotels/get_hotel_info \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "hotel_id": "test_hotel_do_not_book",
  "language": "en"
}'
```

**Compact card**

**Request**

```bash
curl https://api.fx-port.com/api/v1/hotels/get_hotel_info \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "hotel_id": "test_hotel_do_not_book",
  "detail_level": "minimal",
  "images_limit": 1
}'
```

**Several hotels**

**Request**

```bash
curl https://api.fx-port.com/api/v1/hotels/get_hotel_info \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "hotel_ids": [
    "test_hotel_do_not_book",
    "hotel_honduras_maya_6"
  ],
  "detail_level": "minimal"
}'
```


## How the hotel is found

1. With `hotel_id`, FX-Port looks for the id in its cache of hotel content. A **number** (or a numeric string) is taken as the supplier's `hid` and looked up the same way.
2. A single `hotel_id` that is not cached is **fetched from the supplier** (about 0.3 s), stored for every customer, and answered. This is how you get the card of a hotel listed in `missing_hotel_ids` of a search: see [Hotels without content](https://fx-port.com/docs/hotels/hotel-data.md#hotels-without-content).
3. With `hotel_ids` nothing is fetched: the hotels that are cached come back, the others are **left out without an error**, and the answer is `{"data": []}` when none is cached.

A single `hotel_id` that no source knows is `404 hotel_not_found`.

## Response

`data` is always a list, with one entry for a single `hotel_id`. The entry is the full document (camelCase), here shortened to one element per list:

~~~json
{
  "environment": "sandbox",
  "data": [
    {
      "id": "test_hotel_do_not_book",
      "hid": 8473727,
      "name": "Test Hotel (Do Not Book) test",
      "kind": "Apartment",
      "hotelChain": "No chain",
      "isClosed": false,
      "isGenderSpecificationRequired": false,
      "address": "Avenida Roble, Frente al Mall Multiplaza, Tegucigalpa",
      "postalCode": null,
      "latitude": 14.079872,
      "longitude": -87.21677,
      "region": {
        "id": 3421,
        "name": "Tegucigalpa",
        "countryCode": "HN",
        "iata": "TGU",
        "type": "City"
      },
      "starRating": 0,
      "starCertificate": null,
      "contact": {
        "email": "test@example.com",
        "phone": "+15555550123"
      },
      "checkIn": "14:00:00",
      "checkOut": "12:00:00",
      "frontDeskHours": {
        "start": "00:00:00",
        "end": "00:00:00"
      },
      "images": [
        "https://cdn.worldota.net/t/{size}/extranet/b5/d3/b5d3d33394494c68321246882c1bd93a6832dcd5.jpeg"
      ],
      "amenityGroups": [
        {
          "groupName": "General",
          "amenities": [
            "24-hour reception",
            "Early check-in",
            "Late check-out"
          ],
          "paidAmenities": []
        }
      ],
      "serpFilters": [
        "has_internet",
        "has_parking",
        "kitchen"
      ],
      "descriptionStruct": [
        {
          "title": "Location",
          "paragraphs": [
            "A perfect fit for a big group of travellers: apartment «Test Hotel (Do Not Book)» ..."
          ],
          "html": "<h3>Location</h3><p>A perfect fit for a big group of travellers ...</p>"
        }
      ],
      "metapolicyExtraInfo": "Front desk is open 24/7.",
      "metapolicyStruct": {
        "internet": [
          {
            "inclusion": "included",
            "price": "0.00",
            "currency": null,
            "price_unit": "unspecified",
            "internet_type": "unspecified",
            "work_area": "hotel"
          }
        ],
        "pets": [
          {
            "inclusion": "not_included",
            "price": "560.00",
            "currency": "USD",
            "price_unit": "per_room_per_stay",
            "pets_type": "unspecified"
          }
        ],
        "children": [
          {
            "age_start": 0,
            "age_end": 3,
            "price": "500.00",
            "currency": "CHF",
            "extra_bed": "available"
          }
        ]
      },
      "roomGroups": [
        {
          "roomGroupId": 95,
          "name": "Standard Double room full double bed",
          "nameStruct": {
            "mainName": "Standard Double room",
            "beddingType": "full double bed",
            "bathroom": "shared bathroom"
          },
          "images": [
            "https://cdn.worldota.net/t/{size}/extranet/b5/d3/b5d3d33394494c68321246882c1bd93a6832dcd5.jpeg"
          ],
          "roomAmenities": [
            "bath",
            "heating"
          ],
          "roomClass": "room",
          "quality": "standard",
          "bathroom": "private-bathroom",
          "hasPrivateBathroom": true,
          "capacity": 2,
          "balcony": false,
          "view": null,
          "floor": null,
          "bedrooms": null,
          "beddingType": "double-bed",
          "isFamily": false,
          "isClub": false,
          "genderRestriction": null
        }
      ],
      "facts": {
        "floorsNumber": null,
        "roomsNumber": null,
        "yearBuilt": null,
        "yearRenovated": null,
        "electricity": {
          "voltage": [],
          "frequency": [
            60
          ],
          "sockets": [
            "a",
            "b"
          ]
        }
      },
      "paymentMethods": [
        "visa",
        "maestro",
        "cash"
      ],
      "keysPickup": {
        "type": "lockbox",
        "isContactless": true,
        "email": "test@example.com",
        "phone": "+15555550123",
        "officeAddress": null,
        "extraInfo": "The keys are in the box by the front door."
      }
    }
  ]
}
~~~

The reference describes every field: [`get_hotel_info`](https://fx-port.com/docs/api/get-hotel-info.md) for this camelCase form and [The Hotel full object](https://fx-port.com/docs/api/objects/hotel-full.md) for the snake_case form of the same data. Things to know when you read it:

- **`checkIn` and `checkOut` are times of the day** (`14:00:00`, local time), not dates.
- **`images` are URL templates** with `{size}`: build the picture URL as described in [Images](https://fx-port.com/docs/hotels/hotel-data.md#images). `images` can be `null`.
- **`roomGroups` is about 80% of the document.** Each room type carries its own photos and amenities, with its properties decoded into words (`roomClass`, `quality`, `beddingType`, `hasPrivateBathroom`, `balcony`, `view`).
- **`metapolicyStruct` tells what is paid at the property** (parking, extra beds, pets, deposits). Read `children` before you say a child stays free. Its entries keep snake_case keys (`price_unit`, `age_start`).
- **`isClosed: true`** means the property is closed at the supplier: [`get_rates`](https://fx-port.com/docs/hotels/rates.md) will find no rate.
- **The key style follows `detail_level`.** `full` answers camelCase; `minimal` answers the snake_case card of [The Hotel mini object](https://fx-port.com/docs/api/objects/hotel-mini.md). Do not mix them in one parser.

Eight keys of the full document come **twice**, as camelCase and as a snake_case twin, so that you can move to snake_case at your pace: `hotelChain`/`hotel_chain`, `isClosed`/`is_closed`, `postalCode`/`postal_code`, `starRating`/`star_rating`, `starCertificate`/`star_certificate`, `checkIn`/`check_in_time`, `checkOut`/`check_out_time` and `region.countryCode`/`region.country_code`. Add `?naming=snake` to get **every** key in snake_case. See [Conventions and naming](https://fx-port.com/docs/conventions.md#naming).

## Good practice

- **Cache the document by hotel id** (not by language: the content is English). A day is a safe duration.
- **Call it once, when the user opens the hotel**, not for every hotel of a list.
- **Fetch a missing hotel with one id per call**, five calls at a time at most, and remember the failures for a few minutes. The supplier answers about 30 hotel content calls a minute for all of FX-Port. On a `503 supplier_busy` wait the seconds of `Retry-After`: the call was refused before anything was sent.
- **Send `"language": "en"`** when the hotel may not be cached: the fetched content is stored for every customer in the language of that call.

## Errors

| HTTP | Code | Cause |
|---|---|---|
| `400` | `missing_field` | Neither `hotel_id` nor `hotel_ids` is sent (`Hotel IDs are required`; `errors[0].field` is `hotel_ids`). |
| `400` | `invalid_hotel_id`, `invalid_hotel_ids` | An id is not 1 to 128 characters (letters, digits and `_ . : -`; or a positive integer for a `hid`), or `hotel_ids` is not a list. |
| `400` | `too_many_ids` | More than 200 ids in `hotel_ids`. |
| `400` | `invalid_detail_level`, `invalid_images_limit`, `invalid_language` | The value is not one of the documented ones. |
| `404` | `hotel_not_found` | A single `hotel_id` that neither FX-Port nor the supplier knows. |
| `429` | `supplier_rate_limited` | The supplier is receiving too many content calls (only when FX-Port asks it for a single hotel). Retry after a minute. |
| `502` | `supplier_error`, `supplier_invalid_response` | The supplier could not process the request, or answered in a form that could not be used. Retry with backoff. |
| `503` | `supplier_unavailable`, `supplier_timeout`, `supplier_busy` | The supplier could not be reached, did not answer in time, or is at capacity. With `supplier_busy` the call was **refused before anything was sent**: retry after the seconds in `Retry-After`. Otherwise retry with backoff. |
| `401`, `403` | | The key is missing, wrong or not allowed: [Authentication](https://fx-port.com/docs/authentication.md). |

## Related


- [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md): When to use the compact card and when this document.
- [Geo search](https://fx-port.com/docs/hotels/search.md): The cards of the hotels around a point, with their prices.
- [Get hotel rates](https://fx-port.com/docs/hotels/rates.md): The rooms, meals and cancellation terms of the hotel the user picked.
- [API reference: Read one hotel for the hotel page](https://fx-port.com/docs/api/get-hotel-info.md): Every field and error.
