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_self_drive

mobility.book_self_drive — Full Intent Specification

INTENT NAMESPACE: mobility
INTENT NAME:      book_self_drive
FULL ID:          mobility.book_self_drive
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

Self-drive differs from ride-with-driver in five locked ways: (a) user IS the driver — no driver_meta/driver_kyc block; instead the spec captures driver-license verification of the USER; (b) duration is billed in hours/days, not minutes; (c) deposit + CDW + insurance excess + fuel policy are first-class fields; (d) pickup/drop are vehicle hub locations, not passenger origins; (e) condition photos at pickup/return are protocol-mandatory.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "self-drive Innova for the weekend"
  • "rent a car for 3 days"
  • "Zoomcar Swift in Hyderabad tomorrow"
  • "self-drive automatic SUV Saturday morning"
  • "I want to rent a car, drive myself"
  • "weekend road trip car self-drive"
  • "self drive car for 24 hours"
  • "rent EV for the day Hyderabad"
  • "Mahindra Thar self-drive Friday to Sunday"

Classifies OUT — borderline NO

  • "cab to airport" → mobility.book_intracity_ride
  • "Hyderabad to Bangalore cab" → mobility.book_intercity_ride
  • "rent scooter for the day" → mobility.book_two_wheeler_rental
  • "chauffeur for full day" → mobility.book_chauffeur_hourly
  • "Goa package with cab" → mobility.book_outstation_package
  • "I want to buy a car" → no intent (informational; NOT a TOMO action)

MULTI-INTENT TRIGGERS

  • "self-drive car Friday to Sunday and book hotel in Coorg" → mobility.book_self_drive + travel.book_hotel
  • "rent SUV for the weekend and load FASTag" → mobility.book_self_drive + pay.fastag_topup
  • "self-drive car and verify my DL" → mobility.book_self_drive + compliance.verify_rc_dl

2. INPUT — TOMO → PROVIDER

{
  "intent":          "mobility.book_self_drive",
  "intent_version":  "v1.0.0",
  "request_id":      "req_sd_3k9p_2026-05-10T07:00:00Z",
  "user_session_id": "anon_user_token_or_uid",

  "pickup_location": {
    "lat":            17.4435,
    "lng":            78.3772,
    "address":        "Zoomcar Hub Madhapur",
    "neighborhood":   "Madhapur",
    "city":           "Hyderabad",
    "pincode":        "500081",
    "state_code":     "TS",
    "country_code":   "IN",
    "hub_kind":       "branded_hub",
    "instructions":   "Use entrance from main road"
  },

  "drop_location": {
    "lat":            17.4435,
    "lng":            78.3772,
    "address":        "Zoomcar 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-11T08:00:00+05:30",
  "scheduled_return_iso": "2026-05-13T20:00:00+05:30",
  "billed_hours":         60,
  "billed_days":          3,

  "user_driving_profile": {
    "user_age":                       28,
    "license_state_code":             "TS",
    "license_class_includes_lmv":     true,
    "license_held_for_years":         6,
    "license_endorsement_2w":         false,
    "license_verified_with_partner":  false,
    "prior_self_drive_count":         3,
    "international_license":          false
  },

  "preferences": {
    "vehicle_kinds_acceptable":   ["sedan", "suv", "hatchback", "ev_suv"],
    "transmission_acceptable":    ["manual", "automatic"],
    "transmission_preferred":     "automatic",
    "fuel_kinds_acceptable":      ["petrol", "diesel", "ev_full"],
    "budget_band":                "good",
    "budget_max_inr":              8500,
    "ac_required":                 true,
    "min_seat_capacity":           5,
    "doorstep_delivery_required":  false,
    "with_fuel_topped":            true,
    "include_super_cdw":           true,
    "include_roadside_assistance": true
  },

  "trip_intent": {
    "expected_distance_km":      350,
    "expected_states_crossed":   ["TS", "AP"],
    "involves_highway":          true,
    "involves_offroad":          false,
    "is_overnight":              true,
    "is_late_night_pickup":      false,
    "purpose":                   "leisure"
  },

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trust_signals": {
      "is_repeat_customer":          true,
      "prior_self_drive_with_partner": 1,
      "user_account_age_days":       312,
      "fastag_present":              true,
      "fastag_balance_inr":          1840
    }
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "mobility.book_self_drive"
pickup_location.hub_kind enum REQUIRED, see §5
drop_location object REQUIRED same shape as pickup; can equal pickup for round_trip
rental_kind enum REQUIRED, STRICT round_trip | one_way
scheduled_pickup_iso ISO_DATETIME REQUIRED
scheduled_return_iso ISO_DATETIME REQUIRED must be > pickup
billed_hours int REQUIRED, ≥1
billed_days int REQUIRED, ≥0 for daily-rate intents
user_driving_profile.user_age int REQUIRED, ≥18 partners may impose higher floor
user_driving_profile.license_state_code string REQUIRED
user_driving_profile.license_class_includes_lmv bool REQUIRED HARD FILTER if false
user_driving_profile.license_held_for_years int REQUIRED, ≥0
user_driving_profile.license_verified_with_partner bool REQUIRED drives DL re-verify flow
preferences.transmission_acceptable array REQUIRED, ≥1
preferences.transmission_preferred enum REQUIRED
preferences.with_fuel_topped bool REQUIRED full-to-full vs as-is
preferences.include_super_cdw bool REQUIRED excess waiver
preferences.include_roadside_assistance bool REQUIRED
trip_intent.expected_states_crossed array REQUIRED, ≥1 drives interstate-permit gating
trip_intent.involves_offroad bool REQUIRED drives vehicle-fitness filter

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


3. PROVIDER TOOLS

Tool 1: get_self_drive_inventory

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

Tool 2: book_self_drive

PURPOSE:        commit a vehicle 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 }
SLA:            p95 < 5000ms
IDEMPOTENCY:    REQUIRED on idempotency_key

