預訂問題
「預訂問題」功能有助於蒐集旅客的額外詳細資料
搶先體驗預覽版
本文件屬於一項僅限特定合作夥伴參與的「搶先體驗」預覽計畫。Beta 測試計畫將於 2026 年第三季啟動,並於 2027 年正式推出。
如果您有興趣成為 Beta 合作夥伴,請聯絡您的客戶經理。
簡介
「預訂問題」功能可讓您以靈活且 type-safe 的方式,收集旅客的額外資料 (e.g. 身高、體重、旅行證件資訊、飲食限制等)。
主要優點
- 彈性: 無需 API 版本控制即可新增資料類型
- Type-safe: JSON 模式可確保結構正確
- 可發現性: 可透過目錄端點查詢可用類型
- 已篩選: 您只會看到您可以支持的活動
- 本地化: 問題支援多種語言
文件術語表
- 解決方案架構
核心概念、通用資料類型模型、活動需求、合作夥伴能力,以及結構化答案提交。 - API 端點
「資料類型目錄」、「活動篩選」及「價格查詢」端點會提供預訂相關的問題。 - 條件式問題
父子問題的運作方式,以及驅動顯示/隱藏邏輯的運算子。 - 整合流程
End-to-end 從發現到預訂提交的各步驟。 - 資料類型範例
一個展示其資料結構定義與答案結構的資料類型範例。 - 本地化
如何在保持 ID 不變的情況下,以多種語言返回問題和選項。 - 驗證
Client-side 及 server-side 中的驗證規則與錯誤回應格式。
解決方案架構
核心概念
泛型資料型別模型
可重複使用的資料類型模型代表資訊類別 (必要時可新增更多類別),e.g. 包含「測量」、「選項」、「地址」及「電話號碼」。
如需查閱《data-types》的完整目錄,請致電 GET /experiences/booking-questions/data-types
活動要求
活動會透過預訂問題來聲明所需的数据類型,並指定:
- 該問題的唯一字串識別碼
- 問題內容 (本地化)
- 問題文字的說明 (已在地化)
- 資料型別參考
- 無論是採用 per-traveler 還是 per-booking
- 驗證限制 (最小/最大值、長度等)
- 適用於哪些票券
- 為指定的父題定義子題
- 可選的條件邏輯 (取決於其他答案的問題)
合作夥伴的能力
您可透過在搜尋/內容端點上使用 ``supported_booking_data_types 篩選參數,來宣告所支援的資料類型。此功能會篩選活動,僅顯示您能夠處理的那些。
- 若要動態處理任何** 類型的**,請傳送
supported_booking_data_types=*。 - 如需支援**,或有關** 的預訂問題,請寄信至
supported_booking_data_types=none。
結構化答案提交
答案已適當嵌套:
- Per-traveler 答案:—位於每個旅客物件的
booking_question_answers陣列中。 - Per-booking 答案:—位於預訂層級的
booking_question_answers陣列中。
API 端點
取得資料類型目錄
終點: GET /experiences/booking-questions/data-types
查詢參數: ?date_updated_start
目的: 返回所有可用資料型別模型的技術參考目錄。
請求範例:
GET /experiences/booking-questions/data-types回應範例:
[
{
"date_added": "2026-07-14T11:30:08.104575+02:00",
"date_updated": "2026-07-14T11:30:08.104575+02:00",
"description": "Used for collecting any measurement with a numeric value and unit. Common examples include height (in/cm/foot/meter), weight (lb/kg/stone), shoe size (us_men/us_women/eu/uk/cm), helmet size (in/cm), waist size (in/cm), and chest size (in/cm). The allowed_units field specifies which units are valid for this measurement.",
"name": "Measurement",
"schema_definition": {
"properties": {
"unit": {
"description": "Unit of measurement - must be one of the allowed_options specified for the question",
"type": "string"
},
"value": {
"description": "Numeric value as string to preserve precision",
"pattern": "^[0-9]+(\\.[0-9]+)?$",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"type": "object"
},
"type": "measurement"
},
]活動篩選
查詢參數: supported_booking_data_types
適用於: /experiences/activities/content 及/experiences/activities/availability 端點
目的: 篩選結果,僅包含其必填問題使用指定資料類型的活動。
範例:
GET /experiences/activities/content
?activity_id=123&activity_id=456
&language=en-US
&supported_booking_data_types=measurement
&supported_booking_data_types=selection
&supported_booking_data_types=text- 若要處理所有類型並顯示所有活動,請使用
supported_booking_data_types=*。屆時,您將負責處理「價格查詢」與「預訂」中的所有題型。 - 如需處理非預訂相關的問題,請使用
supported_booking_data_types=none。這將僅回傳沒有預約問題的活動。
價格查詢 (包含預訂相關問題)
終點: GET /experiences/activities/{activity_id}/price-check
目的: 執行價格查詢,並回傳該活動的預訂相關問題 (如有)。
請求範例:
GET /experiences/activities/12345/price-check?token=abc123&tickets=...回應範例:
{
"status": "available",
"offer_pricing": {
"total": {
"value": "150.00",
"currency": "USD"
}
},
"booking_questions": [
{
"id": "height_1",
"type": "measurement",
"question": "What is your height?",
"description": "Required for safety equipment fitting",
"applies_to": "per_traveler",
"ticket_ids": ["12346","12345"]
"allowed_options": [
{ "id": "in", "label": "in" },
{ "id": "cm", "label": "cm" }
]
},
{
"id": "weight_1",
"type": "measurement",
"question": "What is your weight?",
"description": "Required for zipline weight limits",
"applies_to": "per_traveler",
"ticket_ids": ["12346","12345"]
"allowed_options": [
{ "id": "lb", "label": "lb" },
{ "id": "kg", "label": "kg" }
]
}
],
"links": {
"create": {
"method": "POST",
"href": "/itineraries/activity"
}
}
}注意: 若您在先前的搜尋/內容呼叫中,曾使用supported_booking_data_types 來篩選活動,則所有回傳的問題都將採用您所支援的資料類型。
條件式問題
問題可根據先前問題的答案而有所不同。這對於根據使用者的選擇來蒐集不同的資訊非常有用。
運作方式
- 父級問題—一個問題 (通常為
selection類型),其答案將決定後續的內容。 - 子節點查詢—包含
conditional欄位且引用父節點的查詢。 - 雙向關聯—父節點在其
children陣列中列出子節點;子節點則透過完整的條件邏輯來引用父節點。
範例:聯絡方式選擇
家長提問:
{
"id": "contact_method_1",
"type": "selection",
"question": "How would you prefer to be contacted?",
"applies_to": "per_traveler",
"ticket_ids": ["12346","12345"]
"children": ["contact_phone_1", "contact_whatsapp_1", "contact_email_1"],
"allow_multiple": false,
"allowed_options": [
{ "id": "phone", "label": "Phone Call" },
{ "id": "whatsapp", "label": "WhatsApp" },
{ "id": "email", "label": "Email" },
{ "id": "no_contact", "label": "No Contact" }
]
}附帶條件的子問題:
[
{
"id": "contact_phone_1",
"type": "phone",
"question": "What is your phone number?",
"applies_to": "per_traveler",
"conditional": {
"parent_question_id": "contact_method_1",
"show_when": {
"operator": "equals",
"field": "selected",
"values": ["phone"]
}
}
},
{
"id": "contact_whatsapp_1",
"type": "text",
"question": "What is your WhatsApp username?",
"applies_to": "per_traveler",
"conditional": {
"parent_question_id": "contact_method_1",
"show_when": {
"operator": "equals",
"field": "selected",
"values": ["whatsapp"]
}
}
},
{
"id": "contact_email_1",
"type": "email",
"question": "What is your email address?",
"applies_to": "per_traveler",
"conditional": {
"parent_question_id": "contact_method_1",
"show_when": {
"operator": "equals",
"field": "selected",
"values": ["email"]
}
}
}
]條件運算子
| 操作員 | 意思 |
|---|---|
contains | 該欄位包含任何指定值之一 |
equals | 該欄位與任何指定值完全相符 |
not_contains | 該欄位不包含任何指定值 |
not_equals | 該欄位與任何指定值均不符 |
整合流程
步驟 1:探索階段
呼叫資料類型目錄以確定哪些類型受到支援:
GET /experiences/booking-questions/data-types?date_updated_start=2026-02-01若提供date_updated_start,則僅會回傳在指定日期或之後新增的資料類型。
步驟 2:使用篩選功能進行搜尋
篩選活動,僅顯示包含相容問題的活動:
GET /experiences/activities/content
?activity_id=123&activity_id=456&activity_id=789
&language=en-US
&supported_booking_data_types=measurement
&supported_booking_data_types=selection
&supported_booking_data_types=text此功能會篩選結果,僅包含其必答問題使用指定資料類型的活動。
步驟 3:價格確認
在價格查詢過程中,您會在回覆中收到關於預訂的問題:
GET /experiences/activities/12345/price-check?token=abc123&tickets=...回應中包含一個booking_questions 陣列 (如有)。所有問題均採用您篩選條件中的資料類型。
步驟 4:彙整答案
建立用於收集答案的用戶介面:
- 針對每種資料類型渲染適當的輸入
- 使用資料類型目錄中的架構來驗證
client-side - 關於「per-traveler」的問題,請為每位旅客彙整答案
- 關於 per-booking 的問題,請集中蒐集一次
- 處理條件式問題 (根據父問題的答案顯示/隱藏)
步驟 5:提交預訂
請隨預訂請求一併提交答案:
POST /itineraries/activity{
"primary_traveler": [
{
"given_name": "John",
"family_name": "Doe",
"booking_question_answers": [
{
"question_id": "height_1",
"answer": {
"value": "72",
"unit": "in"
}
},
{
"question_id": "weight_1",
"answer": {
"value": "180",
"unit": "lb"
}
}
]
}
],
"booking_question_answers": [
{
"question_id": "emergency_contact_1",
"answer": {
"value": "Jane Doe, +1-555-1234"
}
}
]
}資料型別範例
尺寸 (尺寸測量)
模式定義:
{
"type": "object",
"required": ["value", "unit"],
"properties": {
"value": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
},
"unit": {
"type": "string"
}
}
}答案範例:
{
"value": "72",
"unit": "in"
}在地化
透過「價格查詢」中的 ``language 參數,問題功能支援多種語言。
英文範例:
GET /experiences/activities/12345/price-check?token=...&language=en-US{
"id": "dietary_1",
"type": "selection",
"question": "Dietary preferences (optional)",
"description": "Please share any dietary restrictions or allergies."
"applies_to": "per_traveler",
"allow_multiple": false
"ticket_ids": ["12345", "12346"]
"options": [
{ "id": "vegetarian", "label": "Vegetarian" },
{ "id": "vegan", "label": "Vegan" }
]
}西班牙語範例:
與 . 相同的問題language=es-ES。
GET /experiences/activities/12345/price-check?token=...&language=es-ES{
"id": "dietary_1",
"type": "selection",
"question": "Preferencias alimentarias (opcional)",
"description": "Por favor, indica las restricciones alimentarias o alergias. "
"applies_to": "per_traveler",
"allow_multiple": false
"ticket_ids": ["12345", "12346"]
"options": [
{ "id": "vegetarian", "label": "Vegetariano" },
{ "id": "vegan", "label": "Vegano" }
]
}註:為確保答案提交的一致性, 中的題目編號與選項編號在各語言版本中均保持不變。
驗證
Client-side 驗證
合作夥伴應使用資料類型目錄中的架構,對 client-side 上的答案進行驗證,以確保結構正確。
Server-side 驗證
預訂端點會驗證以下事項:
- 所有必答題均已作答
- 答案結構與資料類型架構相符
- 值符合驗證限制
- 針對條件式問題,已給予適當的回答
- Per-traveler 每位旅客都會收到相關問題
- Per-booking 問題只會提供一次
錯誤回應
{
"type": "validation_failed",
"message": "One or more booking question answers are invalid",
"errors": [
{
"type": "missing_required_answer",
"message": "Required question 'height_1' not answered for traveler 1",
"fields": [
{
"path": "$.travelers[0].booking_question_answers",
"value": null
}
]
}
]
}