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.0mobility.book_two_wheeler_rental

mobility.book_two_wheeler_rental — Full Intent Specification

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

Two-wheeler rental shares the self-drive doctrine (user IS the rider) with three locked differences: (a) helmet provisioning is a first-class field — partners MUST provide certified helmets; (b) license endorsement gate is license_endorsement_2w (often missing on Indian DLs that only cover LMV); (c) safety telemetry is critical — 2W riders are high-injury cohort, so partner-side helmet_strap_sensor and gear-provisioning rank heavily.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "rent scooter for the day"
  • "Activa rental Hyderabad"
  • "rent bike for weekend"
  • "Royal Enfield rental Goa"
  • "Yulu scooter to office"
  • "rent EV scooter for the day"
  • "two wheeler rental near me"
  • "Bounce scooter for 2 hours"
  • "rent geared bike for highway ride"

Classifies OUT — borderline NO

  • "Rapido bike taxi to MG Road" → mobility.book_intracity_ride (bike_taxi vehicle_kind)
  • "self-drive car for weekend" → mobility.book_self_drive
  • "I want to buy a scooter" → no intent (informational)
  • "service my bike" → auto.book_general_service
  • "auto to airport" → mobility.book_intracity_ride

MULTI-INTENT TRIGGERS

  • "rent scooter for the day and book hotel near city centre" → mobility.book_two_wheeler_rental + travel.book_hotel
  • "rent bike for highway ride and verify my DL" → mobility.book_two_wheeler_rental + compliance.verify_rc_dl
  • "rent EV scooter and check charge stations along route" → mobility.book_two_wheeler_rental + mobility.book_ev_with_charge_planning

2. INPUT — TOMO → PROVIDER

{
  "intent":          "mobility.book_two_wheeler_rental",
  "intent_version":  "v1.0.0",
  "request_id":      "req_2w_5h7m_2026-05-10T07:30:00Z",
  "user_session_id": "anon_user_token_or_uid",

  "pickup_location": {
    "lat":            17.4435,
    "lng":            78.3772,
    "address":        "Bounce Hub Madhapur",
    "neighborhood":   "Madhapur",
    "city":           "Hyderabad",
    "pincode":        "500081",
    "state_code":     "TS",
    "country_code":   "IN",
    "hub_kind":       "branded_hub",
    "instructions":   "Outside metro station gate B"
  },

  "drop_location": {
    "lat":            17.4435,
    "lng":            78.3772,
    "address":        "Bounce Hub Madhapur",
    "neighborhood":   "Madhapur",
    "city":           "Hyderabad",
    "pincode":        "500081",
    "state_code":     "TS",
    "country_code":   "IN",
    "hub_kind":       "branded_hub",
    "instructions":   "Same hub return"
  },

  "rental_kind":         "round_trip",
  "scheduled_pickup_iso": "2026-05-10T09:00:00+05:30",
  "scheduled_return_iso": "2026-05-10T18:00:00+05:30",
  "billed_hours":         9,
  "billed_days":          0,

  "user_riding_profile": {
    "user_age":                       28,
    "license_state_code":             "TS",
    "license_endorsement_2w":         true,
    "license_endorsement_2w_with_gear": true,
    "license_held_for_years":         6,
    "license_verified_with_partner":  false,
    "prior_2w_rental_count":          7,
    "international_license":          false
  },

  "preferences": {
    "vehicle_kinds_acceptable":   ["scooter", "electric_scooter"],
    "engine_cc_max":              125,
    "fuel_kinds_acceptable":      ["petrol", "ev_full"],
    "budget_band":                "ok",
    "budget_max_inr":              350,
    "min_pillion_capacity":       1,
    "doorstep_delivery_required":  false,
    "with_fuel_topped":            true,
    "include_pillion_helmet":     true,
    "include_raincoat":           true,
    "include_jacket":             false
  },

  "trip_intent": {
    "expected_distance_km":      40,
    "expected_states_crossed":   ["TS"],
    "involves_highway":          false,
    "is_overnight":              false,
    "is_late_night_pickup":      false,
    "purpose":                   "commute_to_office"
  },

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trust_signals": {
      "is_repeat_customer":          true,
      "prior_rentals_with_partner":  3,
      "user_account_age_days":       312
    }
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "mobility.book_two_wheeler_rental"
pickup_location.hub_kind enum REQUIRED, see §5
rental_kind enum REQUIRED, STRICT round_trip | one_way
billed_hours int REQUIRED, ≥1 hourly billing common for 2W
user_riding_profile.user_age int REQUIRED, ≥18
user_riding_profile.license_endorsement_2w bool REQUIRED HARD FILTER if false
user_riding_profile.license_endorsement_2w_with_gear bool REQUIRED required for geared bikes
preferences.vehicle_kinds_acceptable array REQUIRED, ≥1
preferences.engine_cc_max int REQUIRED, ≥0 0 means EV/no-CC; ≥350 for premium bikes
preferences.min_pillion_capacity int REQUIRED, ≥0 0 = solo OK
preferences.include_pillion_helmet bool REQUIRED drives helmet count
preferences.include_raincoat bool REQUIRED seasonal
preferences.include_jacket bool REQUIRED premium-bike rentals
trip_intent.involves_highway bool REQUIRED most 2W rentals city-only

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


