bookbaltic

com.bookbaltic.services.handoff

Status: Draft. Version 2026-11-01. Part of BookBaltic Scheduling Extensions for USP / UCP Local Services.

Extends: dev.usp-protocol.services.catalog, dev.usp-protocol.services.availability

Schema: https://bookbaltic.com/schemas/usp/handoff.json

What it adds: A vendor-format browser link per start that opens the merchant's own booking page with service, start, party size and level preselected, no hold; target form is UCP permalink (#523) with booking/* field paths


3. Booking handoff for the read-only phase

Base UCP and USP already do: UCP dev.ucp.shopping.permalink (#523) is a browser-addressable intent: a GET navigation that initialises shopping state from URL inputs, never authorises an order, treats inputs as untrusted and keeps attribution (utm_*, attribution/*). It is defined for shopping line items (/buy/{item}:{quantity}). USP has service.url (service page) and continue_url only once a booking or checkout exists (5.2). Gap: no permalink fields for a service start: an agent cannot open the merchant's page with service, start, party size and level preselected.

Status: vendor format. The fields below (permalink_template on the service, permalink_url on a start) are what HEADSPA serves today and stay in this extension because they are live. They are not a conforming UCP permalink. In UCP permalink_url names the link to an existing order or booking (order.json, lodging booking_confirmation.json), which a pre-booking start link is not. The target form is UCP dev.ucp.shopping.permalink (#523) applied to services, proposed upstream: the business declares the permalink capability with config.endpoint, and the agent builds the link from the compact item path and booking/* field paths addressing the USP BookingContext (schemas/paid_bookings.json):

ChoiceField pathNotes
Servicecompact item {service_id}:1booking/service_id MAY be omitted; the business takes it from the single compact item.
Start from availabilitybooking/slot/idPreferred when the agent has a slot.
Start without a slot idbooking/slot/startRFC 3339 with offset, percent-encoded (+ as %2B); the business resolves it to a slot (slot.id is required in SlotReference).
Party sizebooking/party_sizeThe compact quantity stays 1 per booking; party size is never read from it.
Attributionattribution/* or non-UCP utm_*Unchanged from permalink.

Target form (HEADSPA target, not served yet). Profile entry:

"dev.ucp.shopping.permalink": [{ "version": "2026-08-25",
  "spec": "https://ucp.dev/2026-08-25/specification/permalink",
  "schema": "https://ucp.dev/2026-08-25/schemas/shopping/permalink.json",
  "config": { "endpoint": "https://www.head-spa.lv/book" } }]

A start with the slot id returned by availability (preferred):

https://www.head-spa.lv/book/svc-aqua-head-spa:1?booking/slot/id=svc-aqua-head-spa%3A2026-12-05T17%3A00%3Apair&booking/party_size=2&attribution/utm_source=agent

The same start known only by its time:

https://www.head-spa.lv/book/svc-aqua-head-spa:1?booking/slot/start=2026-12-05T17%3A00%3A00%2B02%3A00&booking/party_size=2&attribution/utm_source=agent

Possible resolution:

HTTP/1.1 303 See Other
Location: https://www.head-spa.lv/?ritual=svc-aqua-head-spa&guests=2&date=2026-12-05&time=17%3A00&utm_source=agent
Cache-Control: no-store
Referrer-Policy: no-referrer

In the target form a pre-booking start link gets no permalink_url field. A business that supports USP bookings without dev.ucp.shopping.checkout MAY declare the permalink capability; for it the booking root keeps the USP BookingContext shape (paid_bookings.json#/$defs/BookingContext), the business maps booking/slot/id and booking/party_size to the Create Booking fields slot_id and party_size (USP 5.3.1), and resolves booking/slot/start to a slot itself. A stale start keeps the 303 and lands the guest on the nearest valid step. On alignment the vendor fields below, including permalink_url on a start, are dropped; until then agents use them.

Schema

{
  "$id": "https://bookbaltic.com/schemas/usp/handoff.json",
  "$defs": {
    "ServiceExt": { "type": "object", "properties": {
      "permalink_template": { "type": "string",
        "description": "Vendor format. RFC 6570 level 1 template; variables: locale, service_id, level, party_size, date, time. Resolved under the UCP permalink input rules (GET, no order, no hold, inputs untrusted); target form is a UCP permalink with booking/* field paths." },
      "continue_url_template": { "type": "string", "deprecated": true, "description": "Earlier name of permalink_template; same value." },
      "locales": { "type": "array", "items": { "type": "string" } } } },
    "SlotExt": { "type": "object", "properties": {
      "permalink_url": { "type": "string", "format": "uri", "pattern": "^https://", "description": "Vendor field: a pre-booking link to this start. Not the UCP permalink_url, which names an existing order or booking." },
      "continue_url": { "type": "string", "format": "uri", "deprecated": true, "description": "Earlier name of permalink_url. In UCP continue_url resumes an existing session, which this link is not." },
      "holds_slot": { "const": false } } }
  }
}

Rules

Example (HEADSPA live format)

"com.bookbaltic.services.handoff": {
  "permalink_template": "https://www.head-spa.lv/{locale}?ritual={service_id}&level=basic&guests={party_size}&date={date}&time={time}&hs_via=ai_profile",
  "locales": ["lv", "en", "ru"] }

The template is live on each service of POST https://www.head-spa.lv/usp/v1/services/list; continue_url_template is sent alongside with the same value. A start in a point check answer (5.2) carries "permalink_url": "https://www.head-spa.lv/en?ritual=svc-aqua-head-spa&level=basic&guests=2&date=2026-11-05&time=11%3A30&hs_via=ai_check" and continue_url with the same value; hs_via is the merchant's own source tag. The /en prefix is redirected: the site answers 307 to / with the query kept (Cache-Control: public, max-age=0, must-revalidate, Referrer-Policy: strict-origin-when-cross-origin), then 200; /lv and /ru answer 200 directly. Agents follow the redirect as an ordinary navigation.

Without the extension: the agent links service.url, and the guest picks everything again.

Open questions: does the services permalink overlap with ESP (USP Section 9.5) as a "link" delegation mode?