travel.book_tour_guide — Full Intent Specification
INTENT NAMESPACE: travel
INTENT NAME: book_tour_guide
FULL ID: travel.book_tour_guide
VERSION: v1.0.0
STATUS: draft
TTBS WEIGHTS: time 0.15 · taste 0.40 · budget 0.20 · safety 0.25
LAST UPDATED: 2026-05-14
User books a licensed local tour guide for a heritage walk, city tour, museum tour, food tour, photography tour, or destination-specific guided experience. Distinct from travel.book_package (multi-component bundles), travel.book_adventure_activity (specific physical activity), and from generic translation services. Partner exemplars: GetYourGuide, Viator, Klook, Headout, Tripoto, Withlocals, Ministry of Tourism RLG (Regional Level Guide) registries, local guide cooperatives.
1. NATURAL LANGUAGE COVERAGE
Classifies IN
- "local guide for Hampi tour"
- "Old City Hyderabad heritage walk"
- "Mumbai food tour guide"
- "Delhi history tour with a licensed guide"
- "private guide Taj Mahal"
- "Goa Portuguese heritage walk"
- "photography tour Varanasi"
- "Sri Lanka local guide"
- "museum tour Bangalore"
- "wine tour Nashik with guide"
Classifies OUT — borderline NO
- "package tour" →
travel.book_package - "trek with guide" →
travel.book_adventure_activity(when activity dominates) - "language translator" — out of scope v1
- "audio guide rental" — out of scope v1
- "rental car with driver" →
mobility.book_chauffeur_hourly - "city tour bus" →
travel.book_bus(when bus is the booked item) or this intent (when guide is the booked item)
MULTI-INTENT TRIGGERS
- "Hampi tour + hotel + train" →
travel.book_tour_guide+travel.book_hotel+travel.book_train - "Heritage walk + lunch reservation" →
travel.book_tour_guide+food.book_dine_in - "Photography tour + early-morning cab" →
travel.book_tour_guide+mobility.book_chauffeur_hourly
2. INPUT — TOMO → PROVIDER
{
"intent": "travel.book_tour_guide",
"intent_version": "v1.0.0",
"request_id": "req_grd_2h4k_2026-05-14T10:00:00+05:30",
"user_session_id": "anon_user_token_or_uid",
"tour_request": {
"destination_city": "Hampi",
"destination_state": "Karnataka",
"country_code": "IN",
"tour_kind": "heritage_walk",
"tour_themes": ["history", "architecture"],
"tour_date": "2026-08-12",
"start_time_window": {
"start": "2026-08-12T07:00:00+05:30",
"end": "2026-08-12T10:00:00+05:30"
},
"duration_hours": 4,
"party": {
"adult_count": 2,
"children_with_age": [],
"seniors_count": 0,
"disabled_count": 0
},
"preferred_language": "en",
"language_alternates": ["hi"],
"private_tour_required": true,
"transport_included_required": false,
"entry_tickets_included_required": true,
"mobility_walking_floor_km": 3,
"mobility_walking_ceiling_km": 8,
"guide_gender_preference": "no_preference",
"budget_band": "good",
"budget_max_inr_total": 4500
},
"context": {
"user_locale": "en-IN",
"user_currency_pref": "INR",
"trip_purpose": "leisure",
"trust_signals": {
"is_repeat_customer": false,
"user_account_age_days": 312
}
}
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
intent |
string | REQUIRED, equals "travel.book_tour_guide" |
|
tour_request.destination_city |
string | REQUIRED | Validated against partner's coverage |
tour_request.tour_kind |
enum | REQUIRED, STRICT §6 | heritage_walk / city_tour / food_tour / museum_tour / photography / wine / pilgrimage / hidden_gems / family_friendly |
tour_request.tour_themes |
array |
REQUIRED, STRICT §6, ≥1 | |
tour_request.tour_date |
ISO_DATE | REQUIRED | Up to 90 days advance |
tour_request.start_time_window |
object | REQUIRED | start, end ISO_DATETIME |
tour_request.duration_hours |
int | REQUIRED, 1-12 | |
tour_request.party.adult_count |
int | REQUIRED, ≥1, ≤25 | Above 25 = group tour separate intent |
tour_request.party.disabled_count |
int | REQUIRED | Triggers accessibility filter |
tour_request.preferred_language |
enum | REQUIRED, STRICT §6 | |
tour_request.private_tour_required |
bool | REQUIRED | Hard filter on shared vs private |
tour_request.transport_included_required |
bool | REQUIRED | Vehicle + guide combo when true |
tour_request.entry_tickets_included_required |
bool | REQUIRED | Drives total quote |
tour_request.mobility_walking_floor_km |
float | REQUIRED, ≥0 | Lower bound of walking |
tour_request.mobility_walking_ceiling_km |
float | REQUIRED, ≥0 | Upper bound; hard filter |
tour_request.guide_gender_preference |
enum | REQUIRED, STRICT §6 | male / female / no_preference |
Anti-fabrication preamble: no paid placement; guide ordering is TTBS-only. Government-licensed guides (Ministry of Tourism RLG / Regional Level Guide / archaeological-site-specific) MUST be flagged as such. Unlicensed self-styled "guides" at protected monuments = ingest reject.
3. PROVIDER TOOLS
Tool 1: search_guides
PURPOSE: return guides matching destination + date + language + theme
INPUT: §2 request body
OUTPUT: { guides: GuideOption[], result_token, expires_at }
SLA: p50 < 800ms, p95 < 2500ms, p99 < 4500ms
RATE LIMIT: ≤ 1/sec per (user_session_id, partner)
RESULT SET: up to 20 guides
Tool 2: get_guide_detail
PURPOSE: full guide bio + license + sample tour photos + reviews
INPUT: { guide_id, request_id }
OUTPUT: GuideDetail (§5)
SLA: p95 < 1500ms
Tool 3: hold_tour_slot
PURPOSE: soft-lock for 10 minutes
INPUT: { guide_id, tour_date, start_time, duration_hours, request_id }
OUTPUT: { hold_id, hold_expires_at, locked_price_inr }
SLA: p95 < 1200ms
Tool 4: confirm_tour
PURPOSE: confirm booking + issue voucher + e-tickets when applicable
INPUT: { hold_id, party, contact, payment_method_id, request_id }
OUTPUT: ConfirmedTour (§5)
SLA: p95 < 3500ms
IDEMPOTENCY: request_id
Tool 5: cancel_tour
PURPOSE: cancel + refund per policy
INPUT: { booking_id, reason_code, request_id }
OUTPUT: CancellationResult (§5)
SLA: p95 < 2500ms
4. RESPONSE SHAPE
GuideOption
GuideOption:
guide_id: { type: string, constraint: REQUIRED, opaque }
display_name: { type: string, constraint: REQUIRED }
license:
license_kind: { type: enum, constraint: REQUIRED, STRICT §6, values: [moT_RLG, asi_site_specific, state_tourism_licensed, unlicensed_local_expert, international_certified] }
license_number: { type: string, constraint: REQUIRED, semantics: "Empty when license_kind=unlicensed_local_expert" }
license_valid_until: { type: string, constraint: REQUIRED, ISO_DATE, semantics: "Empty when unlicensed" }
issuing_authority: { type: string, constraint: REQUIRED, semantics: "Empty when unlicensed" }
bio_short: { type: string, constraint: REQUIRED, max 280 chars }
years_guiding: { type: int, constraint: REQUIRED, ≥0 }
tours_completed_count: { type: int, constraint: REQUIRED, ≥0 }
languages_spoken: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1 }
fluency_levels:
type: array<object>
constraint: REQUIRED
shape:
language: { type: enum, constraint: REQUIRED, STRICT §6 }
level: { type: enum, constraint: REQUIRED, STRICT §6, values: [conversational, professional, native] }
specialties: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1 }
themes_covered: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1 }
duration_offered_hours:
min: { type: float, constraint: REQUIRED, ≥0.5 }
max: { type: float, constraint: REQUIRED, ≤12 }
pricing:
base_per_hour_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
flat_for_duration_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "Calculated for requested duration" }
private_premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "Add'l for private tour" }
entry_tickets_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "Site entry fees pass-through" }
transport_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "Vehicle pass-through when included" }
gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
total_for_party_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
inclusions: { type: array<enum>, constraint: REQUIRED, STRICT §6, may be empty }
exclusions_text: { type: array<string>, constraint: REQUIRED, may be empty }
walking_distance_estimate_km: { type: float, constraint: REQUIRED, ≥0 }
walking_pace_kmph: { type: float, constraint: REQUIRED, ≥0 }
rest_stops_planned: { type: int, constraint: REQUIRED, ≥0 }
wheelchair_accessible: { type: boolean, constraint: REQUIRED }
kid_friendly: { type: boolean, constraint: REQUIRED }
senior_friendly: { type: boolean, constraint: REQUIRED }
meeting_point:
name: { type: string, constraint: REQUIRED }
address: { type: string, constraint: REQUIRED }
lat: { type: float, constraint: REQUIRED }
lng: { type: float, constraint: REQUIRED }
landmark: { type: string, constraint: REQUIRED }
cancellation_policy:
type: array<object>
constraint: REQUIRED, may NOT be empty
shape:
hours_before_start_min: { type: int, constraint: REQUIRED }
hours_before_start_max: { type: int, constraint: REQUIRED }
refund_pct: { type: int, constraint: REQUIRED, 0-100 }
rating:
average_score: { type: float, constraint: REQUIRED, 0.0-5.0 }
review_count: { type: int, constraint: REQUIRED, ≥0 }
response_time_minutes_median: { type: int, constraint: REQUIRED, ≥0 }
guide_gender: { type: enum, constraint: REQUIRED, STRICT §6, values: [male, female, other, prefer_not_to_say] }
partner_reference:
source: { type: string, constraint: REQUIRED }
deeplink: { type: string, constraint: REQUIRED, HTTPS URL }
GuideDetail
GuideDetail:
guide_id: { type: string, constraint: REQUIRED }
bio_long: { type: string, constraint: REQUIRED }
sample_itineraries:
type: array<object>
constraint: REQUIRED, ≥1
shape:
title: { type: string, constraint: REQUIRED }
duration_hours: { type: float, constraint: REQUIRED }
stops_in_order: { type: array<string>, constraint: REQUIRED, ≥1 }
sample_tour_photos: { type: array<string>, constraint: REQUIRED, ≥3, HTTPS URLs }
recent_reviews:
type: array<object>
constraint: REQUIRED, ≥3
shape:
reviewer_initials: { type: string, constraint: REQUIRED, semantics: "First initial + last initial only" }
review_text_short: { type: string, constraint: REQUIRED, max 280 chars }
rating: { type: float, constraint: REQUIRED, 0.0-5.0 }
tour_date: { type: string, constraint: REQUIRED, ISO_DATE }
verified_booking: { type: boolean, constraint: REQUIRED }
ConfirmedTour
ConfirmedTour:
booking_id: { type: string, constraint: REQUIRED, immutable }
guide_id: { type: string, constraint: REQUIRED }
guide_name: { type: string, constraint: REQUIRED }
tour_date: { type: string, constraint: REQUIRED, ISO_DATE }
starts_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
ends_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
meeting_point: { type: object, constraint: REQUIRED, semantics: "Same shape as GuideOption.meeting_point" }
total_paid_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
voucher_url: { type: string, constraint: REQUIRED, HTTPS URL }
e_tickets:
type: array<object>
constraint: REQUIRED, may be empty
shape:
site_name: { type: string, constraint: REQUIRED }
ticket_url: { type: string, constraint: REQUIRED, HTTPS URL }
valid_for: { type: string, constraint: REQUIRED, ISO_DATE }
guide_phone_at_t_minus_2h: { type: string, constraint: REQUIRED, E.164, semantics: "Empty until 2h before" }
emergency_contact_phone: { type: string, constraint: REQUIRED, E.164 }
cancellation_window:
full_refund_until: { type: string, constraint: REQUIRED, ISO_DATETIME }
last_cancellable_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
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-10 }
non_refundable_components: { type: array<string>, constraint: REQUIRED, may be empty }
FORBIDDEN FIELDS
paid_placement_score,ad_bid,sponsored_rank,featured_guideeditor_pick,tomo_recommended,top_choicefake_review_block,inflated_tours_completed_countai_generated_guide_photo,ai_generated_tour_photo(real photos required)unlicensed_at_protected_site— ingest reject for monuments with ASI/state-licence requirementscommission_padded_per_hour_inr,kickback_from_site_authorityclient_real_namesin reviews — initials only
5. CONTROLLED VOCABULARIES
tour_request.tour_kind:
values:
heritage_walk: "Historical / architectural walking tour"
city_tour: "General city orientation"
food_tour: "Cuisine + eateries tasting"
museum_tour: "Museum-focused"
photography: "Photography composition tour"
wine: "Vineyard / winery tour"
pilgrimage: "Religious / spiritual site tour"
hidden_gems: "Off-the-beaten-track local"
family_friendly: "Kid-friendly pace + content"
pub_crawl: "Adult evening tour"
wildlife_birding: "Bird / wildlife in urban / peri-urban"
tour_request.tour_themes / themes_covered / specialties:
values:
history, architecture, religion, art, food, photography, music,
literature, film, fashion, crafts, markets, street_culture,
colonial, mughal, dravidian, indo_islamic, mauryan, colonial_portuguese,
british_raj, modern_india, freedom_movement, women_in_history,
science_industry, gandhian, ambedkar_buddhist, sufism, vaishnav,
shaiva, jain_heritage, sikh_heritage
tour_request.preferred_language / languages_spoken:
values: [en, hi, te, ta, kn, ml, mr, bn, gu, pa, or, as, ur, fr, es, de, it, ja, zh, ko, ru, ar, pt]
fluency_levels.level:
values: [conversational, professional, native]
GuideOption.license.license_kind:
values:
moT_RLG: "Ministry of Tourism Regional Level Guide"
asi_site_specific: "Archaeological Survey of India site-specific guide"
state_tourism_licensed: "State tourism department licensed guide"
unlicensed_local_expert: "Local expert; not government-licensed (cannot operate at protected sites)"
international_certified: "International association certified (WFTGA, etc.)"
inclusions:
values:
entry_tickets, transport_pickup, transport_drop, transport_intra_tour,
bottled_water, snacks, meal, headset_audio, museum_skip_line, fast_track_entry
guide_gender / guide_gender_preference:
values: [male, female, other, prefer_not_to_say, no_preference]
CancellationResult.refund_method:
values: [original_payment, bank_transfer]
6. TTBS DIMENSIONS
TIME (weight = 0.15):
signals_used:
- earliest tour slot vs start_time_window
- response_time_minutes_median
weighting:
slot_fit: 0.65
response_time: 0.35
user_band_handling:
fast: response time floor 60min
balanced: standard
flexible: 12h response acceptable for cheaper guide
TASTE (weight = 0.40):
signals_used:
- languages_spoken / fluency_levels match
- tour_kind specialty match
- themes_covered overlap with tour_themes
- DNA repeat guide / similar tour history
- guide_gender_preference match
- private_tour_required match (HARD filter when TRUE)
weighting:
language_fluency: 0.25
tour_kind_match: 0.20
themes_overlap: 0.20
dna_repeat: 0.15
gender_preference: 0.10
private_match: 0.10
BUDGET (weight = 0.20):
signals_used:
- pricing.total_for_party_inr
- private_premium_inr
weighting:
total: 0.85
private_premium: 0.15
user_band_handling:
ok: cheapest passing taste + safety floors
good: balanced
great: senior licensed guide regardless of fee
SAFETY (weight = 0.25):
signals_used:
- license.license_kind (HARD filter: protected sites require moT_RLG or asi_site_specific)
- license.license_valid_until ≥ tour_date
- years_guiding ≥3 floor
- tours_completed_count ≥50 floor
- rating.average_score ≥4.0 floor
- walking_distance vs user's mobility_walking_floor/ceiling
- kid_friendly / senior_friendly / wheelchair_accessible when applicable
weighting:
license_match: 0.30
years_floor: 0.15
tours_count: 0.15
rating_floor: 0.20
mobility_fit: 0.20
user_band_handling:
fast: relax tours_count to 20
balanced: floor 50
great: floor 200 + moT_RLG license
Locked weights: time 0.15 / taste 0.40 / budget 0.20 / safety 0.25. Taste dominates — guide quality + language + thematic resonance defines the experience.
7. COMPLETION CONTRACT
POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
"intent": "travel.book_tour_guide",
"external_id": "<booking_id>",
"request_id": "<request_id>",
"amount_inr": 300, // NET partner commission only
"gst_inr": 54,
"tips_inr": 0,
"pass_through_inr": 3500, // remitted to guide + site entry tickets
"closed_at": "2026-08-12T11:00:00+05:30",
"status": "completed",
"tour_kind": "heritage_walk",
"license_kind": "moT_RLG",
"duration_hours": 4,
"party_size": 2
}
User pays full fee + tickets + transport (when included). Partner's NET commission is amount_inr. Guide fee + tickets + transport remittance is pass_through_inr. TOMO charges 10% × amount_inr. HMAC-SHA256.
8. WIDGET
GuideListingWidget.
Field mapping:
- display_name + license_kind pill (e.g. "MoT-Licensed Guide")
- languages_spoken intersection with user pref → language chip
- years_guiding + tours_completed_count → "12 yrs · 420 tours"
- specialties (top 3) → chip row
- pricing.total_for_party_inr → big price
- duration_offered_hours → "4-hour walk"
- walking_distance_estimate_km → "~5 km walking" pill
- rating.average_score + review_count → star pill
- wheelchair_accessible / kid_friendly / senior_friendly → accessibility pills (when applicable)
- private_tour_required match → "Private tour" badge
9. CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
| search_guides | 120s | Calendar volatile |
| get_guide_detail | 24h | Stable profile |
| hold_tour_slot | NO CACHE | Stateful lock |
| confirm_tour | NO CACHE | Idempotent |
| cancel_tour | NO CACHE | — |
| License verification (MoT / ASI / state) | 7d | Issuers update periodically |
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_GUIDE_MATCH |
200 (empty) | No guide fits filters | n/a |
SLOT_TAKEN |
409 | Hold race-lost | UI re-queries |
HOLD_EXPIRED |
410 | Hold elapsed | UI re-holds |
LICENSE_EXPIRED |
422 | Guide license lapsed | No |
PROTECTED_SITE_REQUIRES_LICENSED |
422 | Site needs licensed guide; unlicensed guide returned | No |
LANGUAGE_NOT_FLUENT |
422 | No professional-level fluency in required language | No (UI re-asks) |
MOBILITY_OUT_OF_RANGE |
422 | Walking distance outside floor/ceiling | No |
PAYMENT_FAILED |
402 | Gateway declined | No |
WEATHER_CANCELLATION |
422 | Severe weather; partner cancels with refund | No |
SITE_CLOSED |
422 | Monument closed on date | No |
CANCELLED_TOO_LATE |
410 | Past last_cancellable_at | No |
GUIDE_NO_SHOW |
200 (advisory) | Auto-full-refund + investigation | n/a |
11. SANDBOX → PRODUCTION CHECKLIST
[ ] All five tools implemented
[ ] At least 50 guides across 15+ destinations
[ ] All moT_RLG / asi_site_specific licenses verifiable on issuing authority's records
[ ] Protected-site hard filter enforced (sample audit on 5 ASI sites)
[ ] Language fluency claims sample-audited (random call with guide)
[ ] tours_completed_count defensible (partner produces past file references)
[ ] All reviews from completed bookings; verified_booking=true for each
[ ] Walking-distance estimates measured from sample-tour GPS traces
[ ] Cancellation policy publishes before payment
[ ] HMAC signing verified on test CPC webhook
[ ] amount_inr in CPC is COMMISSION (NET); pass_through_inr = guide fee + tickets + transport
[ ] All controlled vocabularies respected
[ ] No forbidden fields anywhere
[ ] SLA p95 met
[ ] Privacy policy + grievance officer contact uploaded
[ ] Customer support: dedicated guide-tours helpline + email
[ ] Real photos required (sample audit catches AI-generated headshots)
[ ] Guide no-show policy tested with auto-full-refund
12. ANTI-FABRICATION RULES
RULE 1: At protected monuments (ASI / state-protected), only licensed
guides (moT_RLG or asi_site_specific) may be listed. Unlicensed
listings at protected sites = customer-harm + regulatory breach.
RULE 2: License numbers verifiable on Ministry of Tourism's RLG database
or ASI's panel list. Re-checked weekly.
RULE 3: No paid_placement, ad_bid, sponsored_rank, featured_guide.
Ordering is TTBS-only.
RULE 4: rating.average_score must be unrounded from completed bookings
in past 12 months. review_count from completed bookings only.
RULE 5: Reviews must use reviewer_initials only; identifiable client names
forbidden.
RULE 6: years_guiding = years since first license/professional engagement,
not "years interested in history."
RULE 7: tours_completed_count defensible (CPC ledger shadow score).
RULE 8: Languages with claimed professional fluency must withstand a
sample audio call. Partners listing "Japanese fluent" without
capability = customer-harm + suspension.
RULE 9: AI-generated guide photos / fake guide profiles = ingest reject +
suspension. Government-ID verified real photo at intake.
RULE 10: amount_inr is partner COMMISSION only. Guide fee + entry tickets
+ transport are pass_through_inr. Inflated commission base = audit.
RULE 11: Walking-distance estimates must come from real tour traces, not
partner-marketing-team guesses. ±20% deviation between estimate
and actual = customer-harm flag.
RULE 12: Site-entry tickets passed through must match the published
government rates (ASI / state). Padded ticket pricing = breach.
RULE 13: Guide-no-show triggers automatic full refund within 24h
regardless of policy windows; partner cannot block.
RULE 14: Information-completeness score (hidden rank factor weight 0.10)
rewards full §4 shape.
RULE 15: Wheelchair-accessible / kid-friendly / senior-friendly flags
must reflect verifiable route inspection. Partners marking
accessibility falsely = customer-harm + suspension.
VERSION HISTORY
v1.0.0 — 2026-05-14 — Initial spec. MoT-RLG / ASI / state-licensed guides
only at protected sites. Real photos + verified
license at intake. Net-commission base.
Walking-distance estimates from real GPS traces.
Site-entry ticket pass-through at government rates.