Tool 3: get_handover_state

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

Tool 4: cancel_self_drive

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_self_drive

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, request_id }
OUTPUT:         { final_fare_inr, deposit_refund_inr, damage_charge_inr, late_return_charge_inr, status }
SLA:            p95 < 3000ms

All seven REQUIRED.


4. RESPONSE SHAPE

SelfDriveOption (returned by get_self_drive_inventory)

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

vehicle_kind:                     STRICT ENUM, REQUIRED
vehicle_class:                    STRICT ENUM, REQUIRED
display_label:                    string, REQUIRED            # "Maruti Swift VXi 2024"
make:                             string, REQUIRED
model:                            string, REQUIRED
variant:                          string, REQUIRED
year_of_manufacture:              int, REQUIRED
seat_capacity:                    int, REQUIRED, ≥1
luggage_capacity:                 STRICT ENUM, REQUIRED
transmission:                     STRICT ENUM, REQUIRED       # manual | automatic | amt | cvt | dct

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       # 0 if not requested
  hub_open_iso:                   ISO_DATETIME, REQUIRED      # earliest pickup today
  hub_close_iso:                  ISO_DATETIME, REQUIRED      # latest pickup today
  vehicle_ready_at_iso:           ISO_DATETIME, REQUIRED
  alternate_pickup_hubs:          array, REQUIRED, may be empty

fare:
  total_inr:                      INR_INTEGER, REQUIRED       # full rental cost
  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               # km cap before overage
  per_km_overage_inr:             float, REQUIRED
  fuel_policy:                    STRICT ENUM, REQUIRED       # see §5
  fuel_top_up_charge_inr:         INR_INTEGER, REQUIRED       # 0 if user returns full
  basic_insurance_inr:            INR_INTEGER, REQUIRED       # always REQUIRED, never bundled silently
  super_cdw_inr:                  INR_INTEGER, REQUIRED       # 0 if not selected
  excess_amount_inr:              INR_INTEGER, REQUIRED       # max user liability if super_cdw not taken
  roadside_assistance_inr:        INR_INTEGER, REQUIRED       # 0 if not selected
  late_return_per_hour_inr:       INR_INTEGER, REQUIRED
  doorstep_delivery_inr:          INR_INTEGER, REQUIRED       # 0 if not requested
  one_way_drop_charge_inr:        INR_INTEGER, REQUIRED       # 0 if round_trip
  state_border_surcharge_inr:     INR_INTEGER, REQUIRED       # 0 if same-state only
  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       # 0 if not required
  hold_kind:                      STRICT ENUM, REQUIRED       # card_block | bank_transfer | wallet_hold | none
  refund_eta_business_days:       int, REQUIRED, ≥0

