travel.book_package — Full Intent Specification
INTENT NAMESPACE: travel
INTENT NAME: book_package
FULL ID: travel.book_package
VERSION: v1.0.0
STATUS: draft
TTBS WEIGHTS: time 0.20 · taste 0.30 · budget 0.25 · safety 0.25
LAST UPDATED: 2026-05-14
Bundled trip with two or more travel components — typically flight + hotel + transfers, or train + hotel, or fully customised multi-city itinerary including activities. Distinct from individual travel.book_flight / travel.book_hotel / travel.book_train (each a single component), and from travel.book_tour_guide (services-only). Partner exemplars: MakeMyTrip Holidays, Yatra Holidays, Thomas Cook, SOTC, Veena World, Kesari Tours, Cox & Kings, EaseMyTrip Holidays, local DMC operators.
1. NATURAL LANGUAGE COVERAGE
Classifies IN
- "Goa package for 4 days, flight + hotel"
- "honeymoon package Maldives"
- "5 day Kerala tour with car"
- "Bali package from Hyderabad"
- "Manali holiday package winter"
- "all-inclusive Andaman trip"
- "Rajasthan tour 7 days private cab"
- "family package Singapore"
- "europe 10 day group tour"
- "Bhutan all-inclusive package"
Classifies OUT — borderline NO
- "flight only to Goa" →
travel.book_flight - "hotel only in Manali" →
travel.book_hotel - "cruise" →
travel.book_cruise - "adventure trek package" →
travel.book_adventure_activity(single-activity); multi-day adventure tours go throughtravel.book_packagewith activity components - "visa for thailand" →
travel.book_visa_assistance - "currency exchange" →
travel.book_forex
MULTI-INTENT TRIGGERS
- "Goa package + travel insurance" →
travel.book_package+finance.buy_travel_insurance - "Europe trip + forex + visa" →
travel.book_package+travel.book_forex+travel.book_visa_assistance - "Singapore + adventure activities" →
travel.book_package+travel.book_adventure_activity
2. INPUT — TOMO → PROVIDER
{
"intent": "travel.book_package",
"intent_version": "v1.0.0",
"request_id": "req_pkg_2h4k_2026-05-14T10:00:00+05:30",
"user_session_id": "anon_user_token_or_uid",
"package_request": {
"origin_city": "Hyderabad",
"origin_country_code": "IN",
"destinations": [
{ "city": "Goa", "country_code": "IN", "nights": 4 }
],
"trip_kind": "leisure",
"trip_theme": "beach",
"trip_start_date": "2026-07-12",
"trip_end_date": "2026-07-16",
"duration_nights": 4,
"flexible_dates_days": 2,
"party": {
"adult_count": 2,
"children_with_age": [9, 5],
"infants_under_2": 0,
"rooms_required": 1
},
"components_required": ["flight", "hotel", "airport_transfer"],
"components_optional": ["sightseeing", "meals", "activity"],
"preferences": {
"hotel_star_min": 3,
"hotel_star_max": 4,
"hotel_meal_plan": "breakfast_only",
"flight_class": "economy",
"flight_direct_preferred": true,
"transfer_private_required": true,
"veg_meals_priority": true,
"jain_meals_required": false,
"wheelchair_accessibility_required": false,
"guided_local_tours_required": false,
"budget_band": "good",
"budget_max_inr_total": 120000,
"budget_max_inr_per_person": 60000,
"preferred_operators": []
}
},
"context": {
"user_locale": "en-IN",
"user_currency_pref": "INR",
"trust_signals": {
"is_repeat_customer": false,
"user_account_age_days": 312
}
}
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
intent |
string | REQUIRED, equals "travel.book_package" |
|
package_request.origin_city |
string | REQUIRED | User's departure city |
package_request.destinations |
array | REQUIRED, ≥1 | Multi-destination allowed |
package_request.destinations[].nights |
int | REQUIRED, ≥1 | Sum must match duration_nights |
package_request.trip_kind |
enum | REQUIRED, STRICT §6 | leisure / honeymoon / family / group / pilgrimage / wellness / business |
package_request.trip_theme |
enum | REQUIRED, STRICT §6 | beach / hills / heritage / wildlife / adventure / spiritual / city / multi |
package_request.duration_nights |
int | REQUIRED, ≥1, ≤45 | |
package_request.flexible_dates_days |
int | REQUIRED, 0-14 | |
package_request.party.adult_count |
int | REQUIRED, ≥1, ≤20 | |
package_request.party.rooms_required |
int | REQUIRED, ≥1 | Drives multi-room allotment |
package_request.components_required |
array |
REQUIRED, ≥1, STRICT §6 | Components that MUST be in any offered package |
package_request.components_optional |
array |
REQUIRED, STRICT §6, may be empty | Components partner may upgrade |
preferences.hotel_star_min/max |
int | REQUIRED, 1-5 | Star floor and ceiling |
preferences.hotel_meal_plan |
enum | REQUIRED, STRICT §6 | room_only / breakfast_only / half_board / full_board / all_inclusive |
preferences.flight_class |
enum | REQUIRED, STRICT §6 | economy / premium_economy / business / first |
preferences.transfer_private_required |
bool | REQUIRED | Private cab vs shuttle |
preferences.budget_max_inr_total |
int | REQUIRED, INR_INTEGER | Hard ceiling |
Anti-fabrication preamble: no paid placement, no commission-based operator ordering, no fabricated availability — every quoted component must be available at quote time. TOMO never holds the rail — money flows user → operator's regulated merchant account.
3. PROVIDER TOOLS
Tool 1: search_packages
PURPOSE: return packaged offerings matching destinations + dates + party
INPUT: §2 request body
OUTPUT: { packages: PackageOption[], result_token, expires_at }
SLA: p50 < 2000ms, p95 < 5000ms, p99 < 9000ms
RATE LIMIT: ≤ 1/sec per (user_session_id, partner)
RESULT SET: up to 25 packages
Tool 2: get_package_detail
PURPOSE: full day-by-day itinerary + hotel detail + inclusions/exclusions
INPUT: { package_id, request_id }
OUTPUT: PackageDetail (§5)
SLA: p95 < 2000ms
Tool 3: hold_package
PURPOSE: soft-lock components for 15 minutes
INPUT: { package_id, party, contact, request_id }
OUTPUT: { hold_id, hold_expires_at, total_locked_inr, breakdown }
SLA: p95 < 3500ms
Tool 4: confirm_package
PURPOSE: convert hold → confirmed booking; issue vouchers (flight PNR, hotel voucher, transfer voucher, activity vouchers)
INPUT: { hold_id, payment_method_id, request_id }
OUTPUT: ConfirmedPackage (§5)
SLA: p95 < 8000ms
IDEMPOTENCY: request_id
Tool 5: modify_package
PURPOSE: modify in-flight (date shift / room swap / activity add) — re-quote first
INPUT: { booking_id, modification_kind, modification_payload, request_id }
OUTPUT: { re_quote, requires_payment_diff_inr }
SLA: p95 < 4000ms
Tool 6: cancel_package
PURPOSE: cancel + compute refund per stage policy
INPUT: { booking_id, reason_code, request_id }
OUTPUT: CancellationResult (§5)
SLA: p95 < 4000ms
4. RESPONSE SHAPE
PackageOption
PackageOption:
package_id: { type: string, constraint: REQUIRED, opaque }
operator:
operator_id: { type: string, constraint: REQUIRED }
name: { type: string, constraint: REQUIRED }
tafi_iata_or_dot_registration: { type: string, constraint: REQUIRED, semantics: "TAFI / IATA / DoT registration; verifiable" }
years_in_business: { type: int, constraint: REQUIRED, ≥0 }
rating_average: { type: float, constraint: REQUIRED, 0.0-5.0 }
review_count: { type: int, constraint: REQUIRED, ≥0 }
title: { type: string, constraint: REQUIRED, semantics: "e.g. 'Goa Beach Escape – 4N/5D'" }
destinations: { type: array<string>, constraint: REQUIRED, ≥1 }
duration_nights: { type: int, constraint: REQUIRED, ≥1 }
duration_days: { type: int, constraint: REQUIRED, ≥1 }
components_included: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1 }
components_excluded_text: { type: array<string>, constraint: REQUIRED, may be empty, semantics: "Plain-language exclusions" }
hotel_summary:
hotel_count: { type: int, constraint: REQUIRED, ≥1 }
star_min: { type: int, constraint: REQUIRED, 1-5 }
star_max: { type: int, constraint: REQUIRED, 1-5 }
meal_plan: { type: enum, constraint: REQUIRED, STRICT §6 }
sample_property_names: { type: array<string>, constraint: REQUIRED, ≥1 }
flight_summary:
flights_included: { type: boolean, constraint: REQUIRED }
class: { type: enum, constraint: REQUIRED, STRICT §6 }
direct: { type: boolean, constraint: REQUIRED }
sample_airlines: { type: array<string>, constraint: REQUIRED, may be empty }
baggage_kg_check_in: { type: int, constraint: REQUIRED, ≥0 }
baggage_kg_cabin: { type: int, constraint: REQUIRED, ≥0 }
transfer_summary:
transfers_included: { type: boolean, constraint: REQUIRED }
private_cab: { type: boolean, constraint: REQUIRED }
pickup_drop_pairs_count: { type: int, constraint: REQUIRED, ≥0 }
activity_summary:
activities_included_count: { type: int, constraint: REQUIRED, ≥0 }
activities_sample_names: { type: array<string>, constraint: REQUIRED, may be empty }
pricing:
per_person_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
total_for_party_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "5% on tour packages per GST notification" }
tcs_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "TCS on overseas tours per Income Tax Act §206C(1G)" }
advance_payable_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
balance_due_date: { type: string, constraint: REQUIRED, ISO_DATE }
cancellation_policy:
type: array<object>
constraint: REQUIRED, may NOT be empty
shape:
days_before_departure_min: { type: int, constraint: REQUIRED, ≥0 }
days_before_departure_max: { type: int, constraint: REQUIRED, ≥0 }
cancellation_charge_pct: { type: int, constraint: REQUIRED, 0-100 }
ttbs_meta:
package_kind: { type: enum, constraint: REQUIRED, STRICT §6, values: [fixed_departure_group, private_customized, fully_independent_traveler, escorted_group] }
average_walking_required_km_per_day: { type: float, constraint: REQUIRED, ≥0 }
kid_friendly: { type: boolean, constraint: REQUIRED }
senior_friendly: { type: boolean, constraint: REQUIRED }
partner_reference:
source: { type: string, constraint: REQUIRED }
deeplink: { type: string, constraint: REQUIRED, HTTPS URL }
PackageDetail
PackageDetail:
package_id: { type: string, constraint: REQUIRED }
itinerary_days:
type: array<object>
constraint: REQUIRED, length = duration_days
shape:
day_number: { type: int, constraint: REQUIRED, ≥1 }
title: { type: string, constraint: REQUIRED }
narrative: { type: string, constraint: REQUIRED, semantics: "Plain-language day plan, no marketing fluff" }
transfers: { type: array<string>, constraint: REQUIRED, may be empty }
activities: { type: array<string>, constraint: REQUIRED, may be empty }
meals_included: { type: array<enum>, constraint: REQUIRED, STRICT §6, may be empty, values: [breakfast, lunch, dinner, snacks] }
hotel_for_night: { type: string, constraint: REQUIRED, semantics: "Empty when last day no-stay" }
full_inclusions_text: { type: array<string>, constraint: REQUIRED, ≥1 }
full_exclusions_text: { type: array<string>, constraint: REQUIRED, ≥1 }
what_to_pack_text: { type: array<string>, constraint: REQUIRED, may be empty }
cancellation_policy_full_text: { type: string, constraint: REQUIRED }
visa_required: { type: boolean, constraint: REQUIRED }
vaccinations_recommended: { type: array<string>, constraint: REQUIRED, may be empty }
ConfirmedPackage
ConfirmedPackage:
booking_id: { type: string, constraint: REQUIRED, immutable }
operator_reference: { type: string, constraint: REQUIRED }
total_paid_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
balance_due_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
vouchers:
flight_pnrs: { type: array<string>, constraint: REQUIRED, may be empty }
hotel_vouchers: { type: array<object>, constraint: REQUIRED, may be empty, shape: { hotel_name, voucher_url, check_in, check_out } }
transfer_vouchers: { type: array<object>, constraint: REQUIRED, may be empty }
activity_vouchers: { type: array<object>, constraint: REQUIRED, may be empty }
itinerary_pdf_url: { type: string, constraint: REQUIRED, HTTPS URL }
emergency_contacts:
operator_24_7_phone: { type: string, constraint: REQUIRED, E.164 }
operator_email: { type: string, constraint: REQUIRED }
local_dmc_phone: { type: string, constraint: REQUIRED, E.164, semantics: "Destination Management Company contact in destination country" }
insurance:
included: { type: boolean, constraint: REQUIRED }
policy_number: { type: string, constraint: REQUIRED, semantics: "Empty when included=false" }
insurer_name: { type: string, constraint: REQUIRED, semantics: "Empty when included=false" }
CancellationResult
CancellationResult:
booking_id: { type: string, constraint: REQUIRED }
cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
refund_method: { type: enum, constraint: REQUIRED, STRICT §6, values: [original_payment, bank_transfer] }
refund_eta_days: { type: int, constraint: REQUIRED, 0-45, semantics: "International cancellations may take 30+ days" }
cancellation_charge_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
non_refundable_components: { type: array<string>, constraint: REQUIRED, may be empty, semantics: "e.g. flight non-refundable fare" }
FORBIDDEN FIELDS
paid_placement_score,ad_bid,sponsored_rank,featured_package,commission_rankeditor_pick,tomo_recommended,top_choiceurgency_text("Only 2 packages left!") unless legitimate inventory floorai_generated_hotel_photo,ai_generated_destination_photoinflated_review_count,rounded_rating,fake_testimonialcommission_padded_per_person_inr,kickback_from_hotel_chainunpublished_inclusion(must match itinerary)last_minute_price_jumpafter hold expires + 5 minutes
5. CONTROLLED VOCABULARIES
package_request.trip_kind / pricing.trip_kind:
values: [leisure, honeymoon, family, group, pilgrimage, wellness, business, educational, adventure_dominant]
package_request.trip_theme:
values: [beach, hills, heritage, wildlife, adventure, spiritual, city, multi, desert, backwater, snow]
components_required / components_included / components_optional:
values:
flight, train, bus, hotel, homestay, resort, cruise_segment,
airport_transfer, intercity_transfer, sightseeing, meals, activity,
guide, visa_assist, travel_insurance, lounge_access, sim_card_or_esim
hotel_meal_plan:
values: [room_only, breakfast_only, half_board, full_board, all_inclusive]
flight_summary.class:
values: [economy, premium_economy, business, first]
ttbs_meta.package_kind:
values: [fixed_departure_group, private_customized, fully_independent_traveler, escorted_group]
PackageDetail.itinerary_days.meals_included:
values: [breakfast, lunch, dinner, snacks]
CancellationResult.refund_method:
values: [original_payment, bank_transfer]
6. TTBS DIMENSIONS
TIME (weight = 0.20):
signals_used:
- flight_summary.direct
- duration_nights vs requested
- daily walking_km within band
weighting:
direct_flights: 0.40
duration_fit: 0.30
pacing: 0.30
user_band_handling:
fast: prefer fewer days same coverage
balanced: standard
flexible: longer-stay options OK
TASTE (weight = 0.30):
signals_used:
- hotel_star_min/max fit
- meal_plan match
- flight_class fit
- private_cab fit
- DNA: prior trips to similar themes
- ttbs_meta.package_kind preference (e.g. "private_customized" for DNA-honeymoon users)
weighting:
hotel_fit: 0.25
meal_plan_fit: 0.15
flight_class_fit: 0.15
transfer_fit: 0.10
dna_theme: 0.20
package_kind_fit: 0.15
BUDGET (weight = 0.25):
signals_used:
- pricing.per_person_inr
- pricing.total_for_party_inr
- advance_payable_inr (lower preferred for cash-flow)
weighting:
per_person: 0.50
total: 0.40
advance_fit: 0.10
user_band_handling:
ok: cheapest passing taste floor
good: balanced
great: ★4-5 hotels, business class, private transfer
SAFETY (weight = 0.25):
signals_used:
- operator.tafi_iata_or_dot_registration verified
- operator.years_in_business ≥3 floor
- rating_average ≥4.0 floor
- emergency_contacts complete
- insurance.included for international
weighting:
operator_registered: 0.35
years_business: 0.20
rating_floor: 0.20
emergency_contacts: 0.15
insurance_compliance: 0.10
user_band_handling:
fast: relax years floor to 1
balanced: floor 3
great: floor 7 + IATA + TAFI dual registration
Locked weights: time 0.20 / taste 0.30 / budget 0.25 / safety 0.25. Taste leads — package selection is fundamentally an experience-fit problem.
7. COMPLETION CONTRACT
POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
"intent": "travel.book_package",
"external_id": "<booking_id>",
"request_id": "<request_id>",
"amount_inr": 4800, // NET partner commission only (typically 4-8% of package total)
"gst_inr": 864,
"tips_inr": 0,
"pass_through_inr": 65000, // remitted to hotels / airlines / transfers
"closed_at": "2026-06-10T17:30:00+05:30",
"status": "completed",
"destinations": ["Goa"],
"duration_nights": 4,
"package_kind": "private_customized",
"party_size": 4
}
Pass-through covers flight + hotel + transfer + activity remittance. Partner's NET margin sits in amount_inr. TOMO charges 10% × amount_inr. HMAC-SHA256, 5-min replay.
8. WIDGET
PackageListingWidget (planned). Interim: ListingsWidget + ItineraryExpander.
Field mapping:
- title → header
- destinations → "→ Goa" chip
- duration_nights → "4N/5D"
- components_included (top 4) → component chips
- hotel_summary.star_min/max → ★3-4 pill
- flight_summary.direct → "Direct flight" pill
- pricing.per_person_inr → big price
- cancellation_policy → "Free cancel till X" pill (when applicable)
- rating_average + review_count → operator score
- ttbs_meta.package_kind → "Private customised" / "Group" pill
9. CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
| search_packages | 120s | Component availability moves |
| get_package_detail | 60s | Detail mostly stable |
| hold_package | NO CACHE | Stateful lock |
| confirm_package | NO CACHE | Idempotent by request_id |
| modify_package | NO CACHE | — |
| cancel_package | NO CACHE | — |
| Operator static (registration, years) | 24h |
10. ERROR CODES
| Code | HTTP | Meaning | Retry |
|---|---|---|---|
INVALID_REQUEST |
400 | Malformed | No |
RATE_LIMITED |
429 | Throttle | 1, 2s |
INTERNAL_ERROR |
500 | Partner failure | 2, exp |
SIGNATURE_INVALID |
401 | HMAC fail | No |
NO_PACKAGE_MATCH |
200 (empty) | No package fits filters | n/a |
COMPONENT_UNAVAILABLE |
422 | Flight / hotel / activity unavailable for dates | No |
HOLD_EXPIRED |
410 | Lock elapsed | UI re-holds |
PRICE_CHANGED_AT_CONFIRM |
422 | Component price shifted post-hold | No (UI re-quotes) |
PAYMENT_FAILED |
402 | Gateway declined | No |
OPERATOR_REGISTRATION_INVALID |
422 | TAFI/IATA/DoT lookup failed | No |
VISA_REQUIRED_NOT_HELD |
422 | International trip, visa not in hand | No (UI route to visa intent) |
CANCELLED_PAST_POLICY |
410 | Past full-charge stage | No |
MODIFICATION_REJECTED |
422 | Component non-modifiable | No |
INSURANCE_REJECTION |
422 | Travel insurance rejected at provider | No |
11. SANDBOX → PRODUCTION CHECKLIST
[ ] All six tools implemented
[ ] At least 100 packages across 15+ destinations
[ ] At least 25 operators
[ ] Every operator's TAFI / IATA / DoT registration verifiable
[ ] Hotel inclusions match contracted rates (sample audit)
[ ] Sample property names + addresses match real verified hotels
[ ] Flight allotment confirmed with airlines (test on 5 packages)
[ ] Itinerary day-by-day matches what's delivered
[ ] Cancellation policy table tested end-to-end at multiple stages
[ ] HMAC signing verified on test CPC webhook
[ ] amount_inr in CPC is partner COMMISSION only; pass_through_inr covers remittance to component providers
[ ] All controlled vocabularies respected
[ ] No forbidden fields anywhere
[ ] SLA p95 met (100-call sandbox run)
[ ] TCS handling tested for overseas tours (§206C(1G))
[ ] Privacy policy + grievance officer + IATA / TAFI registration uploaded
[ ] Customer support: 24×7 holidays helpline + emergency escalation
[ ] DMC contacts for international destinations verified
[ ] Travel-insurance integration tested when included=true
12. ANTI-FABRICATION RULES
RULE 1: Operator's TAFI / IATA / DoT registration verifiable and current.
Expired or fake = ingest reject + suspension.
RULE 2: No paid_placement / sponsored_rank / featured_package. Ranking
derives from TTBS only.
RULE 3: Hotel star ratings must match either Hotelstars Union (international)
or Ministry of Tourism India classification. Self-styled "5-star"
without classification = ingest reject.
RULE 4: Inclusions list must match what's delivered. Partner promising
"private cab" then giving shuttle = customer-harm + suspension.
RULE 5: rating_average and review_count from completed packages only;
no signup interest or refunded bookings.
RULE 6: TCS computed correctly per Income Tax Act §206C(1G). Under-collection
or non-disclosure = regulatory breach + suspension.
RULE 7: Cancellation policy publishes before payment; mid-process policy
change = breach.
RULE 8: For international, travel insurance integration mandatory (operator
offers; user can decline only with explicit refusal). Operators
hiding insurance = ingest reject.
RULE 9: AI-generated destination / hotel photos forbidden. Real photos with
capture-date metadata required.
RULE 10: Itinerary narrative must be factually accurate. Hallucinated
monuments, fake landmark names, non-existent activities = breach.
RULE 11: amount_inr is partner's NET commission. Hotel / flight / transfer
remittance is pass_through_inr. Inflated commission base = audit.
RULE 12: Sample property names in PackageOption must be among the
actually-contracted hotels (deliverable choice within that set);
"indicative only" cannot hide a worse-than-shown delivery.
RULE 13: Last-minute price jumps post-hold (>5%) are customer-harm and
require the operator to honor the held price.
RULE 14: Information-completeness score (hidden rank factor weight 0.10)
rewards full §4 shape.
RULE 15: For pilgrimage packages, religious-site etiquette guidance must
be included in what_to_pack_text. Skipping = customer-harm for
first-time pilgrims.
VERSION HISTORY
v1.0.0 — 2026-05-14 — Initial spec. TAFI/IATA/DoT-registered operators only.
Hotel star floor per Hotelstars Union or Ministry of
Tourism classification. TCS §206C(1G) compliance.
Net-commission base. Itinerary day-by-day required.
Emergency 24×7 contact + local DMC contact mandatory
for international.