- 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 anamenitiesarray whose entries contain a stablecode, localizedlabel, and supported detail fields. - Hotel amenity values are grouped by category in
hotel.amenityCategories. Categories without assigned amenities are omitted. Values inside each group’samenitiesarray store the amenity’s structured details and a localized guest note when the dictionary includes thenotedetail 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
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:- Fetch
GET /api/v2/amenity-categoriesonce withoutAccept-Language, so your cache holds every locale key. Store the response together with itsETagandX-SpaPortal-Amenities-Versionheaders. - Hotel responses return the current dictionary version in the
X-SpaPortal-Amenities-Versionheader. When the value differs from the one you cached, refetch the dictionary. - To revalidate directly, repeat the same request — with the same
Accept-Language— and addIf-None-Match: <cached ETag>. The API returns304with no body while your copy is current. - When a hotel response contains an amenity code that is missing from your cached dictionary, skip it in display and refresh the dictionary.
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 bycode. 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
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;proximityMetersgives 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, bycodeonly. 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
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.