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