3. PROVIDER TOOLS

Tool 1: get_two_wheeler_inventory

PURPOSE:        return available 2W vehicles for the requested window + location
INPUT:          §2 request body
OUTPUT:         { options: TwoWheelerOption[], result_token, expires_at }
SLA:            p50 < 600ms, p95 < 1500ms
RATE LIMIT:     ≤ 1/sec per user

Tool 2: book_two_wheeler

PURPOSE:        commit a 2W reservation
INPUT:          { option_id, payment_token, request_id, idempotency_key, user_phone, dl_image_url, selfie_image_url, deposit_token }
OUTPUT:         { booking_ref, status, vehicle, fare_quote, deposit_inr, hub_handover_window_iso, gear_kit }
SLA:            p95 < 4000ms
IDEMPOTENCY:    REQUIRED on idempotency_key

Tool 3: get_handover_state

PURPOSE:        live booking + handover state
INPUT:          { booking_ref, request_id }
OUTPUT:         TwoWheelerHandoverState (§4)
SLA:            p95 < 500ms
RATE LIMIT:     ≤ 1 every 30s

Tool 4: cancel_two_wheeler

PURPOSE:        cancel
INPUT:          { booking_ref, reason, request_id }
OUTPUT:         { status, cancellation_charge_inr, refund_amount_inr, refund_processing_days }
SLA:            p95 < 2000ms

Tool 5: extend_two_wheeler

PURPOSE:        extend rental return time
INPUT:          { booking_ref, new_return_iso, request_id }
OUTPUT:         { revised_fare_inr, additional_hours_billed, status }
SLA:            p95 < 1500ms

Tool 6: report_damage_or_incident

PURPOSE:        user reports incident mid-rental
INPUT:          { booking_ref, incident_kind, photos[], description, request_id }
OUTPUT:         { incident_ref, status, partner_action_taken }
SLA:            p95 < 2000ms

Tool 7: complete_return

PURPOSE:        partner records return + condition photos + final fare
INPUT:          { booking_ref, return_photos[], odometer_reading_km, fuel_level, helmet_returned_count, request_id }
OUTPUT:         { final_fare_inr, deposit_refund_inr, damage_charge_inr, helmet_loss_charge_inr, late_return_charge_inr, status }
SLA:            p95 < 3000ms

All seven REQUIRED.


4. RESPONSE SHAPE

TwoWheelerOption (returned by get_two_wheeler_inventory)

id:                               string, REQUIRED
option_token:                     string, REQUIRED
expires_at:                       ISO_DATETIME, REQUIRED

