com.bookbaltic.services.intake
Status: Draft. Version 2026-11-03. Part of BookBaltic Scheduling Extensions for USP / UCP Local Services.
Extends: dev.usp-protocol.services.booking_questions, dev.usp-protocol.services.bookings
Schema: https://bookbaltic.com/schemas/usp/intake.json
What it adds: What USP 11.4 Booking Questions defers: a form per guest of a party, and an agent draft of non-sensitive answers that only the guest confirms through their own link
6. Pre-visit intake per guest
Base USP already does: Section 11.4 Booking Questions (dev.usp-protocol.services.booking_questions, added 2026-10-31) covers pre-visit forms: Service.booking_question_sets[] with purpose: "intake", completion_timing: "before_confirmation" | "before_service", frequency, validity, subject: "buyer" | "recipient", due_at, items with sensitive and collection: "business_surface"; the booking carries booking_question_state[] (status, continue_url for pending business-surface work); the buyer-input Action requires the buyer, not the agent, to supply each answer; platforms MUST never infer an answer from memory without buyer confirmation (11.4.8, 11.4.11 platform rule 4). This extension does not redefine any of that.
Gaps (USP 11.4.13 defers them): (i) "Group forms per attendee": a pair needs two independent forms, one per guest, and USP models one subject per set (subject: buyer | recipient; "USP has no general participants concept"), a group service whose form needs answers from more than the one modelled recipient "stays withheld" (vector bq-flow-group-participants-deferred); an appointment for two (party_size: 2) has the same gap and no rule at all; (ii) there is no state "prepared by the agent, not yet confirmed by the guest", and no way for the second guest to confirm their own form.
Related: reuses the guest pool of UCP Lodging (#780). Input to #922 Phase 3 "intake forms", proposed upstream as a revision of USP 11.4. Checked: #953 and #954 cover lodging guest and booker identity, not forms; PR #915 (declared_fields) has no per-person or sensitive-data rules; discussion #1022 asks for an explicit end-customer consent statement in the booking request (who, which data, which purpose). None defines per-guest intake or an agent draft.
Schema
{
"$id": "https://bookbaltic.com/schemas/usp/intake.json",
"requires": { "protocol": { "min": "2026-08-20" },
"capabilities": { "dev.usp-protocol.services.booking_questions": { "min": "2026-08-20" },
"dev.usp-protocol.services.bookings": { "min": "2026-08-20" } } },
"$defs": {
"BookingQuestionSetExt": { "type": "object", "description": "Added to a USP BookingQuestionSet.", "properties": {
"per_guest": { "type": "boolean", "default": false,
"description": "Each guest of the party answers this set on their own. Valid only with purpose intake. Every item with sensitive true MUST use collection business_surface." },
"agent_draft": { "enum": ["none", "non_sensitive"], "default": "none",
"description": "non_sensitive: an agent may draft in_band items whose sensitive is not true." } } },
"PerGuestSetRule": { "description": "Applied to a USP BookingQuestionSet that carries this extension: with per_guest true, every item with sensitive true uses collection business_surface (collection defaults to in_band, so it must be present).",
"if": { "required": ["com.bookbaltic.services.intake"], "properties": {
"com.bookbaltic.services.intake": { "required": ["per_guest"], "properties": { "per_guest": { "const": true } } } } },
"then": { "properties": { "items": { "items": {
"if": { "required": ["sensitive"], "properties": { "sensitive": { "const": true } } },
"then": { "required": ["collection"], "properties": { "collection": { "const": "business_surface" } } } } } } } },
"Guest": { "type": "object", "required": ["id"], "properties": {
"id": { "type": "string", "description": "Platform-generated, session-scoped, as UCP Lodging guests[].id" },
"role": { "type": "string", "description": "primary or accompanying, as UCP Lodging guest_assignments.role" },
"first_name": { "type": "string" } } },
"GuestQuestionState": { "type": "object", "required": ["guest_id", "set_id", "revision", "status"], "properties": {
"guest_id": { "type": "string", "description": "guests[].id of this booking" },
"set_id": { "type": "string" },
"revision": { "type": "string" },
"status": { "enum": ["pending", "completed", "expired", "waived", "extended", "under_review", "rejected"],
"description": "As USP BookingQuestionState.status" },
"draft_awaiting_guest": { "type": "boolean", "description": "An agent draft exists and waits for this guest's confirmation; status stays pending." },
"continue_url": { "type": "string", "format": "uri", "pattern": "^https://",
"description": "This guest's own session link: unguessable, expiring, no personal data." },
"due_at": { "type": "string", "format": "date-time" } } },
"BookingExt": { "type": "object", "properties": {
"guests": { "type": "array", "items": { "$ref": "#/$defs/Guest" }, "description": "Same shape as guests[] of com.bookbaltic.services.party (UCP Lodging id and first_name, role from guest_assignments.role); omitted when that extension is negotiated, which then carries the pool" },
"guest_question_state": { "type": "array", "items": { "$ref": "#/$defs/GuestQuestionState" },
"description": "One entry per guest for each per_guest set. Never carries answers." } } },
"DraftRequest": { "type": "object", "required": ["set_id", "revision", "answers"], "properties": {
"set_id": { "type": "string" }, "revision": { "type": "string" },
"answers": { "type": "object", "description": "Keyed by question id, as USP BookingQuestionSubmission.answers; only in_band items whose sensitive is not true." } } },
"DraftResponse": { "type": "object", "required": ["status", "draft_awaiting_guest"], "additionalProperties": false, "properties": {
"status": { "const": "pending" },
"draft_awaiting_guest": { "const": true },
"expires_at": { "type": "string", "format": "date-time" } } }
}
}
Rules
- Declare the form as a USP booking question set (
purpose: "intake", usuallycompletion_timing: "before_service", items withsensitive: trueandcollection: business_surfacefor health questions). This extension only addsper_guestandagent_drafton that set, andguest_question_state[]on the booking; the booking usesguests[]ofcom.bookbaltic.services.party(section 2) when that extension is negotiated, otherwise carriesguests[]of the same shape. - Per guest: with
per_guest: truethe booking carries oneguest_question_stateentry per guest, each with that guest's owncontinue_url. Status only, never answers, never a risk level. The USP set-levelbooking_question_stateentry iscompletedonly when every guest entry iscompleted,extendedorwaived; 11.4.6 gating applies to the set-level entry as today. - In a
per_guestset everysensitive: trueitem MUST usecollection: business_surface. - A platform MUST NOT submit one guest's answers for another guest, in line with 11.4.11 platform rule 2. A minor's form is confirmed by a legal guardian.
- Agent draft (optional):
POST {endpoint}/bookings/{booking_id}/booking-questions/{set_id}/guests/{guest_id}/draftwith aDraftRequest, authenticated by the booking-scoped credential (USP 10.1.6). A draft MAY contain onlyin_banditems whosesensitiveis not true. The business rejects any other item with the recoverable errorbooking_question_draft_not_allowedand a question-levelpath(11.4.9 rules). The response echoes no values and no risk level. - Sensitive answers: the agent never drafts them. Health questions stay on the business surface: each guest answers them on their own
continue_url, as this extension requires (USP 11.4.10 only keeps them out of MCP elicitation forms). - Guest confirms: a draft is not a submission. Only the guest's confirmation on
continue_urlmoves that guest's entry tocompleted: the page shows every drafted answer with an edit option, collects the sensitive items, asks explicit consent for health data (an art. 9(2) exception; for a spa explicit consent, GDPR art. 9(2)(a), which the business must be able to show, art. 7(1)) as a separate step, never preselected (11.4.8), and records the wording shown. An unconfirmed draft expires and counts as not filled. - Agent obligations: an agent MUST NOT tell anyone a form is done because a draft was accepted, and MUST NOT store or reuse draft answers after the call. This is stricter than USP 10.1.3, which lets a platform keep personal data while it manages an active booking.
- A platform that did not negotiate the extension sees plain USP: the business applies 11.4.2 and 11.4.13 as today.
First-draft names replaced to follow USP 11.4: form_id → set_id + revision; due: before_confirm | before_visit → completion_timing: before_confirmation | before_service; per → per_guest; agent_fillable → agent_draft; data_classes → USP sensitive per question; actions[] intake_form → guest_question_state[]. In 2026-11-03: agent_draft values contact_only and contact_and_yes_no → non_sensitive (yes/no health answers are sensitive and never drafted); expires_at on the guest entry → due_at, as USP; fhir_questionnaire_url removed (form formats other than the USP item model are out of scope).
Example (HEADSPA)
HEADSPA asks every guest, before the visit, 9 yes/no health questions and one free-text health note, plus contacts, preferences and consents; the guest fills it on the HEADSPA site (one signed link per guest, www.head-spa.lv/anketa/...) and staff read it in its health module. The extension is not yet declared in the HEADSPA profile; the example shows the intended shape.
Service fragment (USP set with this extension):
"booking_question_sets": [{ "id": "headspa-intake", "revision": "v6",
"purpose": "intake", "completion_timing": "before_service", "frequency": "every_booking", "subject": "recipient",
"items": [ "... 9 yes/no health questions and a health note, all sensitive: true, collection: business_surface; contacts, preferences, consents ..." ],
"com.bookbaltic.services.intake": { "per_guest": true, "agent_draft": "non_sensitive" } }]
Draft request for the second guest (answers keyed by question id):
{ "set_id": "headspa-intake", "revision": "v6",
"answers": { "language": "en", "preferences": "medium pressure, quiet room" } }
Response, no values echoed:
{ "status": "pending", "draft_awaiting_guest": true, "expires_at": "2026-11-07T12:00:00+02:00" }
Booking for two, after the primary guest completed the form and the agent drafted the partner's preferences:
"com.bookbaltic.services.intake": {
"guests": [ { "id": "g1", "role": "primary" }, { "id": "g2", "role": "accompanying", "first_name": "Marta" } ],
"guest_question_state": [
{ "guest_id": "g1", "set_id": "headspa-intake", "revision": "v6", "status": "completed" },
{ "guest_id": "g2", "set_id": "headspa-intake", "revision": "v6", "status": "pending", "draft_awaiting_guest": true,
"continue_url": "https://business.example.com/forms/sess_Kp9w...", "due_at": "2026-11-07T12:00:00+02:00" } ] }
HEADSPA links today are per guest and signed, not yet expiring. The set-level booking_question_state stays pending until g2 confirms.
Without the extension: USP 11.4 collects one form for the one recipient; the second guest of a pair has no form of their own (11.4.13 fails closed), and the agent can only hand over the business-surface link.
Honest risks: the merchant cannot verify what the agent platform retains, which is why sensitive answers stay off the agent path; an agent can mis-record a drafted choice, so the confirm page shows each answer and the venue check stays; one-tap confirmation can become blind consent, so consent is a separate explicit step.
Open questions: should per-guest sets move into USP 11.4 once a participants concept exists, sharing UCP Lodging's guests[], or a shared model if #922 defines one?