Skip to content
DocsDashboard

Itineraries (itinerary)

A day-by-day plan you propose (a trip, a weekend, a wedding day, a conference, a night out) for the person to approve or change. Result is { decision: "approve" } or { decision: "changes", items?: [{ itemId, note? }], note? }.

When to use it: You propose a day-by-day plan and the person approves it or marks the stops they want changed. Approving books nothing: book the stops yourself afterwards.

When not to use it: Do not use it to collect a single choice (use the matching picker), or to book or change anything: the person reviews the plan; you act on it.

Gather first:

  • The plan itself: what happens each day, in order, with times and places you have confirmed or proposed.
  • Whether you want approval (readOnly left out) or are only showing the plan (readOnly: true).

Writing good content:

  • Give each item an id you can act on: change requests name items by id.
  • Use kind for the icon (flight, stay, meal, activity, ...), start with the local UTC offset, and place with an address when you have one.
  • Show bookingStatus and confirmation only for what you have actually booked; never mark something booked that is not.
  • With readOnly: true the page only shows the plan and returns no answer.

Display-only when readOnly is true: in that case the page shows information only, has no Submit button and accepts no answer. Otherwise the person answers as below.

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
coverhttps URL. Optional https URL of a landscape cover photo shown at the top. Decorative.
summarystring. Optional one-line overview under the cover, e.g. "3 days in Lisbon · 2 travelers". Limits: 1-120 characters.
timeZonestring. Optional IANA time zone the times are shown in, e.g. "Europe/Lisbon". Flight and train times are always shown at the UTC offset written in them (local departure and arrival). Omit to show every time at its written offset.
readOnlyboolean. Optional. true shows the plan for reference only: no buttons, and no answer is accepted. Omit to ask the person to approve or request changes.
daysobject[], required. The plan, one entry per day in order (1-14). Limits: 1-14 items.
days[].datedate (YYYY-MM-DD). Optional calendar date, "YYYY-MM-DD". Include when known; days are then shown as "Fri, May 15".
days[].labelstring. Optional day label, e.g. "Day 1" or "Saturday night". Omit to show "Day 1", "Day 2", and so on. Limits: 1-40 characters.
days[].titlestring. Optional theme for the day, e.g. "Alfama and the river". Limits: 1-80 characters.
days[].itemsobject[], required. What happens that day, in order (1-12). Limits: 1-12 items.
days[].items[].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}$.
days[].items[].titlestring, required. Required. What happens, e.g. "Dinner at Lumen" or "Flight to Lisbon". Limits: 1-120 characters.
days[].items[].kind"flight" | "train" | "transport" | "stay" | "meal" | "activity" | "event" | "note". Optional category; picks the icon on the timeline. One of flight, train, transport (taxi, ferry, car), stay, meal, activity, event, note. Omit for a plain dot.
days[].items[].startISO 8601 timestamp. Optional start time, ISO 8601 with UTC offset, e.g. "2026-05-15T19:30:00+01:00". Include when the time is set; use timeLabel for a loose time instead.
days[].items[].endISO 8601 timestamp. Optional end time, same format as start. Needs start, and must be after it.
days[].items[].timeLabelstring. Optional loose time shown instead of a clock time, e.g. "Morning" or "After lunch". Do not combine with start. Limits: 1-40 characters.
days[].items[].placeobject. Optional place. Include when known; name is required inside it.
days[].items[].place.namestring, required. Place name, e.g. "Time Out Market". Limits: 1-120 characters.
days[].items[].place.addressstring. Optional street address. Include when known. Limits: 1-200 characters.
days[].items[].place.mapsUrlhttps URL. Optional https map link. Omit to link a map search for the name and address.
days[].items[].photohttps URL. Optional https URL of a photo of the place or activity. Decorative. Omit if you have none.
days[].items[].descriptionstring. Optional plain-text detail, one or two sentences. No HTML or Markdown; it is shown as typed. Limits: 1-600 characters.
days[].items[].priceobject. Optional cost of this item, total for the group. Use amount 0 to show "Free". Omit when unknown.
days[].items[].price.amountnumber, required. Amount in major units, e.g. 249.99 for $249.99. Limits: 0-10000000.
days[].items[].price.currencystring, required. ISO 4217 currency code, uppercase, e.g. "USD", "EUR".
days[].items[].bookingStatus"booked" | "to_book" | "optional". Optional status you report: "booked" (already booked), "to_book" (not booked yet; approving the plan does not book it), "optional" (nice to have, skip if short on time). Omit for no label.
days[].items[].confirmationstring. Optional confirmation or booking code, shown in monospace, e.g. "TP 8RX4QK". Limits: 1-40 characters.
days[].items[].linksobject[]. Optional, up to 3 links (tickets, menu, booking page). Limits: at most 3 items.
days[].items[].links[].labelstring, required. Link text, e.g. "Tickets" or "Menu". Limits: 1-40 characters.
days[].items[].links[].urlhttps URL, required. https URL.