vehicle_kind:                     STRICT ENUM, REQUIRED       # see §5
vehicle_class:                    STRICT ENUM, REQUIRED
display_label:                    string, REQUIRED            # "Honda Activa 6G"
make:                             string, REQUIRED
model:                            string, REQUIRED
variant:                          string, REQUIRED
year_of_manufacture:              int, REQUIRED
engine_cc:                        int, REQUIRED, ≥0           # 0 if EV
pillion_capacity:                 int, REQUIRED, 0 or 1       # 1 for most 2W; 0 for kick-only racing
storage_under_seat_litres:        float, REQUIRED, ≥0

availability:
  pickup_hub_address:             string, REQUIRED
  pickup_hub_lat:                 float, REQUIRED
  pickup_hub_lng:                 float, REQUIRED
  pickup_hub_kind:                STRICT ENUM, REQUIRED
  doorstep_delivery_available:    boolean, REQUIRED
  doorstep_delivery_charge_inr:   INR_INTEGER, REQUIRED
  hub_open_iso:                   ISO_DATETIME, REQUIRED
  hub_close_iso:                  ISO_DATETIME, REQUIRED
  vehicle_ready_at_iso:           ISO_DATETIME, REQUIRED
  alternate_pickup_hubs:          array, REQUIRED, may be empty

fare:
  total_inr:                      INR_INTEGER, REQUIRED
  base_per_hour_inr:              INR_INTEGER, REQUIRED
  base_per_day_inr:               INR_INTEGER, REQUIRED
  billed_hours:                   int, REQUIRED
  billed_days:                    int, REQUIRED
  included_km:                    int, REQUIRED
  per_km_overage_inr:             float, REQUIRED
  fuel_policy:                    STRICT ENUM, REQUIRED
  fuel_top_up_charge_inr:         INR_INTEGER, REQUIRED
  basic_insurance_inr:            INR_INTEGER, REQUIRED
  helmet_inr:                     INR_INTEGER, REQUIRED       # 0 if free
  pillion_helmet_inr:             INR_INTEGER, REQUIRED       # 0 if not requested
  raincoat_inr:                   INR_INTEGER, REQUIRED
  jacket_inr:                     INR_INTEGER, REQUIRED
  panniers_inr:                   INR_INTEGER, REQUIRED       # 0 if not requested
  late_return_per_hour_inr:       INR_INTEGER, REQUIRED
  doorstep_delivery_inr:          INR_INTEGER, REQUIRED
  one_way_drop_charge_inr:        INR_INTEGER, REQUIRED
  platform_fee_inr:               INR_INTEGER, REQUIRED
  gst_inr:                        INR_INTEGER, REQUIRED
  fare_breakdown_text:            string, REQUIRED
  is_upfront_fare:                boolean, REQUIRED
  fare_locked_until_iso:          ISO_DATETIME, REQUIRED

deposit:
  required:                       boolean, REQUIRED
  amount_inr:                     INR_INTEGER, REQUIRED
  hold_kind:                      STRICT ENUM, REQUIRED
  refund_eta_business_days:       int, REQUIRED, ≥0

gear_kit:
  rider_helmet_provided:          boolean, REQUIRED
  rider_helmet_isi_certified:     boolean, REQUIRED
  rider_helmet_size_options:      array<STRICT ENUM>, REQUIRED  # see §5
  pillion_helmet_provided:        boolean, REQUIRED
  pillion_helmet_isi_certified:   boolean, REQUIRED
  raincoat_provided:              boolean, REQUIRED
  jacket_provided:                boolean, REQUIRED
  jacket_with_armor:              boolean, REQUIRED
  panniers_provided:              boolean, REQUIRED
  knee_guards_provided:           boolean, REQUIRED
  gloves_provided:                boolean, REQUIRED