vehicle_amenities:
  ac:                             boolean, REQUIRED
  music_system:                   boolean, REQUIRED
  bluetooth:                      boolean, REQUIRED
  charging_port_usb_c:            boolean, REQUIRED
  charging_port_12v:              boolean, REQUIRED
  android_auto_carplay:           boolean, REQUIRED
  reverse_camera:                 boolean, REQUIRED
  parking_sensors:                boolean, REQUIRED
  cruise_control:                 boolean, REQUIRED
  abs:                            boolean, REQUIRED
  airbags_count:                  int, REQUIRED, ≥0
  ebd:                            boolean, REQUIRED
  esp:                            boolean, REQUIRED
  hill_assist:                    boolean, REQUIRED
  sunroof:                        boolean, REQUIRED
  spare_tyre_present:             boolean, REQUIRED
  first_aid_kit_present:          boolean, REQUIRED
  fire_extinguisher_present:      boolean, REQUIRED
  reflective_triangle_present:    boolean, REQUIRED
  child_seat_anchor_isofix:       boolean, REQUIRED
  toolkit_present:                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 (per state RTO)
  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       # see §5
  geofence_states_allowed:        array<string>, REQUIRED, ≥1
  speed_limit_governed:           boolean, REQUIRED
  speed_limit_kmph:               int, REQUIRED               # 0 if not governed
  telematics_active:              boolean, REQUIRED

vehicle_photos:
  pre_handover_photos_required:   boolean, REQUIRED           # always true
  pre_handover_photos:            array<URL>, REQUIRED        # at least 6 angles
  pre_handover_photos_iso:        ISO_DATETIME, REQUIRED
  damage_marker_present_at_pickup: boolean, REQUIRED          # honest reporting

driver_qualification_for_user:
  min_user_age_required:          int, REQUIRED, ≥18
  min_license_held_years:         int, REQUIRED, ≥0
  international_license_accepted: boolean, REQUIRED
  user_meets_qualification:       boolean, REQUIRED           # derived from §2 input
  qualification_failure_reason:   STRICT ENUM, REQUIRED       # "none" if user_meets_qualification

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           # speed/geofence/late
  roadside_assistance_24x7:       boolean, REQUIRED
  on_call_mechanic:               boolean, REQUIRED
  emergency_swap_vehicle:         boolean, REQUIRED

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_self_drive_volume_30d:  int, REQUIRED, ≥0

SelfDriveHandoverState (returned by get_handover_state)

booking_ref:                      string, REQUIRED
status:                           STRICT ENUM, REQUIRED       # see §5
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

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      # may equal future iso if pending
  verification_method:            STRICT ENUM, REQUIRED       # digilocker | parivahan | manual_kyc | none

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_excess_inr | fake_geofence_states_allowed

5. CONTROLLED VOCABULARIES

vehicle_kind

hatchback | sedan | suv | mini_suv | premium_sedan | premium_suv |
luxury_sedan | luxury_suv | mpv | offroad_4x4 | convertible |
ev_hatchback | ev_sedan | ev_suv | xl_van

vehicle_class

economy | comfort | premium | luxury | offroad

SelfDriveOption.luggage_capacity

small | medium | large | xl

transmission

manual | automatic | amt | cvt | dct

pickup_location.hub_kind and drop_location.hub_kind

branded_hub | partner_dealership | airport_pickup_zone | mall_parking |
metro_station_zone | 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 | diesel | cng | lpg | ev_full | hybrid | bs6_petrol | bs6_diesel

vehicle_meta.emission_norm

bs3 | bs4 | bs6 | ev | unknown_legacy

vehicle_meta.permit_kind

self_drive_permit | tourist | private

Self-drive REQUIRES self_drive_permit (state-RTO-issued for self-drive operators).

vehicle_meta.vehicle_class_certification

private | self_drive_yellow_plate

vehicle_meta.geofence_kind

none | city_only | state_only | multi_state_specified | nationwide

