> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spaportal.cz/llms.txt
> Use this file to discover all available pages before exploring further.

# Amenities

> The hotel amenity dictionary, selected values, detail fields, and filters.

Hotel services and amenities are exposed as structured, filterable data in API v2.

There are two related resources:

1. **The amenity dictionary** lists every amenity SpaPortal can assign to a hotel.
   It is grouped by category. Each category has a stable `code`, a localized
   label, and an `amenities` array whose entries contain a stable `code`,
   localized `label`, and supported detail fields.
2. **Hotel amenity values** are grouped by category in
   `hotel.amenityCategories`.
   Categories without assigned amenities are omitted. Values inside each
   group's `amenities` array store the amenity's structured details and a
   localized guest note when the dictionary includes the `note` detail kind.

<Note>
  These responses serve different purposes. `GET /api/v2/amenity-categories`
  always returns every system category and amenity. `GET /api/v2/hotels` and
  `GET /api/v2/hotels/{hotelId}` return only the amenity values assigned to each
  hotel.
</Note>

## Amenity dictionary

<Card title="GET /api/v2/amenity-categories" icon="square-terminal" href="/api/v2/reference/amenity-categories/list-the-amenity-dictionary" horizontal>
  Fetch the full amenity dictionary.
</Card>

`GET /api/v2/amenity-categories` returns every category and amenity available
in SpaPortal, whether or not a particular hotel uses it. The complete list is
returned in one response and is not paginated. Use
[`Accept-Language`](/api/localization) to select the locale keys returned in
localized fields.

Amenities whose `detailKinds` include `options` also carry an `options` array
with every supported choice and its localized label.

```json Response theme={null}
{
  "data": [
    {
      "category": {
        "code": "wellness",
        "label": {
          "cs-CZ": "Bazény a wellness",
          "en": "Pools & wellness"
        }
      },
      "amenities": [
        {
          "code": "sauna",
          "label": {
            "cs-CZ": "Sauna",
            "en": "Sauna"
          },
          "detailKinds": ["proximity", "access", "options", "opening_hours"],
          "options": [
            {
              "code": "finnish",
              "label": {
                "cs-CZ": "Finská sauna",
                "en": "Finnish sauna"
              }
            },
            {
              "code": "infrared",
              "label": {
                "cs-CZ": "Infra sauna",
                "en": "Infrared sauna"
              }
            },
            {
              "code": "steam",
              "label": {
                "cs-CZ": "Parní lázeň",
                "en": "Steam bath"
              }
            }
          ]
        }
      ]
    }
  ]
}
```

Resolve hotel `amenityCategories[].amenities[].code` values against the entries
nested in each dictionary category instead of hard-coding display labels.
Category and amenity codes are stable.

### Categories

| Code            | English label      |
| --------------- | ------------------ |
| `health`        | Health & treatment |
| `wellness`      | Pools & wellness   |
| `dining`        | Dining             |
| `services`      | Services           |
| `accessibility` | Accessibility      |
| `parking`       | Parking            |
| `sport`         | Sport & activities |
| `children`      | For children       |

### Detail kinds

`detailKinds` tells clients which parts of an [Amenity value](/api/objects/amenity-value)
are included:

| Kind            | Value field                                                                                                   |
| --------------- | ------------------------------------------------------------------------------------------------------------- |
| `proximity`     | `proximity` and `proximityMeters`; the distance is non-null only when `proximity` is `nearby`                 |
| `access`        | `access`                                                                                                      |
| `options`       | amenity-specific choices; the entry carries an `options` array of every valid choice with its localized label |
| `opening_hours` | `openingHours`                                                                                                |
| `note`          | localized `note`                                                                                              |

An empty `detailKinds` array identifies a presence-only amenity. Its hotel value
contains only `code`; for example, `{ "code": "wheelchair_access" }`.

## Caching

The dictionary changes only when SpaPortal releases new amenities, categories,
options, or labels. Codes are only ever added, never renamed or removed, so a
cached dictionary stays safe to use.

Recommended flow:

1. Fetch `GET /api/v2/amenity-categories` once without `Accept-Language`, so
   your cache holds every locale key. Store the response together with its
   `ETag` and `X-SpaPortal-Amenities-Version` headers.