vehicle_meta:
  age_years:                      int, REQUIRED
  fuel_kind:                      STRICT ENUM, REQUIRED
  fuel_efficiency_kmpl:           float, REQUIRED
  fuel_tank_capacity_litres:      float, REQUIRED
  ev_battery_charge_pct:          int, REQUIRED, 0-100
  ev_range_km_remaining:          int, REQUIRED
  emission_norm:                  STRICT ENUM, REQUIRED
  registration_state_code:        string, REQUIRED
  vehicle_class_certification:    STRICT ENUM, REQUIRED       # private | self_drive_yellow_plate
  comprehensive_insurance:        boolean, REQUIRED
  insurance_valid_until_iso:      ISO_DATE, REQUIRED
  insurance_company:              string, REQUIRED
  fitness_certificate_valid_until_iso: ISO_DATE, REQUIRED
  puc_valid_until_iso:            ISO_DATE, REQUIRED
  permit_kind:                    STRICT ENUM, REQUIRED       # self_drive_permit
  vehicle_color:                  string, REQUIRED
  last_serviced_iso:              ISO_DATE, REQUIRED
  odometer_reading_km:            int, REQUIRED, ≥0
  current_fuel_level_pct:         int, REQUIRED, 0-100
  geofence_kind:                  STRICT ENUM, REQUIRED
  geofence_states_allowed:        array<string>, REQUIRED, ≥1
  speed_limit_governed:           boolean, REQUIRED
  speed_limit_kmph:               int, REQUIRED
  telematics_active:              boolean, REQUIRED
  helmet_strap_sensor:            boolean, REQUIRED            # detects unhelmeted riding
  abs:                            boolean, REQUIRED
  cbs:                            boolean, REQUIRED            # combined braking system

vehicle_amenities:
  led_headlight:                  boolean, REQUIRED
  digital_cluster:                boolean, REQUIRED
  bluetooth_console:              boolean, REQUIRED
  usb_charger:                    boolean, REQUIRED
  glove_box:                      boolean, REQUIRED
  side_stand_indicator:           boolean, REQUIRED

vehicle_photos:
  pre_handover_photos_required:   boolean, REQUIRED
  pre_handover_photos:            array<URL>, REQUIRED        # ≥4 angles for 2W
  pre_handover_photos_iso:        ISO_DATETIME, REQUIRED
  damage_marker_present_at_pickup: boolean, REQUIRED

rider_qualification_for_user:
  min_user_age_required:          int, REQUIRED, ≥18
  min_license_held_years:         int, REQUIRED, ≥0
  geared_endorsement_required:    boolean, REQUIRED
  international_license_accepted: boolean, REQUIRED
  user_meets_qualification:       boolean, REQUIRED
  qualification_failure_reason:   STRICT ENUM, REQUIRED

partner_safety_features:
  sos_button_in_app:              boolean, REQUIRED
  trip_share_supported:           boolean, REQUIRED
  in_app_chat_support:            boolean, REQUIRED
  in_app_call_support:            boolean, REQUIRED
  emergency_contact_alerts:       boolean, REQUIRED
  vehicle_telematics_alerts:      boolean, REQUIRED
  roadside_assistance_24x7:       boolean, REQUIRED
  on_call_mechanic:               boolean, REQUIRED
  emergency_swap_vehicle:         boolean, REQUIRED
  helmet_verification_at_pickup:  boolean, REQUIRED            # partner verifies user wears helmet

cancellation:
  free_cancel_until_iso:          ISO_DATETIME, REQUIRED
  cancel_charge_within_24h_inr:   INR_INTEGER, REQUIRED
  cancel_charge_within_2h_inr:    INR_INTEGER, REQUIRED
  no_show_charge_inr:             INR_INTEGER, REQUIRED
  partner_cancel_compensation_inr: INR_INTEGER, REQUIRED
  refund_processing_days:         int, REQUIRED, ≥0

freshness:
  data_last_synced_iso:           ISO_DATETIME, REQUIRED