deposit.hold_kind

card_block | bank_transfer | wallet_hold | none

driver_qualification_for_user.qualification_failure_reason

none | user_age_below_minimum | license_held_too_short |
license_class_invalid | license_expired | foreign_license_not_accepted |
prior_incident_block

SelfDriveHandoverState.status

booked_pending_pickup | dl_verification_required | dl_verified |
ready_for_pickup | 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 | other

cancel_self_drive.reason

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

6. TTBS DIMENSIONS

Per-domain weights (locked; self-drive override)

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

Time weight drops (no driver-ETA matters; only hub-availability window matters). Taste rises (vehicle choice is the experience).

TIME

SIGNALS USED:
  - availability.vehicle_ready_at_iso ≤ pickup_iso     HARD FILTER
  - availability.doorstep_delivery_available           weight 0.20  (if user requested)
  - 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+30min
  - flexible window → above filter relaxed

TASTE

SIGNALS USED:
  - vehicle.year_of_manufacture (newer = better)       weight 0.25
  - vehicle.transmission match preferred               weight 0.20
  - vehicle_amenities match w/ user prefs              weight 0.30
  - vehicle.make/model affinity (DNA-personalized)     weight 0.15
  - vehicle_amenities.android_auto_carplay (for music) weight 0.10

HARD FILTERS:
  - ac_required + ac=false → drop
  - transmission_acceptable doesn't include vehicle.transmission → drop
  - fuel_kinds_acceptable doesn't include vehicle_meta.fuel_kind → drop

BUDGET

SIGNALS USED:
  - fare.total_inr vs band:
      ok    → hatchback / mini_suv / older sedans
      good  → sedan / suv / EV economy
      great → premium_suv / luxury_sedan / convertible
  - fare.is_upfront_fare=true (locked = better)        weight 0.15
  - fare.included_km vs trip_intent.expected_distance_km (more = better) weight 0.20
  - fare.late_return_per_hour_inr (lower = better)     weight 0.10
  - deposit.amount_inr (lower = better, all else equal) weight 0.15
  - super_cdw_inr included in budget (yes if user opted) weight 0.10

HARD FILTERS:
  - fare.total_inr > preferences.budget_max_inr → drop
  - deposit.required AND deposit.amount_inr > 50,000 → soft warning, not drop

SAFETY

SIGNALS USED:
  - vehicle_meta.comprehensive_insurance               HARD FILTER
  - vehicle_meta.permit_kind = self_drive_permit       HARD FILTER
  - vehicle_meta.geofence_states_allowed ⊇ trip_intent.expected_states_crossed HARD FILTER
  - driver_qualification_for_user.user_meets_qualification HARD FILTER
  - vehicle_amenities.airbags_count (≥2 minimum)       weight 0.10
  - vehicle_amenities.abs                              weight 0.10
  - vehicle_amenities.esp                              weight 0.05
  - vehicle_amenities.reverse_camera                   weight 0.05
  - vehicle_amenities.first_aid_kit_present            weight 0.05
  - vehicle_amenities.fire_extinguisher_present        weight 0.05
  - vehicle_amenities.spare_tyre_present               weight 0.10
  - partner_safety_features.roadside_assistance_24x7   weight 0.15
  - partner_safety_features.on_call_mechanic           weight 0.05
  - partner_safety_features.emergency_swap_vehicle     weight 0.10
  - vehicle_photos.pre_handover_photos_required=true   HARD FILTER
  - vehicle_meta.fitness_certificate_valid_until_iso > scheduled_return_iso HARD FILTER
  - is_overnight → safety weight scales 1.4x
  - involves_offroad → vehicle_class must include offroad-capable

HARD FILTERS:
  - user_meets_qualification=false → drop
  - involves_offroad=true AND vehicle_class != offroad → drop

Hidden ranking factor

information_completeness_score weight 0.10.

FASTag check

If trip_intent.expected_distance_km > 50 AND any expected_states_crossed involves toll roads, surface FASTag balance check at option-card level. User holds the FASTag; partner does not own toll responsibility for self-drive.


7. COMPLETION CONTRACT

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