2. Hotel responses return the current dictionary version in the
   `X-SpaPortal-Amenities-Version` header. When the value differs from the one
   you cached, refetch the dictionary.
3. To revalidate directly, repeat the same request — with the same
   `Accept-Language` — and add `If-None-Match: <cached ETag>`. The API returns
   `304` with no body while your copy is current.
4. When a hotel response contains an amenity code that is missing from your
   cached dictionary, skip it in display and refresh the dictionary.

Responses also carry `Cache-Control: private, max-age=3600`, so a plain HTTP
cache can reuse the dictionary for an hour before revalidating. The `ETag`
differs per requested locale set; the version header does not.

## Selected amenities on a hotel

Every v2 [Hotel](/api/objects/hotel) response groups assigned amenities by
category. Only categories containing at least one assigned amenity are returned.
An absent code means the amenity is not offered or has not been advertised.

By default hotel responses carry **codes only** — the category, amenity, and
each selected option are identified by `code`. Fetch the dictionary once (it is
small and cacheable) and look up every localized label there. This keeps hotel
responses small and the labels in one place.

If you would rather have a self-contained response, add
[`expand=amenityLabels`](/api/expand) to the hotel request. It inlines the
localized `label` on each category, amenity, and option, so no dictionary lookup
is needed.

```json Hotel amenity values theme={null}
{
  "amenityCategories": [
    {
      "category": { "code": "wellness" },
      "amenities": [
        {
          "code": "aquapark",
          "proximity": "nearby",
          "proximityMeters": 250,
          "access": "included",
          "openingHours": {
            "continuous": false,
            "blocks": [
              {
                "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
                "from": "09:00",
                "to": "21:00"
              }
            ]
          }
        },
        {
          "code": "sauna",
          "proximity": "in-hotel",
          "proximityMeters": null,
          "access": "included",
          "options": [{ "code": "finnish" }, { "code": "steam" }],
          "openingHours": {
            "continuous": false,
            "blocks": []
          }
        }
      ]
    }
  ]
}
```

To display this hotel's amenities, join each code to the dictionary:
`category.code` → `data[].category`, `amenities[].code` →
`data[].amenities[]`, and each `options[].code` → that amenity's `options` in
the dictionary. All three carry the localized labels. Or request
`expand=amenityLabels` to receive those labels inline on the hotel and skip the
join.

Structured details use the following enums:

* `proximity` — where the amenity is located relative to the hotel:
  * `in-hotel` — inside the hotel building.
  * `connecting` — in an adjacent building linked by an internal passage.
  * `nearby` — a short distance away; `proximityMeters` gives the approximate
    distance.
* `access` — whether using the amenity costs extra:
  * `included` — covered by the stay price.
  * `conditional` — included subject to stated conditions.
  * `paid` — available for an additional charge.
* `options`: amenity-specific choices selected for the hotel, by `code` only.
  The dictionary lists every available choice with its label; hotel responses
  return only the selected codes.
* opening-hour days: `monday`, `tuesday`, `wednesday`, `thursday`, `friday`,
  `saturday`, `sunday`

Apart from `code`, an amenity value includes only the fields mapped from its
entry's `detailKinds` under `data[].amenities[]`. When a declared field is not
filled, scalar fields are `null`, arrays are empty, opening hours are
`{ "continuous": false, "blocks": [] }`, and requested localized note values
are `null`.

`access` also represents whether an amenity carries an additional charge:
`included` means no additional charge, `conditional` means inclusion depends on
the stated conditions, and `paid` means an additional charge applies.

## Filtering hotels

[`GET /api/v2/hotels`](/api/v2/reference/hotels/list-hotels) accepts:

* `amenities`: comma-separated amenity codes. A hotel must have **all** listed
  codes (AND semantics).
* `amenityCategory`: a category code. A hotel must have at least one amenity in
  that category.

```bash theme={null}
# Hotels with both a sauna and parking
GET /api/v2/hotels?amenities=sauna,parking

# Hotels with any pool or wellness amenity
GET /api/v2/hotels?amenityCategory=wellness
```

<Note>
  Amenity filters apply to hotels. Room types do not carry structured amenities
  — a room type lists its equipment in the free-text `facilities` field.
</Note>