Why these are required: A plan needs at least one stop per day, with an id the change request can name and a title to show.

When optional fields are left out: No hero photo or summary line (the stop counts still show); days are labelled "Day 1", "Day 2"; a plain dot instead of a kind icon; no time, map link, photo, price, booking chip, code or links. Times use each timestamp's UTC offset (flights and trains always do). Under the plan the person answers "Looks good" or "Request changes"; marking a stop "Change this" picks "Request changes", which needs a marked stop or a note before Submit is enabled. With readOnly: true there is no question and no Submit, and every result is rejected.

Minimal example

Only the required fields:

{
  "version": 1,
  "title": "Plan for Saturday",
  "page": {
    "type": "itinerary",
    "days": [
      {
        "items": [
          {
            "id": "brunch",
            "title": "Brunch with Ana"
          },
          {
            "id": "park",
            "title": "Walk in the park"
          }
        ]
      }
    ]
  }
}

Full example

{
  "version": 1,
  "title": "Your long weekend in Lisbon",
  "description": "Here is the plan I put together. Approve it, or mark anything you want changed.",
  "branding": {
    "name": "Juniper Travel",
    "accentColor": "#0b7a5c"
  },
  "page": {
    "type": "itinerary",
    "cover": "https://images.example.test/itinerary-lisbon-cover.jpg",
    "summary": "3 days in Lisbon · 2 travelers",
    "timeZone": "Europe/Lisbon",
    "days": [
      {
        "date": "2026-05-15",
        "label": "Day 1",
        "title": "Arrive and settle in",
        "items": [
          {
            "id": "flight-out",
            "kind": "flight",
            "title": "Flight to Lisbon",
            "start": "2026-05-15T06:10:00-04:00",
            "end": "2026-05-15T17:35:00+01:00",
            "place": {
              "name": "Humberto Delgado Airport (LIS)"
            },
            "description": "Skyward Air SW 212, nonstop from Boston. Two checked bags included.",
            "price": {
              "amount": 1184,
              "currency": "USD"
            },
            "bookingStatus": "booked",
            "confirmation": "SW8RX4QK"
          },
          {
            "id": "hotel",
            "kind": "stay",
            "title": "Check in at Casa do Largo",
            "start": "2026-05-15T18:45:00+01:00",
            "place": {
              "name": "Casa do Largo",
              "address": "Largo do Carmo 14, Lisbon"
            },
            "photo": "https://images.example.test/itinerary-casa-do-largo.jpg",
            "description": "Three nights, a double room with a balcony over the square. Breakfast included.",
            "price": {
              "amount": 612,
              "currency": "EUR"
            },
            "bookingStatus": "booked",
            "confirmation": "CDL-20931"
          },
          {
            "id": "dinner-1",
            "kind": "meal",
            "title": "Dinner at Lumen",
            "start": "2026-05-15T20:30:00+01:00",
            "end": "2026-05-15T22:30:00+01:00",
            "place": {
              "name": "Lumen",
              "address": "Rua da Rosa 52, Bairro Alto"
            },
            "photo": "https://images.example.test/itinerary-lumen.jpg",
            "description": "Small seafood place five minutes’ walk from the hotel. The grilled octopus is the one to get.",
            "price": {
              "amount": 110,
              "currency": "EUR"
            },
            "bookingStatus": "to_book",
            "links": [
              {
                "label": "Menu",
                "url": "https://lumen.example/menu"
              }
            ]
          }
        ]
      },
      {
        "date": "2026-05-16",
        "label": "Day 2",
        "title": "Alfama and the river",
        "items": [
          {
            "id": "tram",
            "kind": "transport",
            "title": "Tram 28 up to the castle",
            "timeLabel": "Morning",
            "place": {
              "name": "Praça Martim Moniz"
            },
            "description": "Get on at the first stop to find a seat. Buy a 24-hour pass at the kiosk.",
            "price": {
              "amount": 13.4,
              "currency": "EUR"
            }
          },
          {
            "id": "castle",
            "kind": "activity",
            "title": "Castelo de São Jorge",
            "start": "2026-05-16T10:00:00+01:00",
            "end": "2026-05-16T12:00:00+01:00",
            "place": {
              "name": "Castelo de São Jorge",
              "address": "Rua de Santa Cruz do Castelo, Lisbon"
            },
            "photo": "https://images.example.test/itinerary-castle.jpg",
            "price": {
              "amount": 30,
              "currency": "EUR"
            },
            "bookingStatus": "to_book",
            "links": [
              {
                "label": "Tickets",
                "url": "https://castle.example/tickets"
              }
            ]
          },
          {
            "id": "lunch-2",
            "kind": "meal",
            "title": "Lunch at Tasca Azul",
            "start": "2026-05-16T13:00:00+01:00",
            "place": {
              "name": "Tasca Azul",
              "address": "Beco do Mexias 3, Alfama"
            },
            "photo": "https://images.example.test/itinerary-tasca-azul.jpg",
            "description": "Walk-in only; there is usually a short line after 1 PM."
          },
          {
            "id": "river",
            "kind": "activity",
            "title": "Sunset sail on the Tagus",
            "start": "2026-05-16T18:30:00+01:00",
            "end": "2026-05-16T20:30:00+01:00",
            "place": {
              "name": "Doca de Santo Amaro"
            },
            "photo": "https://images.example.test/itinerary-sail.jpg",
            "price": {
              "amount": 96,
              "currency": "EUR"
            },
            "bookingStatus": "optional"
          }
        ]
      },
      {
        "date": "2026-05-17",
        "label": "Day 3",
        "title": "Belém, then home",
        "items": [
          {
            "id": "pasteis",
            "kind": "meal",
            "title": "Custard tarts in Belém",
            "timeLabel": "Late morning",
            "place": {
              "name": "Fábrica do Rio",
              "address": "Rua de Belém 88, Lisbon"
            },
            "photo": "https://images.example.test/itinerary-pasteis.jpg",
            "price": {
              "amount": 0,
              "currency": "EUR"
            }
          },
          {
            "id": "museum",
            "kind": "activity",
            "title": "Tile museum",
            "start": "2026-05-17T13:30:00+01:00",
            "end": "2026-05-17T15:00:00+01:00",
            "place": {
              "name": "Museu do Azulejo"
            },
            "photo": "https://images.example.test/itinerary-tiles.jpg",
            "price": {
              "amount": 20,
              "currency": "EUR"
            },
            "bookingStatus": "to_book"
          },
          {
            "id": "flight-home",
            "kind": "flight",
            "title": "Flight home",
            "start": "2026-05-17T19:05:00+01:00",
            "end": "2026-05-17T21:40:00-04:00",
            "place": {
              "name": "Humberto Delgado Airport (LIS)"
            },
            "bookingStatus": "booked",
            "confirmation": "SW8RX4QK"
          }
        ]
      }
    ]
  }
}