_provider:
  name:                           string, REQUIRED
  tomo_partner_id:                string, REQUIRED
  partner_tier:                   STRICT ENUM, REQUIRED
  deep_link:                      URL, REQUIRED
  customer_support_phone:         string, REQUIRED
  customer_support_24x7:          boolean, REQUIRED
  partner_2w_volume_30d:          int, REQUIRED, ≥0

TwoWheelerHandoverState (returned by get_handover_state)

booking_ref:                      string, REQUIRED
status:                           STRICT ENUM, REQUIRED
status_updated_iso:               ISO_DATETIME, REQUIRED
status_history:                   array, REQUIRED, ≥1

handover_window:
  pickup_window_open_iso:         ISO_DATETIME, REQUIRED
  pickup_window_close_iso:        ISO_DATETIME, REQUIRED
  return_window_open_iso:         ISO_DATETIME, REQUIRED
  return_window_close_iso:        ISO_DATETIME, REQUIRED

vehicle:
  vehicle_kind:                   STRICT ENUM, REQUIRED
  display_label:                  string, REQUIRED
  color:                          string, REQUIRED
  plate_masked:                   string, REQUIRED
  current_lat:                    float, REQUIRED
  current_lng:                    float, REQUIRED
  current_state_code:             string, REQUIRED
  current_speed_kmh:              float, REQUIRED, ≥0
  current_fuel_level_pct:         int, REQUIRED, 0-100
  odometer_reading_km:            int, REQUIRED, ≥0
  geofence_breach_active:         boolean, REQUIRED
  speed_limit_breach_active:      boolean, REQUIRED
  helmet_strap_breach_active:     boolean, REQUIRED            # rider unhelmeted

usage_so_far:
  km_driven:                      float, REQUIRED, ≥0
  hours_elapsed:                  int, REQUIRED, ≥0
  fuel_consumed_litres:           float, REQUIRED, ≥0
  current_overage_charge_inr:     INR_INTEGER, REQUIRED, ≥0

incidents:
  incidents_reported:             int, REQUIRED, ≥0
  open_incidents:                 array, REQUIRED, may be empty

dl_verification:
  required:                       boolean, REQUIRED
  verified_iso:                   ISO_DATETIME, REQUIRED
  verification_method:            STRICT ENUM, REQUIRED

support_phone:                    string, REQUIRED
support_email:                    string, REQUIRED

Forbidden fields

paid_placement_score | sponsored_rank | promotion_priority |
artificial_demand_text | fake_recent_booking_text |
auto_inflate_user_age_min | partner_paid_for_top_listing |
hidden_deposit_inr | undocumented_helmet_loss_inr |
fake_isi_certified | uncertified_helmet_charged_as_certified

5. CONTROLLED VOCABULARIES

vehicle_kind

scooter | geared_bike | premium_bike | adventure_bike | cruiser_bike |
sport_bike | electric_scooter | electric_bike | electric_moped

vehicle_class

economy | comfort | premium | sport | adventure | cruiser

transmission (implicit, no input field needed)

Most scooters: CVT-automatic. Geared bikes: manual gearbox. Auto-classified by partner per vehicle.

pickup_location.hub_kind and drop_location.hub_kind

branded_hub | partner_dealership | metro_station_zone |
mall_parking | doorstep_delivery

rental_kind

round_trip | one_way

fare.fuel_policy

full_to_full | empty_to_empty | as_is | included

vehicle_meta.fuel_kind

petrol | ev_full | hybrid | bs6_petrol

vehicle_meta.emission_norm

bs3 | bs4 | bs6 | ev | unknown_legacy

vehicle_meta.permit_kind

self_drive_permit | private

vehicle_meta.vehicle_class_certification

private | self_drive_yellow_plate

vehicle_meta.geofence_kind

none | city_only | state_only | multi_state_specified | nationwide

gear_kit.rider_helmet_size_options[]

xs | s | m | l | xl | xxl

deposit.hold_kind

card_block | bank_transfer | wallet_hold | none

rider_qualification_for_user.qualification_failure_reason

