Skip to content
DocsDashboard

Hotels (hotel_picker)

Let the person choose one hotel for a stay. The page records the choice; it does not book or hold a room. The result is { hotelId }.

When to use it: The person chooses a hotel for a stay. Each card shows the nightly and total price, rating and cancellation policy. The page only records the choice: book it yourself afterwards, and do not tell the person it is booked until you have.

When not to use it: Do not use it to choose dates (use date_range first) or to confirm a hotel that is already chosen.

Gather first:

  • The check-in and check-out dates and the number of guests, so every price covers the same stay.
  • Real availability and prices from your search for those dates.

Writing good content:

  • nightlyPrice is per night with taxes when you know them; add totalPrice for the whole stay so options compare fairly.
  • Set checkIn and checkOut so the page shows the dates and number of nights.
  • Include photos, stars, guestRating and neighborhood when you have them: they are what people compare.
  • State the cancellation policy only when your source says it; leave refundable out rather than guess.
  • Use your provider's hotel or offer id as id, so you can act on exactly the one picked. The page only records the choice: book it yourself afterwards.

Fields

Every page also has version (always 1), title, and optionally description, branding, cover (a header photo) and expiresAt; see Page fields. These are the fields of page (the page type and its data):

FieldDescription
checkIndate (YYYY-MM-DD). Optional check-in date, "YYYY-MM-DD". Same for every option. Include when known.
checkOutdate (YYYY-MM-DD). Optional check-out date, "YYYY-MM-DD", after checkIn. Include when known.
optionsobject[], required. Hotels in display order (1-30). Limits: 1-30 items.
options[].idstring, required. Stable identifier you choose. It is returned in the result, so keep it meaningful to you. Limits: matches ^[A-Za-z0-9_-]{1,64}$.
options[].namestring, required. Hotel name, e.g. "Harbor Light Hotel". Required. Limits: 1-80 characters.
options[].photoshttps URL[]. Optional, up to 5 https photo URLs. The person swipes through them; ones that fail to load show a blank box. Omit if you have none. Limits: at most 5 items.
options[].starsinteger. Optional official star class, 1-5. Include when known. Limits: 1-5.
options[].guestRatingnumber. Optional guest rating on a 0-10 scale, e.g. 8.7. Limits: 0-10.
options[].neighborhoodstring. Optional neighborhood or area, e.g. "Old Town". Include when known. Limits: 1-80 characters.
options[].nightlyPriceobject, required. Price per night, including taxes if you know them. Required.
options[].nightlyPrice.amountnumber, required. Amount in major units, e.g. 249.99 for $249.99. Limits: 0-10000000.
options[].nightlyPrice.currencystring, required. ISO 4217 currency code, uppercase, e.g. "USD", "EUR".
options[].totalPriceobject. Optional price for the whole stay, in the same currency as nightlyPrice. Include when known.
options[].totalPrice.amountnumber, required. Amount in major units, e.g. 249.99 for $249.99. Limits: 0-10000000.
options[].totalPrice.currencystring, required. ISO 4217 currency code, uppercase, e.g. "USD", "EUR".
options[].amenitiesstring[]. Optional, up to 8 short amenities, e.g. ["Free Wi-Fi", "Pool"]. Limits: at most 8 items.
options[].cancellationPolicystring. Optional cancellation policy in one sentence, e.g. "Free cancellation until Nov 1." Include when known. Limits: 1-200 characters.
options[].refundableboolean. Optional: true if the rate can be cancelled for a refund, false if not. Omit when you do not know.
options[].badgesstring[]. Optional short highlights such as "Best value" (up to 3). Limits: at most 3 items.
options[].recommendedboolean. Optional. Mark at most one option you recommend, when you have a reason: true shows a "Recommended" label on it. Omit on the others.
options[].recommendedReasonstring. Optional short reason shown with the label, e.g. "Nonstop, and only $30 more". Only with recommended: true. Limits: 1-80 characters.

Why these are required: A stay is a booking decision, so each hotel shows a price.

When optional fields are left out: No dates line or "for N nights"; only the nightly price; no stars, rating, area, policy, or refundable claim; no "Recommended" label (same one-per-page rule as flight_picker).

Minimal example

Only the required fields:

{
  "version": 1,
  "title": "Pick a hotel near the conference",
  "page": {
    "type": "hotel_picker",
    "options": [
      {
        "id": "alder",
        "name": "Alder Court Hotel",
        "nightlyPrice": {
          "amount": 159,
          "currency": "USD"
        }
      },
      {
        "id": "tidewater",
        "name": "Tidewater Suites",
        "nightlyPrice": {
          "amount": 212,
          "currency": "USD"
        }
      },
      {
        "id": "pinecone",
        "name": "Pinecone Lodge",
        "nightlyPrice": {
          "amount": 98,
          "currency": "USD"
        }
      }
    ]
  }
}

Full example

{
  "version": 1,
  "title": "Hotels in Portland",
  "description": "Prices are for 1 room, 2 guests, taxes included.",
  "branding": {
    "name": "Juniper Travel",
    "accentColor": "#0b7a5c"
  },
  "page": {
    "type": "hotel_picker",
    "checkIn": "2026-11-03",
    "checkOut": "2026-11-06",
    "options": [
      {
        "id": "harbor",
        "name": "Harbor Light Hotel",
        "photos": [
          "https://images.example.test/harbor-1.jpg",
          "https://images.example.test/harbor-2.jpg",
          "https://images.example.test/harbor-3.jpg"
        ],
        "stars": 4,
        "guestRating": 8.7,
        "neighborhood": "Old Town",
        "nightlyPrice": {
          "amount": 189,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 567,
          "currency": "USD"
        },
        "amenities": [
          "Free Wi-Fi",
          "Pool",
          "Breakfast"
        ],
        "cancellationPolicy": "Free cancellation until Nov 1.",
        "refundable": true,
        "badges": [
          "Best value"
        ],
        "recommended": true,
        "recommendedReason": "Refundable, breakfast included, and well rated"
      },
      {
        "id": "cedar",
        "name": "Cedar Row Inn",
        "photos": [
          "https://images.example.test/cedar-1.jpg"
        ],
        "stars": 3,
        "guestRating": 7.9,
        "neighborhood": "University District",
        "nightlyPrice": {
          "amount": 124.5,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 373.5,
          "currency": "USD"
        },
        "amenities": [
          "Free parking",
          "Free Wi-Fi"
        ],
        "cancellationPolicy": "Non-refundable rate. No changes.",
        "refundable": false
      },
      {
        "id": "skyline",
        "name": "Skyline Grand",
        "photos": [
          "https://images.example.test/skyline-1.jpg",
          "https://broken.example.test/missing.jpg"
        ],
        "stars": 5,
        "neighborhood": "Financial District",
        "nightlyPrice": {
          "amount": 342,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 1026,
          "currency": "USD"
        },
        "cancellationPolicy": "Free cancellation until 48 hours before check-in.",
        "refundable": true,
        "badges": [
          "Top rated",
          "Spa"
        ]
      },
      {
        "id": "plain",
        "name": "Meadow Motel",
        "stars": 2,
        "guestRating": 6.8,
        "neighborhood": "Airport",
        "nightlyPrice": {
          "amount": 79,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 237,
          "currency": "USD"
        },
        "cancellationPolicy": "Free cancellation until check-in day.",
        "refundable": true
      }
    ]
  }
}

Result

When the person submits, the result has these fields:

FieldDescription
hotelIdstring, required. id of the chosen hotel. Limits: matches ^[A-Za-z0-9_-]{1,64}$.

For the full example above, a result could be:

{
  "hotelId": "harbor"
}

Send it

eve, AI SDK and MCP

With @pagesystems/eve, @pagesystems/ai-sdk or the MCP server, the model calls get_page_type_schema with "hotel_picker" (the same JSON as GET /v1/page-types/hotel_picker), then create_page with this input (no version or branding; branding comes from your dashboard):