Result

When the person submits, the result has these fields:

FieldDescription
(variant 1).decision"approve", required. The person approved the whole plan.
(variant 2).decision"changes", required. The person wants changes before you go ahead.
(variant 2).itemsobject[]. Items marked "Change this", in plan order. Limits: 1-168 items.
(variant 2).items[].itemIdstring, required. id of an item to change. Limits: matches ^[A-Za-z0-9_-]{1,64}$.
(variant 2).items[].notestring. What to change about it, in their words, when they wrote it. Limits: 1-500 characters.
(variant 2).notestring. Overall note on what to change. Limits: 1-500 characters.

For the full example above, a result could be:

{
  "decision": "changes",
  "items": [
    {
      "itemId": "dinner-1",
      "note": "Somewhere closer to the hotel"
    }
  ]
}

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 "itinerary" (the same JSON as GET /v1/page-types/itinerary), then create_page with this input (no version or branding; branding comes from your dashboard):

{
  "title": "Your long weekend in Lisbon",
  "description": "Here is the plan I put together. Approve it, or mark anything you want changed.",
  "page": {
    "type": "itinerary",
    "cover": "https://images.example.test/itinerary-lisbon-cover.jpg",
    "summary": "3 days in Lisbon · 2 travelers",
    "timeZone": "Europe/Lisbon",
    "days": [
      {
        "date": "2026-05-15",
        "label": "Day 1",
        "title": "Arrive and settle in",
        "items": [
          {
            "id": "flight-out",
            "kind": "flight",
            "title": "Flight to Lisbon",
            "start": "2026-05-15T06:10:00-04:00",
            "end": "2026-05-15T17:35:00+01:00",
            "place": {
              "name": "Humberto Delgado Airport (LIS)"
            },
            "description": "Skyward Air SW 212, nonstop from Boston. Two checked bags included.",
            "price": {
              "amount": 1184,
              "currency": "USD"
            },
            "bookingStatus": "booked",
            "confirmation": "SW8RX4QK"
          },
          {
            "id": "hotel",
            "kind": "stay",
            "title": "Check in at Casa do Largo",
            "start": "2026-05-15T18:45:00+01:00",
            "place": {
              "name": "Casa do Largo",
              "address": "Largo do Carmo 14, Lisbon"
            },
            "photo": "https://images.example.test/itinerary-casa-do-largo.jpg",
            "description": "Three nights, a double room with a balcony over the square. Breakfast included.",
            "price": {
              "amount": 612,
              "currency": "EUR"
            },
            "bookingStatus": "booked",
            "confirmation": "CDL-20931"
          },
          {
            "id": "dinner-1",
            "kind": "meal",
            "title": "Dinner at Lumen",
            "start": "2026-05-15T20:30:00+01:00",
            "end": "2026-05-15T22:30:00+01:00",
            "place": {
              "name": "Lumen",
              "address": "Rua da Rosa 52, Bairro Alto"
            },
            "photo": "https://images.example.test/itinerary-lumen.jpg",
            "description": "Small seafood place five minutes’ walk from the hotel. The grilled octopus is the one to get.",
            "price": {
              "amount": 110,
              "currency": "EUR"
            },
            "bookingStatus": "to_book",
            "links": [
              {
                "label": "Menu",
                "url": "https://lumen.example/menu"
              }
            ]
          }
        ]
      },
      {
        "date": "2026-05-16",
        "label": "Day 2",
        "title": "Alfama and the river",
        "items": [
          {
            "id": "tram",
            "kind": "transport",
            "title": "Tram 28 up to the castle",
            "timeLabel": "Morning",
            "place": {
              "name": "Praça Martim Moniz"
            },
            "description": "Get on at the first stop to find a seat. Buy a 24-hour pass at the kiosk.",
            "price": {
              "amount": 13.4,
              "currency": "EUR"
            }
          },
          {
            "id": "castle",
            "kind": "activity",
            "title": "Castelo de São Jorge",
            "start": "2026-05-16T10:00:00+01:00",
            "end": "2026-05-16T12:00:00+01:00",
            "place": {
              "name": "Castelo de São Jorge",
              "address": "Rua de Santa Cruz do Castelo, Lisbon"
            },
            "photo": "https://images.example.test/itinerary-castle.jpg",
            "price": {
              "amount": 30,
              "currency": "EUR"
            },
            "bookingStatus": "to_book",
            "links": [
              {
                "label": "Tickets",
                "url": "https://castle.example/tickets"
              }
            ]
          },
          {
            "id": "lunch-2",
            "kind": "meal",
            "title": "Lunch at Tasca Azul",
            "start": "2026-05-16T13:00:00+01:00",
            "place": {
              "name": "Tasca Azul",
              "address": "Beco do Mexias 3, Alfama"
            },
            "photo": "https://images.example.test/itinerary-tasca-azul.jpg",
            "description": "Walk-in only; there is usually a short line after 1 PM."
          },
          {
            "id": "river",
            "kind": "activity",
            "title": "Sunset sail on the Tagus",
            "start": "2026-05-16T18:30:00+01:00",
            "end": "2026-05-16T20:30:00+01:00",
            "place": {
              "name": "Doca de Santo Amaro"
            },
            "photo": "https://images.example.test/itinerary-sail.jpg",
            "price": {
              "amount": 96,
              "currency": "EUR"
            },
            "bookingStatus": "optional"
          }
        ]
      },
      {
        "date": "2026-05-17",
        "label": "Day 3",
        "title": "Belém, then home",
        "items": [
          {
            "id": "pasteis",
            "kind": "meal",
            "title": "Custard tarts in Belém",
            "timeLabel": "Late morning",
            "place": {
              "name": "Fábrica do Rio",
              "address": "Rua de Belém 88, Lisbon"
            },
            "photo": "https://images.example.test/itinerary-pasteis.jpg",
            "price": {
              "amount": 0,
              "currency": "EUR"
            }
          },
          {
            "id": "museum",
            "kind": "activity",
            "title": "Tile museum",
            "start": "2026-05-17T13:30:00+01:00",
            "end": "2026-05-17T15:00:00+01:00",
            "place": {
              "name": "Museu do Azulejo"
            },
            "photo": "https://images.example.test/itinerary-tiles.jpg",
            "price": {
              "amount": 20,
              "currency": "EUR"
            },
            "bookingStatus": "to_book"
          },
          {
            "id": "flight-home",
            "kind": "flight",
            "title": "Flight home",
            "start": "2026-05-17T19:05:00+01:00",
            "end": "2026-05-17T21:40:00-04:00",
            "place": {
              "name": "Humberto Delgado Airport (LIS)"
            },
            "bookingStatus": "booked",
            "confirmation": "SW8RX4QK"
          }
        ]
      }
    ]
  }
}

