T
TOMO
Developer Docs
BETA These docs are under partner review. Some features described are roadmap items, not yet shipped. Verify against your sandbox before relying on any contract.
● DRAFTv1.0.0travel.book_package

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 through travel.book_package with 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_rank
  • editor_pick, tomo_recommended, top_choice
  • urgency_text ("Only 2 packages left!") unless legitimate inventory floor
  • ai_generated_hotel_photo, ai_generated_destination_photo
  • inflated_review_count, rounded_rating, fake_testimonial
  • commission_padded_per_person_inr, kickback_from_hotel_chain
  • unpublished_inclusion (must match itinerary)
  • last_minute_price_jump after 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.