{
  "title": "Hotels in Portland",
  "description": "Prices are for 1 room, 2 guests, taxes included.",
  "page": {
    "type": "hotel_picker",
    "checkIn": "2026-11-03",
    "checkOut": "2026-11-06",
    "options": [
      {
        "id": "harbor",
        "name": "Harbor Light Hotel",
        "photos": [
          "https://images.example.test/harbor-1.jpg",
          "https://images.example.test/harbor-2.jpg",
          "https://images.example.test/harbor-3.jpg"
        ],
        "stars": 4,
        "guestRating": 8.7,
        "neighborhood": "Old Town",
        "nightlyPrice": {
          "amount": 189,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 567,
          "currency": "USD"
        },
        "amenities": [
          "Free Wi-Fi",
          "Pool",
          "Breakfast"
        ],
        "cancellationPolicy": "Free cancellation until Nov 1.",
        "refundable": true,
        "badges": [
          "Best value"
        ],
        "recommended": true,
        "recommendedReason": "Refundable, breakfast included, and well rated"
      },
      {
        "id": "cedar",
        "name": "Cedar Row Inn",
        "photos": [
          "https://images.example.test/cedar-1.jpg"
        ],
        "stars": 3,
        "guestRating": 7.9,
        "neighborhood": "University District",
        "nightlyPrice": {
          "amount": 124.5,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 373.5,
          "currency": "USD"
        },
        "amenities": [
          "Free parking",
          "Free Wi-Fi"
        ],
        "cancellationPolicy": "Non-refundable rate. No changes.",
        "refundable": false
      },
      {
        "id": "skyline",
        "name": "Skyline Grand",
        "photos": [
          "https://images.example.test/skyline-1.jpg",
          "https://broken.example.test/missing.jpg"
        ],
        "stars": 5,
        "neighborhood": "Financial District",
        "nightlyPrice": {
          "amount": 342,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 1026,
          "currency": "USD"
        },
        "cancellationPolicy": "Free cancellation until 48 hours before check-in.",
        "refundable": true,
        "badges": [
          "Top rated",
          "Spa"
        ]
      },
      {
        "id": "plain",
        "name": "Meadow Motel",
        "stars": 2,
        "guestRating": 6.8,
        "neighborhood": "Airport",
        "nightlyPrice": {
          "amount": 79,
          "currency": "USD"
        },
        "totalPrice": {
          "amount": 237,
          "currency": "USD"
        },
        "cancellationPolicy": "Free cancellation until check-in day.",
        "refundable": true
      }
    ]
  }
}

REST

curl https://pagesystems.ai/v1/pages \
  -H "Authorization: Bearer $PAGE_SYSTEMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"version":1,"title":"Hotels in Portland","description":"Prices are for 1 room, 2 guests, taxes included.","branding":{"name":"Juniper Travel","accentColor":"#0b7a5c"},"page":{"type":"hotel_picker","checkIn":"2026-11-03","checkOut":"2026-11-06","options":[{"id":"harbor","name":"Harbor Light Hotel","photos":["https://images.example.test/harbor-1.jpg","https://images.example.test/harbor-2.jpg","https://images.example.test/harbor-3.jpg"],"stars":4,"guestRating":8.7,"neighborhood":"Old Town","nightlyPrice":{"amount":189,"currency":"USD"},"totalPrice":{"amount":567,"currency":"USD"},"amenities":["Free Wi-Fi","Pool","Breakfast"],"cancellationPolicy":"Free cancellation until Nov 1.","refundable":true,"badges":["Best value"],"recommended":true,"recommendedReason":"Refundable, breakfast included, and well rated"},{"id":"cedar","name":"Cedar Row Inn","photos":["https://images.example.test/cedar-1.jpg"],"stars":3,"guestRating":7.9,"neighborhood":"University District","nightlyPrice":{"amount":124.5,"currency":"USD"},"totalPrice":{"amount":373.5,"currency":"USD"},"amenities":["Free parking","Free Wi-Fi"],"cancellationPolicy":"Non-refundable rate. No changes.","refundable":false},{"id":"skyline","name":"Skyline Grand","photos":["https://images.example.test/skyline-1.jpg","https://broken.example.test/missing.jpg"],"stars":5,"neighborhood":"Financial District","nightlyPrice":{"amount":342,"currency":"USD"},"totalPrice":{"amount":1026,"currency":"USD"},"cancellationPolicy":"Free cancellation until 48 hours before check-in.","refundable":true,"badges":["Top rated","Spa"]},{"id":"plain","name":"Meadow Motel","stars":2,"guestRating":6.8,"neighborhood":"Airport","nightlyPrice":{"amount":79,"currency":"USD"},"totalPrice":{"amount":237,"currency":"USD"},"cancellationPolicy":"Free cancellation until check-in day.","refundable":true}]}}}'

Validation errors

A spec that does not validate is refused with 400 invalid_spec. The message names each problem and its path, so a model can fix the spec and try again. For example:

Leaving out the required field options:

✖ Invalid input: expected array, received undefined
  → at page.options

Misspelling a field name (unknown keys are rejected):

✖ Unrecognized key: "colour"
  → at page