REST

curl https://pagesystems.ai/v1/pages \
  -H "Authorization: Bearer $PAGE_SYSTEMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"version":1,"title":"Your long weekend in Lisbon","description":"Here is the plan I put together. Approve it, or mark anything you want changed.","branding":{"name":"Juniper Travel","accentColor":"#0b7a5c"},"page":{"type":"itinerary","cover":"https://images.example.test/itinerary-lisbon-cover.jpg","summary":"3 days in Lisbon · 2 travelers","timeZone":"Europe/Lisbon","days":[{"date":"2026-05-15","label":"Day 1","title":"Arrive and settle in","items":[{"id":"flight-out","kind":"flight","title":"Flight to Lisbon","start":"2026-05-15T06:10:00-04:00","end":"2026-05-15T17:35:00+01:00","place":{"name":"Humberto Delgado Airport (LIS)"},"description":"Skyward Air SW 212, nonstop from Boston. Two checked bags included.","price":{"amount":1184,"currency":"USD"},"bookingStatus":"booked","confirmation":"SW8RX4QK"},{"id":"hotel","kind":"stay","title":"Check in at Casa do Largo","start":"2026-05-15T18:45:00+01:00","place":{"name":"Casa do Largo","address":"Largo do Carmo 14, Lisbon"},"photo":"https://images.example.test/itinerary-casa-do-largo.jpg","description":"Three nights, a double room with a balcony over the square. Breakfast included.","price":{"amount":612,"currency":"EUR"},"bookingStatus":"booked","confirmation":"CDL-20931"},{"id":"dinner-1","kind":"meal","title":"Dinner at Lumen","start":"2026-05-15T20:30:00+01:00","end":"2026-05-15T22:30:00+01:00","place":{"name":"Lumen","address":"Rua da Rosa 52, Bairro Alto"},"photo":"https://images.example.test/itinerary-lumen.jpg","description":"Small seafood place five minutes’ walk from the hotel. The grilled octopus is the one to get.","price":{"amount":110,"currency":"EUR"},"bookingStatus":"to_book","links":[{"label":"Menu","url":"https://lumen.example/menu"}]}]},{"date":"2026-05-16","label":"Day 2","title":"Alfama and the river","items":[{"id":"tram","kind":"transport","title":"Tram 28 up to the castle","timeLabel":"Morning","place":{"name":"Praça Martim Moniz"},"description":"Get on at the first stop to find a seat. Buy a 24-hour pass at the kiosk.","price":{"amount":13.4,"currency":"EUR"}},{"id":"castle","kind":"activity","title":"Castelo de São Jorge","start":"2026-05-16T10:00:00+01:00","end":"2026-05-16T12:00:00+01:00","place":{"name":"Castelo de São Jorge","address":"Rua de Santa Cruz do Castelo, Lisbon"},"photo":"https://images.example.test/itinerary-castle.jpg","price":{"amount":30,"currency":"EUR"},"bookingStatus":"to_book","links":[{"label":"Tickets","url":"https://castle.example/tickets"}]},{"id":"lunch-2","kind":"meal","title":"Lunch at Tasca Azul","start":"2026-05-16T13:00:00+01:00","place":{"name":"Tasca Azul","address":"Beco do Mexias 3, Alfama"},"photo":"https://images.example.test/itinerary-tasca-azul.jpg","description":"Walk-in only; there is usually a short line after 1 PM."},{"id":"river","kind":"activity","title":"Sunset sail on the Tagus","start":"2026-05-16T18:30:00+01:00","end":"2026-05-16T20:30:00+01:00","place":{"name":"Doca de Santo Amaro"},"photo":"https://images.example.test/itinerary-sail.jpg","price":{"amount":96,"currency":"EUR"},"bookingStatus":"optional"}]},{"date":"2026-05-17","label":"Day 3","title":"Belém, then home","items":[{"id":"pasteis","kind":"meal","title":"Custard tarts in Belém","timeLabel":"Late morning","place":{"name":"Fábrica do Rio","address":"Rua de Belém 88, Lisbon"},"photo":"https://images.example.test/itinerary-pasteis.jpg","price":{"amount":0,"currency":"EUR"}},{"id":"museum","kind":"activity","title":"Tile museum","start":"2026-05-17T13:30:00+01:00","end":"2026-05-17T15:00:00+01:00","place":{"name":"Museu do Azulejo"},"photo":"https://images.example.test/itinerary-tiles.jpg","price":{"amount":20,"currency":"EUR"},"bookingStatus":"to_book"},{"id":"flight-home","kind":"flight","title":"Flight home","start":"2026-05-17T19:05:00+01:00","end":"2026-05-17T21:40:00-04:00","place":{"name":"Humberto Delgado Airport (LIS)"},"bookingStatus":"booked","confirmation":"SW8RX4QK"}]}]}}}'

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 days:

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

Misspelling a field name (unknown keys are rejected):

✖ Unrecognized key: "colour"
  → at page