> ## 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.

# Building package pages

> Use the visit type catalog for package cards, the hotel choice, and price periods.

This guide walks through a package listing and a package detail with a hotel
choice and price periods. Use the same [public key](https://docs.spaportal.cz/api/authentication.md)
for all requests of a page and request the [languages](https://docs.spaportal.cz/api/localization.md)
the page displays.

## Package cards

Load the [package list](https://docs.spaportal.cz/api/v2/reference/visit-types/list-visit-types.md) and
follow its [pagination](https://docs.spaportal.cz/api/pagination.md). A card typically shows the name,
`shortDescription`, the first image, the category, the board, and the stay
length from the [Visit type](https://docs.spaportal.cz/api/objects/visit-type.md) object. Use
`channelContent.collections` to group the cards of your own website.

For a "from" price, show `priceFrom.{currency}.perStay.amount` with the
`nights` and `occupancy` of that minimum, for example "from CZK 12,600 for
7 nights, 1 guest". Keep every displayed value from the same minimum; the
`perPersonPerNight` minimum can be a different offer. When the minimum has a
non-null `original`, show it struck through.

When `priceFrom` is `null`, show the card without a price. Label a `request`
package as available on request, even when it has a price. The list returns
only available packages unless you set `available`; a package detail can be
unavailable, so check `available` separately from the price.

## Hotel choice

Load the [package detail](https://docs.spaportal.cz/api/v2/reference/visit-types/get-a-visit-type.md)
with `expand=hotel`. Each `hotelListings[]` entry carries the hotel summary
and the price of this package at that hotel. A listing without a complete
stay has `priceFrom: null` but remains a hotel that sells the package. Fetch
the [Hotel](https://docs.spaportal.cz/api/objects/hotel.md) detail for the full profile and room
catalog.

When the website visitor chooses a hotel, show that listing's price. The
package's top-level `priceFrom` compares all hotels and can belong to a
different hotel than the selected one.

## Stay rules and board

Present `stayLength`, `arrival`, and `validity` as the booking constraints,
and `board.meals` as what is included and what the guest can change. The
[stay-rule algorithm](https://docs.spaportal.cz/api/visit-types.md#testing-a-stay-against-its-rules)
tells you whether a chosen arrival and length is permitted. Check whether a
room is free when the guest chooses dates.

## Price periods

Load the price table of the selected hotel with:

```http theme={null}
GET /api/v2/visit-types/{visitTypeId}?hotelId={hotelId}&expand=hotel,priceFromPeriods
```

Build the table from `hotelListings[0].priceFromPeriods`. The package's own
`priceFromPeriods` compares all hotels, even when you send `hotelId`.

Show one row per [price period](https://docs.spaportal.cz/api/objects/price-from-period.md), labeled with
its `from` and `to` night dates. Keep periods whose `priceFrom` is `null` as
rows without a price. When `priceFromPeriods` is `null` or an empty array,
hide the table.

Periods include past nights. For a page with current prices, hide periods
whose `to` is before today in Europe/Prague.

Use the same price in every row:

| Price shown | Use |
| - | - |
| Per guest for the whole stay, such as "per person, 7 nights" | `perPersonPerStay.amount` with its `nights` |
| Per guest per night | `perPersonPerNight.amount` |
| Whole room for the whole stay | `perStay.amount` with its `nights` and `occupancy` |

Take each amount from the API. Multiplying a rounded nightly price by the
number of nights can give a different result.

A row is the price of the package's shortest stay at that period's prices. It
can have a price even when its cheapest room is sold out or the stay is longer
than the period. Show the package's stay rules next to the table and check
free rooms when the guest selects dates.

## Regular and discounted price

Take `amount` and `original` from the same metric of the same period.
`original` is the price of the same room before promotions. When it is
`null`, show the amount without a struck-through price.

For example, the `expand-hotel-price-periods` example of the
[detail reference](https://docs.spaportal.cz/api/v2/reference/visit-types/get-a-visit-type.md) has these
package prices for the period from 5 to 9 January 2027:

| Metric | `amount` | `original` | Room |
| - | - | - | - |
| `perPersonPerStay` | EUR 523.60 | EUR 770.00 | Hotel Bellevue, 2 guests, 7 nights |
| `perPersonPerNight` | EUR 74.80 | EUR 110.00 | Hotel Bellevue, 2 guests |
| `perStay` | EUR 714.00 | EUR 840.00 | Hotel Pramen, 1 guest, 7 nights |

Show "from EUR 523.60 per person for 7 nights" with EUR 770.00 struck
through, or "from EUR 714.00 for 7 nights, 1 guest" with EUR 840.00 struck
through. The lowest price per guest and the lowest room price come from
different hotels here, so show the hotel and occupancy of the metric you
display.

## Displaying discounts

Look up the ids in `activePromotionIds` and `appliedPromotionIds` in the
package's `promotions`.
[Promotions in a period](https://docs.spaportal.cz/api/objects/price-from-period.md#promotions-in-a-period)
explains the difference between the two lists.

Describe each promotion with the
[Promotion fields](https://docs.spaportal.cz/api/objects/promotion.md#fields-and-night-bounds):

* Show the localized name and `percentage` with the discounted nights `from`
  and `to`. A `null` `to` means no end date.
* For the chosen hotel, show promotions with `appliesTo: null` or with a
  matching `appliesTo[].hotelId`. The detail's `hotelId` parameter does not
  filter `promotions`.
* Look up `roomTypeIds` in the [hotel's room catalog](https://docs.spaportal.cz/api/objects/hotel.md) and
  skip ids that are not there, such as disabled room types. `null` means all
  room types of that hotel, and `appliesTo: null` means all rooms at all
  hotels that sell the package.

With `expand=priceFromPeriods`, `promotions` also contains ended promotions.
For a list of current offers, show only promotions whose `to` is `null` or
not before today in Europe/Prague.

For a savings badge, calculate `100 × (1 − amount.amountMinor / original.amountMinor)` from
the metric you display, before rounding for display, and only when
`original` is not `null`. Label it as the saving on the displayed price. Do
not show a promotion's `percentage` as the discount of every price in a
period, and do not apply it to `amount` again.

## Opening a package from a hotel price

A hotel's `priceFrom` minima name their `visitType` and `hotel`. Take
`visitType.id` and `hotel.id` from the exact minimum you displayed (currency,
group, and metric); the two minima of one group can name different packages.
Then load:

```http theme={null}
GET /api/v2/visit-types/{visitTypeId}?hotelId={hotelId}&expand=hotel,priceFromPeriods
```

Use the listing's `priceFrom` for the "from" price, its `priceFromPeriods`
for the price table, and the package fields for board, treatment, and stay
rules. Period prices can differ from the "from" price.

Refresh the hotel price instead of showing an outdated amount when:

* the detail returns `404` or has no listing for the hotel,
* `available` is `false` or `bookingMode` is not `instant`, or
* the listing's `priceFrom` is `null`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.