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.0lifestyle.book_gym_session

lifestyle.book_gym_session — Full Intent Specification

INTENT NAMESPACE: lifestyle
INTENT NAME:      book_gym_session
FULL ID:          lifestyle.book_gym_session
VERSION:          v1.0.0
STATUS:           draft
LAST UPDATED:     2026-05-10
TTBS WEIGHTS:     time 0.30 · taste 0.30 · budget 0.30 · safety 0.10

Drop-in / pay-per-session gym visit. Inherits the slot-based service-provider shape from lifestyle.book_salon: every salon-shape field has a gym equivalent (gym instead of salon, equipment instead of services, trainer-on-floor instead of stylist). Distinct: (a) gym_kind enum (full_service_chain / boutique / co-working_gym / hotel_gym); (b) equipment_categories[] and class_kinds_offered; (c) trainer_assistance_kind (floor_walker / on_demand / paid_pt_session); (d) peak_hours data drives time signals; (e) shower + locker + parking are first-class amenities.

Inheritance contract: every field in lifestyle.book_salon §2/§4/§5 except cosmetics-specific (skin_kind, hair_kind, products) is REQUIRED here too — adapted to the gym domain.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "drop-in gym session today"
  • "1-day gym pass"
  • "Cult.fit gym near me"
  • "pay per use gym"
  • "gym for travel day"
  • "weight training session today"
  • "cardio gym 1 hour"
  • "Anytime Fitness drop-in"
  • "boutique gym near office"

Classifies OUT — borderline NO

  • "yoga class" → lifestyle.book_yoga_class
  • "personal trainer" → lifestyle.book_personal_trainer
  • "monthly gym membership" → lifestyle.book_gym_membership
  • "wellness retreat" → lifestyle.book_wellness_retreat
  • "spa" → lifestyle.book_spa_treatment

MULTI-INTENT TRIGGERS

  • "gym session and protein shake" → lifestyle.book_gym_session + food.order_delivery
  • "gym pass and membership inquiry" → lifestyle.book_gym_session + lifestyle.book_gym_membership

2. INPUT — TOMO → PROVIDER (DELTA over lifestyle.book_salon)

All input fields from lifestyle.book_salon §2 are REQUIRED. REPLACE services_wanted with workout_intent. ADAPT user_profile to fitness profile. ADD gym-specific fields.

