Docs

Package search

Last updated: 26 Oct 2026

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}/search

Query parameters

ParameterTypeRequiredDescription
check_in_datedate (YYYY-MM-DD)staysFirst night. For a bundle with asks_for_day, the day of the visit.
check_out_datedate (YYYY-MM-DD)staysDeparture date; must be after check_in_date
guestsintegernoParty size. Defaults to 2, kept between the package's smallest_party and largest_party.
choices[{component_public_id}]stringnoThe 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[]stringnoPublic IDs of the extras the guest ticked
slots[{component_public_id}]stringnoThe 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

  • quotes has one quote per room option when the guest chooses the room, otherwise one quote. option_id is the room option's public ID, or null when the package has no room. option is that room option, with the fields of a package's option.
  • A quote's total is the package price for the whole party, without extras. It is null when the quote is not available.
  • reason says 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.
  • from is true when the total can change with an option the guest has not picked yet. Show the total as "From …".
  • chosen_quote is the quote the selection books: the guest's room, else the first room that can be booked. It is null when nothing can be booked.

The guest's selection

FieldTypeDescription
totalmoney | nullWhat the selection costs: the chosen quote's total plus the booked extras. null when nothing can be booked.
choicesobjectThe guest's picks the search used, component public ID → option public ID. Send it back as choices.
picksobjectEvery component except the room → the option the total uses: the guest's choice, else the cheapest.
pick_component_idsstring[]Components the guest chooses an option for, other than the room: included ones and booked extras. Offer their options.
extrasextra[]Every extra, in display order, with what it adds for these dates and guests and whether it is booked
extra_component_idsstring[]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.
slotsobjectThe 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.
seatsobjectService option public ID → the seats a time needs for this party. Send it as seats to Service availability.
service_starts_on / service_ends_ondate | nullThe days to list service times on: the stay, the chosen day, or the event's days
next_stepstringWhat the guest does next: add (add to the cart), choose-date, choose-time or unavailable
untimed_component_idstring | nullThe first booked service component without a time, while next_step is choose-time
nightsinteger | nullStays with both dates only. Nights between them.
dates_labelstring | nullStays 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 }:

  • price is what the extra adds to the total for these dates and guests, with the guest's pick. Show it as "+ …".
  • reason says 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.
  • booked is true when 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_days

For 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

  1. Search with the dates and party. Show quotes, or the package's price_range until the stay has dates. For a bundle with asks_for_day, offer the days that are not in its closed days.
  2. 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_ids and slots plus the change. When the dates or the party change, send no slots.
  3. Show total and label the button by next_step. For choose-time, point the guest at untimed_component_id.
  4. When next_step is add, add it with Cart packages, sending the answer's dates, guests, choices, extra_component_ids and slots. The cart books the extras as sent, so one that sold out since the search returns 422 with the reason.

The cart returns the exact total after the package is added.