none | user_age_below_minimum | license_held_too_short |
license_2w_endorsement_missing | license_2w_geared_missing |
license_expired | foreign_license_not_accepted | prior_incident_block

TwoWheelerHandoverState.status

booked_pending_pickup | dl_verification_required | dl_verified |
ready_for_pickup | helmet_handed_over | picked_up | in_use |
return_pending | returned_pending_inspection | inspection_complete |
completed | cancelled_by_user | cancelled_by_partner | failed |
incident_open | extended

dl_verification.verification_method

digilocker | parivahan | manual_kyc | none

incidents[].incident_kind

breakdown | accident | flat_tyre | dead_battery | fuel_empty |
mechanical_warning | exterior_damage_observed | theft_attempt |
geofence_breach | helmet_strap_breach | speed_limit_breach | other

cancel_two_wheeler.reason

user_changed_mind | wrong_vehicle | wrong_pickup_address |
vehicle_unavailable | safety_concern | weather_cancellation |
no_longer_needed | found_alternative | emergency

context.trip_intent.purpose

commute_to_office | commute_to_home | leisure | college |
errand | sightseeing | food_pickup | other

6. TTBS DIMENSIONS

Per-domain weights (locked; 2W mirrors self-drive: safety up)

mobility (two_wheeler_rental): { time: 0.20, taste: 0.20, budget: 0.30, safety: 0.30 }

TIME

SIGNALS USED:
  - availability.vehicle_ready_at_iso ≤ pickup_iso     HARD FILTER
  - availability.doorstep_delivery_available           weight 0.20
  - availability.alternate_pickup_hubs count           weight 0.10
  - cancellation.refund_processing_days (lower=better) weight 0.10

USER BAND HANDLING:
  - "right now" → drop options where vehicle_ready_at_iso > now+15min

TASTE

SIGNALS USED:
  - vehicle.year_of_manufacture (newer = better)       weight 0.25
  - vehicle.make/model affinity (DNA-personalized)     weight 0.25
  - vehicle_amenities match w/ user prefs              weight 0.20
  - gear_kit.jacket_with_armor (if requested)          weight 0.10
  - vehicle_amenities.bluetooth_console                weight 0.10
  - vehicle_amenities.led_headlight                    weight 0.10

HARD FILTERS:
  - fuel_kinds_acceptable doesn't include vehicle_meta.fuel_kind → drop
  - engine_cc_max < vehicle.engine_cc → drop (user wanted ≤125, got 350)
  - min_pillion_capacity > vehicle.pillion_capacity → drop

BUDGET

SIGNALS USED:
  - fare.total_inr vs band:
      ok    → scooter / electric_scooter (basic)
      good  → geared_bike (mid) / premium scooter
      great → premium_bike / cruiser_bike / sport_bike
  - fare.is_upfront_fare=true                          weight 0.15
  - fare.included_km vs trip_intent.expected_distance_km weight 0.20
  - fare.late_return_per_hour_inr (lower)              weight 0.10
  - deposit.amount_inr (lower)                         weight 0.15
  - bundled gear (helmet+raincoat at 0 cost)           weight 0.10

HARD FILTERS:
  - fare.total_inr > preferences.budget_max_inr → drop

SAFETY

SIGNALS USED:
  - vehicle_meta.comprehensive_insurance               HARD FILTER
  - vehicle_meta.permit_kind = self_drive_permit       HARD FILTER
  - rider_qualification_for_user.user_meets_qualification HARD FILTER
  - gear_kit.rider_helmet_provided AND rider_helmet_isi_certified HARD FILTER
  - gear_kit.pillion_helmet_provided AND pillion_helmet_isi_certified  (if requested) HARD FILTER
  - gear_kit.knee_guards_provided                      weight 0.10
  - gear_kit.gloves_provided                           weight 0.10
  - vehicle_meta.helmet_strap_sensor                   weight 0.20
  - vehicle_meta.abs                                   weight 0.15
  - vehicle_meta.cbs (if no abs)                       weight 0.10
  - vehicle_meta.speed_limit_governed                  weight 0.10
  - vehicle_meta.fitness_certificate_valid_until_iso > scheduled_return_iso HARD FILTER
  - partner_safety_features.helmet_verification_at_pickup weight 0.15
  - partner_safety_features.roadside_assistance_24x7   weight 0.15
  - partner_safety_features.emergency_swap_vehicle     weight 0.10
  - vehicle_photos.pre_handover_photos_required=true   HARD FILTER
  - is_late_night_pickup → safety weight scales 1.5x