{
  "intent":            "mobility.book_self_drive",
  "intent_version":    "v1.0.0",
  "external_id":       "ZOOMCAR-SD-XYZ",
  "amount_inr":         8240,
  "closed_at":         "2026-05-13T20:14:00+05:30",
  "request_id":        "req_sd_3k9p_...",
  "status":            "completed",
  "booking_ref":       "ZOOMCAR-SD-XYZ",
  "picked_up_at":      "2026-05-11T08:12:00+05:30",
  "returned_at":       "2026-05-13T19:48:00+05:30",
  "km_driven":          312.4,
  "billed_hours_actual": 60,
  "fuel_top_up_charge_inr": 280,
  "damage_charge_inr":  0,
  "late_return_charge_inr": 0,
  "deposit_refunded_inr": 5000,
  "states_traversed":   ["TS", "AP"],
  "tolls_user_paid_inr": 645,
  "incidents_reported": 0,
  "currency":          "INR",
  "fare_breakdown_total_inr": 8240,
  "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:        self_drive_options
SOURCE:             src/widgets/types.ts
TYPE NAME:          SelfDriveOptionsPayload
RENDERED IN:        components/widgets/SelfDriveOptionsWidget.tsx

Default: 3 stacked rows showing make+model+year, transmission badge, fuel kind, fare with deposit + cdw line callouts, included_km, hub address. Tap row → confirmation card with full fare breakdown including deposit + cdw + roadside + late-return rates → DL upload widget → "Book". Then SelfDriveHandoverCard with handover window + condition photo gallery.


9. CACHING POLICY

Call TTL Rationale
get_self_drive_inventory 60s inventory turns over slowly during day
get_handover_state 0s always live during rental
book_self_drive 0s
cancel_self_drive 0s
extend_self_drive 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 at requested window 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 user fails age/license requirements surface qualification_failure_reason
DL_VERIFICATION_FAILED 401 DL image rejected surface, allow retry
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 vehicle reserved for next user surface alternate vehicle
GEOFENCE_BREACH 403 vehicle outside allowed states surface, partner enforces
INCIDENT_OPEN 409 active incident blocks state change surface
RETURN_LATE_BEYOND_GRACE 400 late return triggers extra charge surface fare delta

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All §2 inputs validated, request_id echoed
[ ] get_self_drive_inventory returns ≥3 options for "Hyderabad weekend, sedan, automatic"
[ ] 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_self_drive returns booking_ref + handover window within SLA
[ ] DL verification flow works via DigiLocker or Parivahan
[ ] deposit hold tested with sandbox card
[ ] pre_handover_photos uploaded for every booking (≥6 angles)
[ ] get_handover_state returns vehicle location ≤30s old
[ ] cancel respects free_cancel_until_iso
[ ] extend recomputes fare correctly
[ ] complete_return computes damage + late 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 vehicle photos.
  pre_handover_photos must show the actual vehicle handed over. AI-generated
  photos forbidden. TOMO samples and re-photographs at random.

RULE 3 — No hidden deposit.
  deposit.amount_inr declared up-front in option. Any post-booking deposit
  ask = breach.

RULE 4 — No undocumented excess.
  excess_amount_inr is the maximum user liability if super_cdw not taken.
  Charges beyond declared excess for non-fault incidents = breach.

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

RULE 6 — No fake DL re-verification loops.
  Once DL is verified via DigiLocker or Parivahan, re-verification within 90
  days is a UX regression. Partners who block flow with redundant verification
  = 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 is enforced.
  partner-stated grace window (e.g., 30 min after scheduled_return) cannot be
  reduced retroactively. Charging within stated grace = breach.

RULE 9 — Fuel policy must be honored.
  full_to_full means user returns at same fuel level as pickup. Partner
  charging fuel_top_up beyond actual delta = breach.

RULE 10 — No commission-based response shaping.
  Same vehicle + window on TOMO and on partner's app must yield same fare.

RULE 11 — Vehicle swap requires user consent.
  If partner swaps the assigned vehicle pre-pickup, user must be notified
  and accept. Silent swap = breach.

RULE 12 — Geofence breach charge requires user-visible warning first.
  Telematics must alert user 1km before geofence boundary. Charging without
  prior warning = breach.

VERSION HISTORY

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