{
  "intent":          "lifestyle.book_gym_session",
  "intent_version":  "v1.0.0",
  "request_id":      "req_gym_8h2k_2026-05-10T08:00:00Z",

  "search_area": { ... },                                     // same as base

  "scheduled_for_iso":   "2026-05-10T18:30:00+05:30",
  "duration_minutes":     90,
  "session_kind":        "drop_in_pass",

  "user_fitness_profile": {
    "gender":              "female",
    "age_band":            "26-35",
    "fitness_level":       "intermediate",
    "weight_kg":           58,
    "height_cm":           165,
    "medical_conditions_disclosed": [],
    "fitness_goals":       ["weight_loss", "tone_up"],
    "current_workout_routine_kind": "mixed_cardio_strength"
  },

  "workout_intent": [
    {
      "category_kind":     "weight_training",
      "duration_minutes":  45
    },
    {
      "category_kind":     "treadmill_cardio",
      "duration_minutes":  30
    }
  ],

  "preferences": {
    "gym_kind_acceptable":    ["chain_gym", "boutique_gym", "hotel_gym"],
    "gym_kind_preferred":     "chain_gym",
    "trainer_floor_walker_required":  false,
    "trainer_on_demand_pt_required":  false,
    "shower_required":        true,
    "locker_required":        true,
    "ac_required":            true,
    "parking_required":       true,
    "vegan_smoothie_bar_required": false,
    "single_gender_floor_required": false,
    "budget_band":            "good",
    "budget_max_inr_total":   800,
    "max_wait_minutes_for_equipment": 5,
    "online_payment_required": true,
    "trial_workout_buddy_optional": false
  },

  "context": {
    ... (same as base)
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "lifestyle.book_gym_session"
session_kind enum REQUIRED, see §5
duration_minutes int REQUIRED, ≥30 typical 60-120
user_fitness_profile.gender enum REQUIRED
user_fitness_profile.age_band enum REQUIRED
user_fitness_profile.fitness_level enum REQUIRED, see §5 drives gym-class filter
user_fitness_profile.medical_conditions_disclosed array REQUIRED, may be empty see §5
user_fitness_profile.fitness_goals array REQUIRED, ≥1 see §5
user_fitness_profile.current_workout_routine_kind enum REQUIRED
workout_intent[].category_kind enum REQUIRED, see §5
workout_intent[].duration_minutes int REQUIRED, ≥0
preferences.gym_kind_acceptable array REQUIRED, ≥1 see §5
preferences.trainer_floor_walker_required bool REQUIRED
preferences.trainer_on_demand_pt_required bool REQUIRED
preferences.shower_required bool REQUIRED
preferences.locker_required bool REQUIRED
preferences.parking_required bool REQUIRED
preferences.single_gender_floor_required bool REQUIRED
preferences.max_wait_minutes_for_equipment int REQUIRED, ≥0
preferences.trial_workout_buddy_optional bool REQUIRED first-timer support

REMOVED from lifestyle.book_salon §2:

  • user_profile.skin_kind, hair_kind, hair_length, current_color
  • services_wanted (replaced by workout_intent)
  • preferences.products_kind_preferred, ammonia_free_color_required, vegan_products_only, halal_products_required

Anti-fabrication preamble (universal): no paid placement, no urgency text, no commission-influenced fields.


3. PROVIDER TOOLS (DELTA)

8 of 9 tools from lifestyle.book_salon §3 REQUIRED, with SalonOption replaced by GymSessionOption. ADD:

Tool 10: get_equipment_availability_now

PURPOSE:        live equipment occupancy for the requested gym at slot time
INPUT:          { gym_id, slot_iso, request_id }
OUTPUT:         { categories: EquipmentAvailability[], updated_iso }
SLA:            p95 < 800ms

All 10 REQUIRED.


4. RESPONSE SHAPE (DELTA)

GymSessionOption

All from SalonOption §4 REQUIRED, REPLACE salon with gym, services_offered with equipment_and_classes_catalog, stylists with trainers_on_floor. ADD gym-specific blocks.

gym:
  id:                             string, REQUIRED
  merchant_id:                    string, REQUIRED
  name:                           string, REQUIRED
  brand:                          string, REQUIRED
  gym_kind:                       STRICT ENUM, REQUIRED         # see §5
  partner_account_age_days:       int, REQUIRED, ≥0
  total_sessions_30day:           int, REQUIRED, ≥0
  total_active_members:           int, REQUIRED, ≥0
  address:
    line_1:                       string, REQUIRED
    line_2:                       string, REQUIRED
    neighborhood:                 string, REQUIRED
    city:                         string, REQUIRED
    pincode:                      string, REQUIRED
    lat:                          float, REQUIRED
    lng:                          float, REQUIRED
    google_place_id:              string, REQUIRED
    landmark:                     string, REQUIRED
  distance_from_user_km:          float, REQUIRED
  ratings:
    overall_score:                float, REQUIRED, 0-5
    overall_count:                int, REQUIRED, ≥0
    recent_30day_score:           float, REQUIRED, 0-5
    cleanliness_score:            float, REQUIRED, 0-5
    equipment_quality_score:      float, REQUIRED, 0-5
    crowd_density_score:          float, REQUIRED, 0-5         # less crowded = higher
    trainer_helpfulness_score:    float, REQUIRED, 0-5
    on_time_30day_pct:            float, REQUIRED, 0-1
  operating_hours:                # same shape as salon
    is_open_now:                  boolean, REQUIRED
    next_open_iso:                ISO_DATETIME, REQUIRED
    next_close_iso:               ISO_DATETIME, REQUIRED
    is_24x7:                      boolean, REQUIRED
    hours_of_operation:           array, REQUIRED, 7 entries
  amenities:
    air_conditioning:             boolean, REQUIRED
    music_system:                 boolean, REQUIRED
    showers_count:                int, REQUIRED, ≥0
    showers_with_hot_water:       boolean, REQUIRED
    lockers_count:                int, REQUIRED, ≥0
    locker_secure_lock:           boolean, REQUIRED
    changing_rooms_count:         int, REQUIRED, ≥0
    parking_available:            boolean, REQUIRED
    parking_kind:                 STRICT ENUM, REQUIRED
    smoothie_bar_present:         boolean, REQUIRED
    smoothie_vegan_options:       boolean, REQUIRED
    wifi_available:               boolean, REQUIRED
    wifi_free:                    boolean, REQUIRED
    pool_available:               boolean, REQUIRED            # for fitness centers with pool
    sauna_available:              boolean, REQUIRED
    steam_room_available:         boolean, REQUIRED
    physiotherapist_on_call:      boolean, REQUIRED
    childcare_available:          boolean, REQUIRED
    wheelchair_accessible:        boolean, REQUIRED
    female_only_floor:            boolean, REQUIRED
    male_only_floor:              boolean, REQUIRED
    cctv_in_workout_zones:        boolean, REQUIRED
    panic_button_at_floor:        boolean, REQUIRED
  trust:
    municipal_license_number:     string, REQUIRED
    municipal_license_valid_until: ISO_DATE, REQUIRED
    safety_audit_iso:             ISO_DATETIME, REQUIRED
    safety_audit_score:           int, REQUIRED, 0-100
    equipment_certified_safe:     boolean, REQUIRED            # last cert iso for safety
    equipment_last_certified_iso: ISO_DATE, REQUIRED
    employee_kyc_pct:             int, REQUIRED, 0-100
    insurance_coverage_inr:       INR_INTEGER, REQUIRED
    aed_defibrillator_present:    boolean, REQUIRED
    first_aid_kit_present:        boolean, REQUIRED
    fire_safety_compliant:        boolean, REQUIRED
    tomo_field_team_audited:      boolean, REQUIRED
  photos:
    thumbnail_url:                URL, REQUIRED
    hero_url:                     URL, REQUIRED
    workout_zones_photos_url:     URL, REQUIRED
    amenities_photos_url:         URL, REQUIRED
    ai_generated_photos:          boolean, REQUIRED            # MUST be false

equipment_and_classes_catalog:    array<EquipmentItem>, REQUIRED, ≥3   # see §5

available_slots:                  array<GymSlotOption>, REQUIRED, ≥1

trainers_on_floor:                array<TrainerProfile>, REQUIRED, may be empty

current_capacity:
  current_members_in_premises:    int, REQUIRED, ≥0
  capacity_max:                   int, REQUIRED, ≥1
  capacity_pct:                   int, REQUIRED, 0-100
  peak_hours_today:               array<string>, REQUIRED      # time ranges as strings

session_pass:
  pass_kind:                      STRICT ENUM, REQUIRED         # see §5
  pass_inr:                       INR_INTEGER, REQUIRED
  pass_includes:                  array<STRICT ENUM>, REQUIRED  # see §5
  trainer_floor_walker_included:  boolean, REQUIRED
  pt_session_extra_inr:           INR_INTEGER, REQUIRED         # 0 if not requested
  shower_included:                boolean, REQUIRED
  smoothie_voucher_included:      boolean, REQUIRED
  smoothie_voucher_inr:           INR_INTEGER, REQUIRED
  validity_days:                  int, REQUIRED, ≥1            # 1 typical for drop-in

# (all other fields from SalonOption §4 - fare, cancellation, freshness, _provider - REQUIRED)

EquipmentItem

equipment_id:                     string, REQUIRED
category_kind:                    STRICT ENUM, REQUIRED         # see §5
equipment_name:                   string, REQUIRED              # "Treadmill", "Squat Rack"
brand:                            string, REQUIRED
units_count:                      int, REQUIRED, ≥1
maintenance_score:                int, REQUIRED, 0-100
last_certified_iso:               ISO_DATE, REQUIRED
peak_wait_minutes:                int, REQUIRED, ≥0
photo_url:                        URL, REQUIRED
photo_ai_generated:               boolean, REQUIRED              # MUST be false

TrainerProfile

(Subset of StylistProfile — drop hair-specific fields, add fitness specializations. All fields REQUIRED.)

trainer_id:                       string, REQUIRED
display_name:                     string, REQUIRED
photo_url:                        URL, REQUIRED
photo_ai_generated:               boolean, REQUIRED              # MUST be false
gender:                           STRICT ENUM, REQUIRED
age_band:                         STRICT ENUM, REQUIRED
experience_years:                 int, REQUIRED, ≥0
specializations:                  array<STRICT ENUM>, REQUIRED, ≥1   # see §5
certifications:                   array<STRICT ENUM>, REQUIRED, may be empty
certifying_authorities:           array<STRICT ENUM>, REQUIRED, may be empty
languages_spoken:                 array<RFC_3066_LOCALE>, REQUIRED, ≥1
ratings:
  rating_avg:                     float, REQUIRED, 0-5
  rating_count:                   int, REQUIRED, ≥0
  sessions_completed_total:       int, REQUIRED, ≥0
  sessions_completed_30day:       int, REQUIRED, ≥0
kyc:
  aadhaar_verified:               boolean, REQUIRED
  pan_verified:                   boolean, REQUIRED
  background_check_passed:        boolean, REQUIRED
  background_check_iso:           ISO_DATETIME, REQUIRED
employed_kind:                    STRICT ENUM, REQUIRED          # full_time | part_time | freelance
on_floor_now:                     boolean, REQUIRED
pt_session_inr:                   INR_INTEGER, REQUIRED          # if available for paid PT

GymSlotOption

slot_id:                          string, REQUIRED
slot_iso:                         ISO_DATETIME, REQUIRED
duration_minutes:                 int, REQUIRED
class_id:                         string, REQUIRED               # may be empty for free-floor sessions
class_name:                       string, REQUIRED               # "Spin class", "" for free-floor
trainer_id_assigned:              string, REQUIRED               # may be empty
spots_remaining:                  int, REQUIRED, ≥0
spots_total:                      int, REQUIRED, ≥1
arrival_window_minutes:           int, REQUIRED
deposit_inr:                      INR_INTEGER, REQUIRED          # 0 if no deposit
cancellation_until_iso:           ISO_DATETIME, REQUIRED
no_show_charge_inr:               INR_INTEGER, REQUIRED
slot_kind:                        STRICT ENUM, REQUIRED          # see §5
freshness_iso:                    ISO_DATETIME, REQUIRED

Forbidden fields

paid_placement_score | sponsored_rank | promotion_priority |
fake_recent_session_text | auto_inflate_session_count |
ai_generated_photos (must equal false) | hidden_pass_fee |
fake_certifications | fake_aed_defibrillator_present |
fake_partner_completion_rate_30d | hidden_locker_charge

5. CONTROLLED VOCABULARIES (DELTA)

All non-cosmetic vocabularies from lifestyle.book_salon §5 REQUIRED.

session_kind

drop_in_pass | day_pass | trial_session | guest_pass |
class_only_pass | open_floor_only | premium_drop_in

gym_kind

chain_gym | boutique_gym | budget_gym | premium_independent |
hotel_gym | corporate_gym | women_only_gym | mens_only_gym |
24x7_gym | community_gym | celebrity_brand

user_fitness_profile.fitness_level

beginner | novice | intermediate | advanced | athlete | elite

user_fitness_profile.medical_conditions_disclosed[]

hypertension | diabetes | heart_condition | back_pain | knee_problem |
shoulder_problem | pregnancy | postpartum | hernia | arthritis |
asthma | recent_surgery | medication_dependent | other | none

user_fitness_profile.fitness_goals[]

weight_loss | weight_gain | muscle_gain | tone_up | endurance |
strength | flexibility | rehabilitation | sport_specific |
general_fitness | mental_wellbeing

user_fitness_profile.current_workout_routine_kind

none | mixed_cardio_strength | strength_only | cardio_only |
calisthenics | yoga_pilates | crossfit | sport_specific | other

workout_intent[].category_kind and EquipmentItem.category_kind

weight_training | strength_machines | free_weights | cardio_treadmill |
cardio_elliptical | cardio_bike | cardio_rower | cardio_stairmaster |
functional_training | crossfit_zone | trx_suspension | yoga_zone |
pilates_zone | spin_class | hiit_class | zumba_class | dance_class |
boxing_zone | mma_zone | kettlebell_zone | sled_zone |
recovery_zone | stretching_zone | sauna_steam | pool_lap_swim |
pool_aqua_aerobics | physio_zone

gym.amenities.parking_kind

valet_only | self_park_lot | street_parking | mall_parking |
no_parking | basement

session_pass.pass_kind

basic_floor | full_access | class_inclusive | premium_with_pt |
trial_first_visit | guest_with_member

session_pass.pass_includes[]

weight_training_floor | cardio_floor | functional_zone |
classes_unlimited_today | spin_class | hiit_class | yoga_class |
zumba_class | sauna_steam | pool_access | locker_access |
shower_access | smoothie_voucher

TrainerProfile.specializations[]

strength_coach | functional_trainer | crossfit_l1 | crossfit_l2 |
yoga_alliance_certified | pilates_certified | hiit_specialist |
zumba_licensed | spin_certified | nutrition_consultant |
postpartum_specialist | rehab_specialist | sport_coach

TrainerProfile.certifications[]

acsm_certified | nasm_certified | issa_certified | aceit_certified |
icb_l1 | icb_l2 | yoga_alliance_200 | yoga_alliance_500 |
pilates_l1 | pilates_l2 | nutrition_certified | partner_internal_cert

TrainerProfile.certifying_authorities[]

acsm | nasm | issa | acefit | crossfit_inc | yoga_alliance |
balanced_body_pilates | iyengar_yoga_institute | partner_internal

TrainerProfile.gender

female | male | non_binary | prefer_not_to_say

TrainerProfile.employed_kind

full_time_employee | part_time_employee | contract | freelance

GymSlotOption.slot_kind

walk_in_compatible | premium_slot | early_morning_5_to_7 |
mid_morning_7_to_10 | midday_10_to_4 | evening_peak_5_to_8 |
late_evening_8_plus | weekend_premium | women_only_hours

cancel_appointment.reason (additions)

medical_condition_arose | gym_closed_unexpectedly |
... (all base reasons)

merchant_id resolution order

1. Google Place ID
2. Municipal license number
3. partner_id + ":" + gym_id

6. TTBS DIMENSIONS (DELTA)

Per-domain weights

lifestyle (book_gym_session): { time: 0.30, taste: 0.30, budget: 0.30, safety: 0.10 }

TIME

SIGNALS USED:
  - distance_from_user_km                            weight 0.30
  - operating_hours.is_open_now                      HARD FILTER
  - gym.is_24x7=true if late-night slot              weight 0.10
  - current_capacity.capacity_pct (lower=better)     weight 0.30
  - max_wait_minutes_for_equipment match             weight 0.20
  - peak_hours_today avoidance                       weight 0.10

TASTE

SIGNALS USED:
  - gym.ratings.equipment_quality_score              weight 0.20
  - gym.ratings.crowd_density_score                  weight 0.20
  - gym.ratings.trainer_helpfulness_score            weight 0.15
  - equipment_and_classes_catalog match workout_intent weight 0.20
  - amenities match preferences                      weight 0.15
  - trainer_floor_walker_included if requested       weight 0.10

HARD FILTERS:
  - shower_required AND showers_count=0
  - locker_required AND lockers_count=0
  - parking_required AND parking_available=false
  - single_gender_floor_required AND no female_only/male_only floor
  - workout_intent category not in equipment_and_classes_catalog

BUDGET

SIGNALS USED:
  - session_pass.pass_inr vs band:
      ok    → budget_gym / community_gym
      good  → chain_gym / boutique_gym
      great → premium_independent / celebrity_brand
  - smoothie_voucher_included (bonus)               weight 0.10
  - shower_included (bonus)                         weight 0.10
  - cancellation tier permissiveness                weight 0.15
  - online_payment_required match                    HARD FILTER

HARD FILTERS:
  - session_pass.pass_inr > preferences.budget_max_inr_total → drop

SAFETY

SIGNALS USED:
  - gym.trust.equipment_certified_safe              HARD FILTER
  - gym.trust.aed_defibrillator_present             weight 0.30
  - gym.trust.first_aid_kit_present                 HARD FILTER
  - gym.trust.fire_safety_compliant                 HARD FILTER
  - gym.trust.safety_audit_score                    weight 0.20
  - gym.amenities.cctv_in_workout_zones             weight 0.10
  - gym.amenities.panic_button_at_floor             weight 0.10
  - trainer_floor_walker_required HARD FILTER (if true)
  - medical_conditions disclosed AND physiotherapist_on_call  weight 0.20

Hidden ranking factor

information_completeness_score weight 0.10.


7. COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/{tomo_partner_id}
{
  "intent":            "lifestyle.book_gym_session",
  "intent_version":    "v1.0.0",
  "external_id":       "GYM-XYZ",
  "amount_inr":         650,
  "closed_at":         "2026-05-10T20:14:00+05:30",
  "request_id":        "req_gym_8h2k_...",
  "status":            "completed",
  "appointment_ref":   "GYM-XYZ",
  "merchant_id":       "ChIJxxxxx",
  "gym_id":            "gym_8423",
  "checked_in_at":     "2026-05-10T18:32:00+05:30",
  "checked_out_at":    "2026-05-10T20:08:00+05:30",
  "duration_minutes_actual": 96,
  "categories_used":   ["weight_training", "treadmill_cardio"],
  "any_safety_incident": false,
  "currency":          "INR",
  "fare_breakdown_total_inr": 650,
  "rider_tip_inr":       0,
  "ratings_pending":     true,
  "notes":              ""
}

Status enum: completed | cancelled_by_user | cancelled_by_gym | no_show | failed | partial_completion_user_left_early


8. WIDGET (DELTA)

WIDGET TYPE:        gym_session_options
SOURCE:             src/widgets/types.ts
TYPE NAME:          GymSessionOptionsPayload
RENDERED IN:        components/widgets/GymSessionOptionsWidget.tsx

Default: 3 stacked rows with gym + capacity-pct badge + equipment-match strip + pass price. Tap → gym detail with equipment catalog + trainer roster + amenities → "Book session". Then GymSessionCard with QR check-in + locker assignment + AED location.


9. CACHING POLICY

Call TTL Rationale
search_gym_options 60s capacity shifts
get_gym_detail 5min equipment catalog static-ish
get_equipment_availability_now 30s live
Standard tools per lifestyle.book_salon §9 inherited

10. ERROR CODES (DELTA)

All from lifestyle.book_salon §10 REQUIRED. ADD:

Code HTTP Meaning TOMO behavior
GYM_AT_CAPACITY 503 premises full suggest later slot
EQUIPMENT_OUT_OF_ORDER 503 requested equipment unavailable surface alternates
MEDICAL_DISCLOSURE_REQUIRED 400 undisclosed condition flagged surface waiver flow
PARTNER_INSURANCE_GAP 503 gym insurance lapsed drop, fall back

11. SANDBOX → PRODUCTION CHECKLIST

All from lifestyle.book_salon §11 REQUIRED. ADD:

[ ] equipment_certified_safe within last 12 months
[ ] AED defibrillator present + staff trained
[ ] first_aid_kit complete
[ ] insurance_coverage_inr verified
[ ] physiotherapist_on_call tested for medical-disclosure user
[ ] panic_button_at_floor functional
[ ] capacity_max accurate (TOMO field count)
[ ] trial_workout_buddy assigned for first-timer (if requested)

12. ANTI-FABRICATION RULES (DELTA)

All rules from lifestyle.book_salon §12 REQUIRED. ADD:

RULE 17 — Equipment safety claims real.
  equipment_certified_safe=true with last cert > 12 months = breach.

RULE 18 — AED claim real.
  aed_defibrillator_present=true requires functional, tested device.
  Marketing prop without working AED = severe safety breach.

RULE 19 — No fake capacity_pct.
  Inflating "low crowd" to attract users when actually full = breach.

RULE 20 — Trainer specializations real and active.
  Crossfit L1 cert claim must be active CrossFit Inc registration.

RULE 21 — Class spots honest.
  spots_remaining must decrement honestly. Showing "1 left!" when actually
  empty = breach.

RULE 22 — Insurance current.
  insurance_coverage_inr backed by IRDAI policy current at session date.
  Lapsed insurance during session = severe breach.

RULE 23 — No silent class cancellation.
  If class is cancelled within free_cancel window, partner refunds + offers
  alternate. Pocketing = breach.

VERSION HISTORY

v1.0.0 — 2026-05-10 — Initial spec (delta over lifestyle.book_salon, gym extension)