HARD FILTERS:
  - geared_endorsement_required AND license_endorsement_2w_with_gear=false → drop
  - is_overnight=true AND vehicle_kind in [scooter, electric_scooter] → drop  (city scooters not for overnight long-distance)
  - involves_highway=true AND vehicle.engine_cc < 100 → drop

Hidden ranking factor

information_completeness_score weight 0.10.


7. COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/{tomo_partner_id}
X-TOMO-Timestamp: <ms>
X-TOMO-Signature: sha256=<hex>

{
  "intent":            "mobility.book_two_wheeler_rental",
  "intent_version":    "v1.0.0",
  "external_id":       "BOUNCE-2W-XYZ",
  "amount_inr":         320,
  "closed_at":         "2026-05-10T18:14:00+05:30",
  "request_id":        "req_2w_5h7m_...",
  "status":            "completed",
  "booking_ref":       "BOUNCE-2W-XYZ",
  "picked_up_at":      "2026-05-10T09:08:00+05:30",
  "returned_at":       "2026-05-10T17:55:00+05:30",
  "km_driven":          38.4,
  "billed_hours_actual": 9,
  "fuel_top_up_charge_inr": 40,
  "damage_charge_inr":  0,
  "helmet_loss_charge_inr": 0,
  "late_return_charge_inr": 0,
  "deposit_refunded_inr": 1000,
  "incidents_reported": 0,
  "currency":          "INR",
  "fare_breakdown_total_inr": 320,
  "ratings_pending":     true,
  "notes":              ""
}

Status enum: completed | cancelled_by_user | cancelled_by_partner | failed | partial_completion_user_returned_early | extended_and_completed | terminated_with_damage_claim


8. WIDGET

WIDGET TYPE:        two_wheeler_options
SOURCE:             src/widgets/types.ts
TYPE NAME:          TwoWheelerOptionsPayload
RENDERED IN:        components/widgets/TwoWheelerOptionsWidget.tsx

Default: 3 stacked rows showing make+model+year, engine_cc badge or EV-range, fuel kind, fare with helmet/raincoat line callouts, included_km, hub address. Tap row → confirmation card with full fare breakdown + gear kit visual + DL upload widget → "Book". Then TwoWheelerHandoverCard with handover window + helmet size selection + condition photo gallery.


9. CACHING POLICY

Call TTL Rationale
get_two_wheeler_inventory 60s inventory turns over slowly
get_handover_state 0s always live during rental
book_two_wheeler 0s
cancel_two_wheeler 0s
extend_two_wheeler 0s
report_damage_or_incident 0s
complete_return 0s
Failure responses 0s

10. ERROR CODES

