Package search prices one package for the guest's whole selection: dates, party, picks, extras and service times. It answers everything the booking form shows, so a storefront sends each change the guest makes and renders the answer. When the guest chooses the room, it returns one quote per room.
All endpoints are scoped to the organization that owns the API key:
/store/v0
Search a package
GET /store/v0/packages/{package_public_id}/searchQuery parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
check_in_date | date (YYYY-MM-DD) | stays | First night. For a bundle with asks_for_day, the day of the visit. |
check_out_date | date (YYYY-MM-DD) | stays | Departure date; must be after check_in_date |
guests | integer | no | Party size. Defaults to 2, kept between the package's smallest_party and largest_party. |
choices[{component_public_id}] | string | no | The option the guest picked for a component with guest_picks: true, the room included. Components without a choice use their cheapest option. |
extra_component_ids[] | string | no | Public IDs of the extras the guest ticked |
slots[{component_public_id}] | string | no | The starts_at of the time the guest picked for a service component |
A stay without both dates returns no quotes. A bundle is always quoted.
Response
{
"search": {
"check_in_date": "2026-10-14",
"check_out_date": "2026-10-17",
"guests": 2,
"nights": 3,
"dates_label": "Wed 14 Oct – Sat 17 Oct · 3 nights",
"choices": {
"f7fc65b6-f266-408f-b5c5-5bbf8f344022": "d031cf87-a78d-4b6e-997b-bc8a6ff37f41"
},
"picks": {
"1054ad56-72da-4149-bc41-4216887d98be": "5a74bfac-5a61-46bf-b830-fd2c71087ce7",
"99f1cb76-4180-456b-b880-b8e09c600d84": "96fa5303-146e-4a06-9ce6-8cb4467c7373"
},
"pick_component_ids": [],
"extra_component_ids": ["99f1cb76-4180-456b-b880-b8e09c600d84"],
"extras": [
{
"component_id": "99f1cb76-4180-456b-b880-b8e09c600d84",
"booked": true,
"price": {
"cents": 150000,
"currency": "NOK",
"amount": 1500.0,
"formatted": "1,500 NOK",
"abbreviated": "1.5k NOK"
},
"reason": null
}
],
"slots": {},
"seats": { "5a74bfac-5a61-46bf-b830-fd2c71087ce7": 1 },
"service_starts_on": "2026-10-14",
"service_ends_on": "2026-10-17",
"next_step": "choose-time",
"untimed_component_id": "1054ad56-72da-4149-bc41-4216887d98be",
"total": {
"cents": 930500,
"currency": "NOK",
"amount": 9305.0,
"formatted": "9,305 NOK",
"abbreviated": "9.3k NOK"
},
"chosen_quote": {
"option_id": "d031cf87-a78d-4b6e-997b-bc8a6ff37f41",
"available": true,
"from": false,
"reason": null,
"total": {
"cents": 780500,
"currency": "NOK",
"amount": 7805.0,
"formatted": "7,805 NOK",
"abbreviated": "7.8k NOK"
},
"option": {
"public_id": "d031cf87-a78d-4b6e-997b-bc8a6ff37f41",
"name": "Lodgen",
"capacity": 6,
"capacity_label": "Up to 6 guests",
"…": "…"
}
},
"quotes": [
{
"option_id": "d031cf87-a78d-4b6e-997b-bc8a6ff37f41",
"available": true,
"from": false,
"reason": null,
"total": {
"cents": 780500,
"currency": "NOK",
"amount": 7805.0,
"formatted": "7,805 NOK",
"abbreviated": "7.8k NOK"
},
"option": {
"public_id": "d031cf87-a78d-4b6e-997b-bc8a6ff37f41",
"…": "…"
}
},
{
"option_id": "31db3e8f-f64d-4aef-9e3f-7005f974b2b4",
"available": false,
"from": false,
"reason": "No Deluxe Elvasus 2.0 left on these dates",
"total": null,
"option": {
"public_id": "31db3e8f-f64d-4aef-9e3f-7005f974b2b4",
"…": "…"
}
}
]
}
}Quotes
quoteshas one quote per room option when the guest chooses the room, otherwise one quote.option_idis the room option's public ID, ornullwhen the package has no room.optionis that room option, with the fields of a package's option.- A quote's
totalis the package price for the whole party, without extras. It isnullwhen the quote is not available. reasonsays why a quote is not available, for example that the dates are outside the season, the party is too large, or the room is full. Show it to the guest.fromistruewhen the total can change with an option the guest has not picked yet. Show the total as "From …".chosen_quoteis the quote the selection books: the guest's room, else the first room that can be booked. It isnullwhen nothing can be booked.
The guest's selection
| Field | Type | Description |
|---|---|---|
total | money | null | What the selection costs: the chosen quote's total plus the booked extras. null when nothing can be booked. |
choices | object | The guest's picks the search used, component public ID → option public ID. Send it back as choices. |
picks | object | Every component except the room → the option the total uses: the guest's choice, else the cheapest. |
pick_component_ids | string[] | Components the guest chooses an option for, other than the room: included ones and booked extras. Offer their options. |
extras | extra[] | Every extra, in display order, with what it adds for these dates and guests and whether it is booked |
extra_component_ids | string[] | The extras the selection books. A ticked extra that can't be booked, like an add-on whose service isn't ticked, is left out. Send it back as extra_component_ids. |
slots | object | The service times the selection books, component public ID → starts_at. Times of components that aren't booked are left out. Send it back as slots. |
seats | object | Service option public ID → the seats a time needs for this party. Send it as seats to Service availability. |
service_starts_on / service_ends_on | date | null | The days to list service times on: the stay, the chosen day, or the event's days |
next_step | string | What the guest does next: add (add to the cart), choose-date, choose-time or unavailable |
untimed_component_id | string | null | The first booked service component without a time, while next_step is choose-time |
nights | integer | null | Stays with both dates only. Nights between them. |
dates_label | string | null | Stays with both dates only. The dates and nights in English, like "Wed 14 Oct – Sat 17 Oct · 3 nights". |
An extra is { "component_id": "…", "booked": true, "price": money, "reason": null }:
priceis what the extra adds to the total for these dates and guests, with the guest's pick. Show it as "+ …".reasonsays why it can't be added now, like"Add Private sauna first"or"No times left for Private sauna on this day". Disable the extra while it has one.bookedistruewhen the selection books it.
Reasons and labels are in English.
Errors
A choices key or value, an extra_component_ids entry or a slots key that is not a component or option of the package returns 404.
Closed days
GET /store/v0/packages/{package_public_id}/closed_daysFor a bundle with asks_for_day: the days in the next 90 on which a service the bundle books has no time for the party. Disable them in the day picker; later days stay open. Takes guests and choices like the search, and returns null for any other package.
{ "closed_days": ["2026-10-19", "2026-10-26"] }It checks every service's times for 90 days, so ask for it again only when guests or choices change, not on every search.
Building the booking form
- Search with the dates and party. Show
quotes, or the package'sprice_rangeuntil the stay has dates. For a bundle withasks_for_day, offer the days that are not in its closed days. - Each time the guest picks a room or an option, ticks an extra or picks a time, search again with the answer's
choices,extra_component_idsandslotsplus the change. When the dates or the party change, send noslots. - Show
totaland label the button bynext_step. Forchoose-time, point the guest atuntimed_component_id. - When
next_stepisadd, add it with Cart packages, sending the answer's dates,guests,choices,extra_component_idsandslots. The cart books the extras as sent, so one that sold out since the search returns422with the reason.
The cart returns the exact total after the package is added.