Skip to main content
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.
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.

Amenity dictionary

GET /api/v2/amenity-categories

Fetch the full amenity dictionary.
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 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.
Response
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

Detail kinds

detailKinds tells clients which parts of an Amenity value are included: 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 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 to the hotel request. It inlines the localized label on each category, amenity, and option, so no dictionary lookup is needed.
Hotel amenity values
To display this hotel’s amenities, join each code to the dictionary: category.codedata[].category, amenities[].codedata[].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 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.
Amenity filters apply to hotels. Room types do not carry structured amenities — a room type lists its equipment in the free-text facilities field.