Code HTTP Meaning TOMO behavior
NO_INVENTORY 503 no vehicle matches retry or fall back
OUT_OF_SERVICE_AREA 400 pickup outside coverage surface
OPTION_EXPIRED 410 option_token invalid re-quote
USER_NOT_QUALIFIED 403 age/license/endorsement fails surface qualification_failure_reason
DL_VERIFICATION_FAILED 401 DL image rejected surface, retry
DL_2W_ENDORSEMENT_MISSING 403 user has only LMV; needs 2W surface
DEPOSIT_DECLINED 402 card block / hold failed surface
BOOKING_NOT_FOUND 404 booking_ref doesn't exist surface
ALREADY_CANCELLED 409 duplicate cancel idempotent return
EXTENSION_BLOCKED 409 reserved for next user surface alternate
GEOFENCE_BREACH 403 vehicle outside allowed states surface
HELMET_NOT_RETURNED 400 return without helmets surface helmet_loss_charge
INCIDENT_OPEN 409 active incident blocks state change surface
RETURN_LATE_BEYOND_GRACE 400 late return triggers extra surface fare delta

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All §2 inputs validated, request_id echoed
[ ] get_two_wheeler_inventory returns ≥3 options
[ ] All §4 required fields populated with REAL data
[ ] vehicle_meta truthful + verifiable on demand
[ ] insurance_company verifiable (IRDAI registered)
[ ] permit_kind = self_drive_permit verified per state RTO
[ ] geofence_states_allowed enforced server-side via telematics
[ ] book_two_wheeler returns booking_ref + handover window within SLA
[ ] DL verification flow works via DigiLocker / Parivahan
[ ] license_endorsement_2w gate enforced server-side
[ ] license_endorsement_2w_with_gear gate enforced for geared bikes
[ ] deposit hold tested with sandbox card
[ ] pre_handover_photos uploaded for every booking (≥4 angles)
[ ] gear_kit dispatched matching ordered config (helmet/raincoat/etc.)
[ ] rider_helmet_isi_certified verifiable via BIS standard
[ ] helmet_verification_at_pickup procedure shown to TOMO ops
[ ] helmet_strap_sensor data flows to telematics (sample data)
[ ] cancel respects free_cancel_until_iso
[ ] extend recomputes fare correctly
[ ] complete_return computes damage + late + helmet-loss charges deterministically
[ ] CPC webhook arrives within 60s of return completion
[ ] HMAC verification passes
[ ] No forbidden fields anywhere
[ ] SOS button tested with TOMO ops monitoring
[ ] Roadside assistance dispatched within SLA in dry-run
[ ] No paid placement / sponsored signals
[ ] customer_support_24x7 verified by TOMO field call
[ ] Self-drive permit certificate uploaded for every operating state

12. ANTI-FABRICATION RULES

RULE 1 — No paid placement signals.

RULE 2 — No fake ISI certification.
  rider_helmet_isi_certified=true requires BIS-certified helmet (IS 4151).
  Charging for ISI helmet but providing non-ISI = breach + safety violation.

RULE 3 — No hidden deposit.
  deposit.amount_inr declared up-front. Post-booking ask = breach.

RULE 4 — No undocumented helmet loss charge.
  helmet_loss_charge_inr disclosed at booking. Unilateral upgrade at return = breach.

RULE 5 — No fake helmet-strap telemetry.
  helmet_strap_sensor=true must be backed by actual sensor data. Charging
  helmet-strap-breach without sensor evidence = breach.

RULE 6 — No fake geofence enforcement.
  geofence_states_allowed must be enforced via telematics. Charging
  geofence-breach without telematics evidence = breach.

RULE 7 — Damage claims must be photo-backed.
  damage_charge_inr > 0 requires return_photos showing damage NOT in
  pre_handover_photos. Charging without photo evidence = breach.

RULE 8 — Late return grace honored.
  Stated grace (e.g., 15 min) cannot be reduced retroactively.

RULE 9 — Fuel policy honored.
  full_to_full means user returns at same fuel level. Charging fuel_top_up
  beyond actual delta = breach.

RULE 10 — No commission-based response shaping.

RULE 11 — Vehicle swap requires user consent.

RULE 12 — Speed-limit-governed claim must be testable.
  speed_limit_governed=true must actually electronically limit. Throttle-soft
  governance that user can bypass = breach.

RULE 13 — Helmet verification at pickup is non-negotiable.
  Partner staff MUST verify user fits helmet correctly before handover.
  Skipping for "speed" = safety breach.

VERSION HISTORY

v1.0.0 — 2026-05-10 — Initial spec