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_recurring_commute

mobility.book_recurring_commute — Full Intent Specification

INTENT NAMESPACE: mobility
INTENT NAME:      book_recurring_commute
FULL ID:          mobility.book_recurring_commute
VERSION:          v1.0.0
STATUS:           draft (V2 marker)
LAST UPDATED:     2026-05-10
TTBS WEIGHTS:     time 0.40 · taste 0.20 · budget 0.30 · safety 0.10

Recurring commute is a subscription-style intent for daily/weekly rides between two fixed locations, on a defined schedule, billed periodically. It differs from per-ride booking in five locked ways: (a) recurrence_pattern defines the schedule; (b) billing_period aggregates many rides into one charge; (c) assigned_driver_kind supports dedicated for repeat-customer comfort; (d) pause/resume + skip-day are first-class operations; (e) safety weight is LOWER than other mobility (0.10) — the user has already vetted this pattern + driver across N prior rides; taste rises (driver familiarity is what users actually buy here). V2 status marker — partner integrations should be cautious until at least 1 partner ships this end-to-end.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "subscribe to daily ride to office"
  • "recurring commute Mon-Fri to HITEC City"
  • "book a daily cab to office for the month"
  • "monthly cab subscription home to office"
  • "set up weekday commute rides"
  • "every morning ride to work, can you book it"
  • "fixed driver for daily commute"
  • "weekly cab pass for office"
  • "shuttle subscription Madhapur to Banjara Hills"

Classifies OUT — borderline NO

  • "cab to office today" → mobility.book_intracity_ride (one-off)
  • "rent car for the week" → mobility.book_self_drive
  • "8h chauffeur block" → mobility.book_chauffeur_hourly
  • "school bus subscription" → no mobility intent (logistics/school-bus is a different domain)

MULTI-INTENT TRIGGERS

  • "daily commute subscription and FASTag top-up" → mobility.book_recurring_commute + pay.fastag_topup
  • "monthly cab pass to office and verify my KYC" → mobility.book_recurring_commute + compliance.verify_kyc

2. INPUT — TOMO → PROVIDER

{
  "intent":          "mobility.book_recurring_commute",
  "intent_version":  "v1.0.0",
  "request_id":      "req_rec_2k7p_2026-05-10T08:00:00Z",
  "user_session_id": "anon_user_token_or_uid",

  "subscription_kind": "monthly_weekday",

  "pickup": {
    "lat":            17.4435,
    "lng":            78.3772,
    "address":        "Near Madhapur metro station",
    "neighborhood":   "Madhapur",
    "city":           "Hyderabad",
    "pincode":        "500081",
    "state_code":     "TS",
    "country_code":   "IN",
    "place_kind":     "home",
    "instructions":   "Pickup at building gate B"
  },

  "drop": {
    "lat":            17.4124,
    "lng":            78.4470,
    "address":        "Hitex Trade Center, Banjara Hills",
    "neighborhood":   "Banjara Hills",
    "city":           "Hyderabad",
    "pincode":        "500034",
    "state_code":     "TS",
    "country_code":   "IN",
    "place_kind":     "office",
    "instructions":   "Office gate 3"
  },

  "recurrence": {
    "pattern":               "mon_to_fri",
    "ride_kind_per_day":     "round_trip",
    "morning_pickup_window": {
      "start_iso_local":     "08:30:00",
      "end_iso_local":       "09:00:00"
    },
    "evening_pickup_window": {
      "start_iso_local":     "18:30:00",
      "end_iso_local":       "19:30:00"
    },
    "skip_dates":            ["2026-05-15", "2026-05-22"],
    "active_from_iso":       "2026-05-12",
    "active_until_iso":      "2026-06-12"
  },

  "billing": {
    "period_kind":           "monthly",
    "auto_renew":            true,
    "billing_day_of_month":  10
  },

  "assigned_driver_kind":    "preferred_pool",
  "preferred_driver_id":     "",

  "party": {
    "passenger_count":  1,
    "luggage_pieces":   1,
    "luggage_size":     "backpack"
  },

  "preferences": {
    "vehicle_kinds_acceptable":   ["sedan", "hatchback"],
    "vehicle_kinds_preferred":    ["sedan"],
    "budget_band":                "good",
    "budget_max_inr_per_period":  18000,
    "max_eta_minutes":             5,
    "ac_required":                 true,
    "female_driver_required":      false,
    "ev_only":                     false,
    "wheelchair_accessible_required": false,
    "no_pool":                     true,
    "driver_must_speak_locales":   ["en-IN", "te-IN"],
    "max_seat_capacity_min":       4
  },

  "route_context": {
    "estimated_distance_km":   18.5,
    "estimated_duration_min":  35,
    "involves_toll":           false,
    "involves_highway":        false,
    "is_late_night":           false
  },

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trip_purpose":         "commute_to_work",
    "trust_signals": {
      "is_repeat_customer":          true,
      "prior_rides_with_partner":    47,
      "user_account_age_days":       312,
      "fastag_present":              false,
      "fastag_balance_inr":          0
    }
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "mobility.book_recurring_commute"
subscription_kind enum REQUIRED, see §5
pickup.place_kind enum REQUIRED
drop.place_kind enum REQUIRED
recurrence.pattern enum REQUIRED, see §5
recurrence.ride_kind_per_day enum REQUIRED, STRICT one_way_morning | one_way_evening | round_trip
recurrence.morning_pickup_window.start_iso_local string REQUIRED, "HH:MM:SS"
recurrence.morning_pickup_window.end_iso_local string REQUIRED
recurrence.evening_pickup_window object REQUIRED, may be empty if one_way_morning
recurrence.skip_dates array REQUIRED, may be empty
recurrence.active_from_iso ISO_DATE REQUIRED
recurrence.active_until_iso ISO_DATE REQUIRED must be > active_from
billing.period_kind enum REQUIRED, STRICT weekly | monthly | quarterly
billing.auto_renew bool REQUIRED
billing.billing_day_of_month int REQUIRED, 1-31 drives charge cycle
assigned_driver_kind enum REQUIRED, see §5
preferred_driver_id string REQUIRED, may be empty only relevant if dedicated
preferences.budget_max_inr_per_period INR_INTEGER REQUIRED matches billing.period_kind
preferences.max_eta_minutes int REQUIRED, ≥0 per-ride pickup tolerance

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


3. PROVIDER TOOLS

Tool 1: get_recurring_commute_estimates

PURPOSE:        return subscription options across vehicle kinds + driver kinds
INPUT:          §2 request body
OUTPUT:         { options: RecurringCommuteOption[], result_token, expires_at }
SLA:            p50 < 800ms, p95 < 1500ms
RATE LIMIT:     ≤ 1/sec per user

Tool 2: book_recurring_commute

PURPOSE:        commit a subscription
INPUT:          { option_id, payment_token, request_id, idempotency_key, user_phone, otp_required }
OUTPUT:         { subscription_ref, status, fare_quote, first_ride_iso, dedicated_driver_assigned, billing_setup_status }
SLA:            p95 < 5000ms
IDEMPOTENCY:    REQUIRED on idempotency_key

Tool 3: get_subscription_state

PURPOSE:        live subscription state with rides-this-period summary
INPUT:          { subscription_ref, request_id }
OUTPUT:         RecurringCommuteState (§4)
SLA:            p95 < 600ms
RATE LIMIT:     ≤ 1 every 60s

Tool 4: pause_subscription

PURPOSE:        pause the subscription temporarily
INPUT:          { subscription_ref, pause_from_iso, pause_until_iso, reason, request_id }
OUTPUT:         { status, billing_credit_inr, resume_iso }
SLA:            p95 < 1500ms

Tool 5: resume_subscription

PURPOSE:        resume a paused subscription
INPUT:          { subscription_ref, resume_from_iso, request_id }
OUTPUT:         { status, next_billing_iso, next_ride_iso }
SLA:            p95 < 1500ms

Tool 6: skip_ride_for_date

PURPOSE:        user skips a single day
INPUT:          { subscription_ref, skip_date_iso, request_id }
OUTPUT:         { status, billing_adjustment_inr }
SLA:            p95 < 1000ms

Tool 7: update_subscription_window

PURPOSE:        modify pickup window (e.g., user shifts WFH)
INPUT:          { subscription_ref, new_morning_window, new_evening_window, request_id }
OUTPUT:         { status, effective_from_iso, fare_delta_inr }
SLA:            p95 < 1500ms

Tool 8: cancel_subscription

PURPOSE:        cancel
INPUT:          { subscription_ref, reason, cancel_immediately, request_id }
OUTPUT:         { status, refund_amount_inr, last_ride_iso, refund_processing_days }
SLA:            p95 < 2000ms

Tool 9: get_ride_for_date

PURPOSE:        get the ride record for a specific date (today's or upcoming)
INPUT:          { subscription_ref, date_iso, leg_kind, request_id }
OUTPUT:         { ride_ref, driver, vehicle, eta, status }
SLA:            p95 < 600ms

Tool 10: track_ride

PURPOSE:        live tracking of an active ride within the subscription
INPUT:          { ride_ref, request_id }
OUTPUT:         RideTrack (same shape as mobility.book_intracity_ride §4)
SLA:            p95 < 400ms

All ten REQUIRED.


4. RESPONSE SHAPE

RecurringCommuteOption (returned by get_recurring_commute_estimates)

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

subscription_kind:                STRICT ENUM, REQUIRED
vehicle_kind:                     STRICT ENUM, REQUIRED       # see §5
vehicle_class:                    STRICT ENUM, REQUIRED
display_label:                    string, REQUIRED            # "Daily Sedan Pass"
seat_capacity:                    int, REQUIRED, ≥1
luggage_capacity:                 STRICT ENUM, REQUIRED

ride_summary:
  expected_rides_in_period:       int, REQUIRED, ≥1
  rides_per_week:                 float, REQUIRED, ≥0
  total_period_distance_km:       float, REQUIRED
  per_ride_distance_km:           float, REQUIRED

assigned_driver:
  kind:                           STRICT ENUM, REQUIRED       # any | preferred_pool | dedicated
  dedicated_driver_id:            string, REQUIRED            # may be empty
  dedicated_driver_name:          string, REQUIRED            # may be empty
  pool_size:                      int, REQUIRED, ≥0           # 0 if dedicated
  fallback_to_pool_on_unavailable: boolean, REQUIRED

fare:
  total_period_inr:               INR_INTEGER, REQUIRED
  per_ride_avg_inr:               INR_INTEGER, REQUIRED
  base_per_ride_inr:              INR_INTEGER, REQUIRED       # standalone-equivalent rate
  subscription_discount_pct:      float, REQUIRED, 0-1        # e.g., 0.15 = 15% off
  toll_inr_per_period:            INR_INTEGER, REQUIRED
  ac_charge_inr_per_period:       INR_INTEGER, REQUIRED
  late_night_charge_inr_per_period: INR_INTEGER, REQUIRED
  platform_fee_inr_per_period:    INR_INTEGER, REQUIRED
  gst_inr_per_period:             INR_INTEGER, REQUIRED
  fare_breakdown_text:            string, REQUIRED
  is_upfront_fare:                boolean, REQUIRED            # locked for the period
  fare_locked_until_iso:          ISO_DATETIME, REQUIRED

billing:
  period_kind:                    STRICT ENUM, REQUIRED
  charge_iso:                     ISO_DATETIME, REQUIRED       # next charge
  payment_method_required:        STRICT ENUM, REQUIRED        # see §5
  prorated_first_period:          boolean, REQUIRED
  proration_method:               STRICT ENUM, REQUIRED
  refund_policy:                  STRICT ENUM, REQUIRED        # see §5

vehicle_amenities:
  ac:                             boolean, REQUIRED
  music_on_demand:                boolean, REQUIRED
  charging_port:                  boolean, REQUIRED
  wifi_in_cab:                    boolean, REQUIRED
  bottled_water:                  boolean, REQUIRED
  newspaper:                      boolean, REQUIRED
  pet_friendly:                   boolean, REQUIRED
  child_seat_available:           boolean, REQUIRED
  child_seat_kind:                STRICT ENUM, REQUIRED
  wheelchair_accessible:          boolean, REQUIRED

vehicle_meta:
  age_years:                      int, REQUIRED
  fuel_kind:                      STRICT ENUM, REQUIRED
  ev_battery_charge_pct:          int, REQUIRED, 0-100
  emission_norm:                  STRICT ENUM, REQUIRED
  registration_state_code:        string, REQUIRED
  vehicle_class_certification:    STRICT ENUM, REQUIRED
  comprehensive_insurance:        boolean, REQUIRED
  insurance_valid_until_iso:      ISO_DATE, REQUIRED
  fitness_certificate_valid_until_iso: ISO_DATE, REQUIRED
  permit_kind:                    STRICT ENUM, REQUIRED
  vehicle_color:                  string, REQUIRED

driver_meta:
  driver_id:                      string, REQUIRED
  display_name:                   string, REQUIRED
  photo_url:                      URL, REQUIRED
  rating_avg:                     float, REQUIRED, 0-5
  rides_completed_total:          int, REQUIRED, ≥0
  recurring_subscriptions_served: int, REQUIRED, ≥0
  partner_account_age_days:       int, REQUIRED, ≥0
  languages_spoken:               array<RFC_3066_LOCALE>, REQUIRED, ≥1
  female_driver:                  boolean, REQUIRED
  age_band:                       STRICT ENUM, REQUIRED
  experience_years_driving:       int, REQUIRED, ≥0
  on_time_arrival_rate_30d:       float, REQUIRED, 0-1

driver_kyc:
  dl_verified:                    boolean, REQUIRED
  dl_number_masked:               string, REQUIRED
  dl_valid_until_iso:             ISO_DATE, REQUIRED
  rc_verified:                    boolean, REQUIRED
  aadhaar_verified:               boolean, REQUIRED
  pan_verified:                   boolean, REQUIRED
  background_check_passed:        boolean, REQUIRED
  background_check_iso:           ISO_DATETIME, REQUIRED
  badge_id_displayed:             boolean, REQUIRED

safety_features:
  sos_button_in_app:              boolean, REQUIRED
  trip_share_supported:           boolean, REQUIRED
  in_app_chat_supported:          boolean, REQUIRED
  in_app_call_supported:          boolean, REQUIRED
  emergency_contact_alerts:       boolean, REQUIRED
  cctv_in_cab:                    boolean, REQUIRED
  panic_alert_to_local_police:    boolean, REQUIRED
  driver_drowsiness_detection:    boolean, REQUIRED
  speed_limit_governing:          boolean, REQUIRED

cancellation:
  free_cancel_within_first_n_rides: int, REQUIRED, ≥0          # grace period
  cancel_charge_inr_per_remaining_period: INR_INTEGER, REQUIRED
  exit_charge_inr:                INR_INTEGER, REQUIRED        # one-time at cancel
  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
  in_app_chat_supported:          boolean, REQUIRED
  partner_recurring_subscriptions_served_30d: int, REQUIRED, ≥0
  partner_subscription_completion_rate_30d:   float, REQUIRED, 0-1

RecurringCommuteState (returned by get_subscription_state)

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

period_summary:
  current_period_start_iso:       ISO_DATETIME, REQUIRED
  current_period_end_iso:         ISO_DATETIME, REQUIRED
  rides_completed_this_period:    int, REQUIRED, ≥0
  rides_skipped_by_user:          int, REQUIRED, ≥0
  rides_failed_by_partner:        int, REQUIRED, ≥0
  rides_remaining_this_period:    int, REQUIRED, ≥0
  current_period_inr_charged:     INR_INTEGER, REQUIRED, ≥0
  next_period_inr:                INR_INTEGER, REQUIRED

driver_assignment:
  current_driver_id:              string, REQUIRED
  current_driver_name:            string, REQUIRED
  is_dedicated:                   boolean, REQUIRED
  consecutive_rides_with_driver:  int, REQUIRED, ≥0

next_ride:
  date_iso:                       ISO_DATE, REQUIRED
  leg_kind:                       STRICT ENUM, REQUIRED        # morning | evening
  pickup_window_start_iso:        ISO_DATETIME, REQUIRED
  pickup_window_end_iso:          ISO_DATETIME, REQUIRED
  expected_driver_id:             string, REQUIRED
  expected_vehicle_label:         string, REQUIRED

paused:
  is_paused:                      boolean, REQUIRED
  paused_from_iso:                ISO_DATETIME, REQUIRED       # may equal active iso
  paused_until_iso:               ISO_DATETIME, REQUIRED       # may equal active iso

billing_state:
  next_charge_iso:                ISO_DATETIME, REQUIRED
  payment_method_status:          STRICT ENUM, REQUIRED        # see §5
  auto_renew_active:              boolean, 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_recurring_subscriptions_served | partner_paid_for_top_listing |
fake_subscription_completion_rate_30d | hidden_exit_charge_inr |
silently_charged_skipped_rides

5. CONTROLLED VOCABULARIES

subscription_kind

weekly_weekday | weekly_full | monthly_weekday | monthly_full |
quarterly_weekday | quarterly_full | custom

vehicle_kind

hatchback | sedan | suv | premium_sedan | ev_hatchback | ev_sedan

Recurring is typically economy/comfort tier — premium and luxury are unusual but allowed.

vehicle_class

economy | comfort | premium

RecurringCommuteOption.luggage_capacity

small | medium | large

pickup.place_kind and drop.place_kind

home | office | school | college | gym | mall | landmark | other

recurrence.pattern

mon_to_fri | mon_to_sat | every_day | mon_wed_fri | tue_thu | weekends_only |
custom_days

If custom_days, partner provides additional recurrence.custom_days_array field listing weekday numbers (0=Mon, 6=Sun).

recurrence.ride_kind_per_day

one_way_morning | one_way_evening | round_trip

assigned_driver_kind

any | preferred_pool | dedicated
  • any — partner picks driver per ride
  • preferred_pool — partner maintains 2-3 driver pool, rotates among them
  • dedicated — single driver assigned for the entire period (premium tier)

billing.period_kind

weekly | monthly | quarterly

billing.payment_method_required

upi_autopay | card_recurring_mandate | netbanking_emandate | wallet_auto_recharge

billing.proration_method

exact_per_ride_count | flat_first_period | no_proration

billing.refund_policy

unused_rides_credited | unused_rides_refunded_60pct | unused_rides_refunded_full |
no_refund_after_period_starts

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

contract_carriage | aggregator_permit | tourist

vehicle_meta.vehicle_class_certification

commercial_yellow_plate | aggregator_white_with_yellow_strip | tourist

vehicle_amenities.child_seat_kind

none | infant | toddler | booster | universal

driver_meta.age_band

21-30 | 31-40 | 41-55 | 56+

RecurringCommuteState.status

pending_first_ride | active | paused_by_user | paused_by_partner |
billing_failed | renewal_failed | cancelled_by_user | cancelled_by_partner |
expired | failed

RecurringCommuteState.next_ride.leg_kind

morning | evening

RecurringCommuteState.billing_state.payment_method_status

active | pending_authorization | failed_last_charge | mandate_revoked

cancel_subscription.reason

user_changed_address | user_changed_office | user_changed_schedule |
unsatisfied_with_driver | unsatisfied_with_vehicle | unsatisfied_with_pricing |
moved_city | found_alternative | medical | other

pause_subscription.reason

travel | medical | wfh_period | sabbatical | other

context.trip_purpose

commute_to_work | commute_to_home | commute_to_school | commute_to_college |
commute_to_gym | other

6. TTBS DIMENSIONS

Per-domain weights (locked; recurring override)

mobility (recurring_commute): { time: 0.40, taste: 0.20, budget: 0.30, safety: 0.10 }

Time stays high (commute punctuality is the whole point). Safety drops to 0.10 — repeat-customer pattern means user has already vetted partner across many rides; safety filters still apply as HARD FILTERS but don't drive ranking. Taste up — driver familiarity is what users actually buy here.

TIME

SIGNALS USED:
  - driver_meta.on_time_arrival_rate_30d                weight 0.40
  - max_eta_minutes met during pilot rides              weight 0.20
  - assigned_driver_kind=dedicated (most predictable)   weight 0.20
  - cancellation.free_cancel_within_first_n_rides       weight 0.10
  - billing.refund_policy permissiveness                weight 0.10

USER BAND HANDLING:
  - "very strict morning timing" → on_time rate threshold ≥ 0.95

TASTE

SIGNALS USED:
  - assigned_driver.kind ranking: dedicated > preferred_pool > any   weight 0.30
  - driver_meta.rating_avg                              weight 0.20
  - vehicle.year_of_manufacture                         weight 0.10
  - vehicle_amenities match w/ user prefs               weight 0.20
  - driver_meta.recurring_subscriptions_served (high=experienced) weight 0.10
  - languages_spoken match user_locale                  weight 0.10

HARD FILTERS:
  - ac_required + ac=false → drop
  - female_driver_required → drop male-driver pools

BUDGET

SIGNALS USED:
  - fare.total_period_inr vs band:
      ok    → hatchback / shared (rare for recurring)
      good  → sedan / hatchback dedicated
      great → premium_sedan dedicated / EV
  - fare.subscription_discount_pct (higher=better)      weight 0.30
  - fare.is_upfront_fare=true                           weight 0.20
  - cancellation.cancel_charge_inr_per_remaining_period weight 0.20
  - cancellation.exit_charge_inr (lower=better)         weight 0.15
  - billing.refund_policy permissiveness                weight 0.15

HARD FILTERS:
  - fare.total_period_inr > preferences.budget_max_inr_per_period → drop

SAFETY

SIGNALS USED:
  - driver_kyc.background_check_passed                  HARD FILTER
  - driver_kyc.dl_verified                              HARD FILTER
  - vehicle_meta.comprehensive_insurance                HARD FILTER
  - vehicle_meta.permit_kind valid                      HARD FILTER
  - vehicle_meta.fitness_certificate_valid_until_iso > active_until HARD FILTER
  - safety_features.sos_button_in_app                   weight 0.20
  - safety_features.cctv_in_cab                         weight 0.20
  - safety_features.driver_drowsiness_detection         weight 0.20
  - driver_meta.female_driver
      (boosted if female_driver_required)              weight 0.20
  - driver_meta.recurring_subscriptions_served (≥10)    weight 0.20
  - is_late_night → safety scales 1.3x

Note: safety weight in domain TTBS is 0.10, but HARD FILTERS still apply.

Hidden ranking factor

information_completeness_score weight 0.10.


7. COMPLETION CONTRACT

Recurring intent has TWO completion contract events: per-period billing settlement AND subscription closure. Both POST to the same endpoint with different event_kind.

Per-period settlement (fires after each billing period)

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

{
  "intent":            "mobility.book_recurring_commute",
  "intent_version":    "v1.0.0",
  "event_kind":        "period_settled",
  "external_id":       "OLA-RECUR-XYZ",
  "subscription_ref":  "OLA-RECUR-XYZ",
  "amount_inr":         16800,
  "closed_at":         "2026-06-10T00:01:00+05:30",
  "request_id":        "req_rec_2k7p_...",
  "status":            "period_completed",
  "period_start_iso":  "2026-05-12T00:00:00+05:30",
  "period_end_iso":    "2026-06-09T23:59:59+05:30",
  "rides_completed":   42,
  "rides_skipped_by_user": 4,
  "rides_failed_by_partner": 0,
  "currency":          "INR",
  "notes":             ""
}

Subscription closure

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

{
  "intent":            "mobility.book_recurring_commute",
  "intent_version":    "v1.0.0",
  "event_kind":        "subscription_closed",
  "external_id":       "OLA-RECUR-XYZ",
  "subscription_ref":  "OLA-RECUR-XYZ",
  "amount_inr":         50400,
  "closed_at":         "2026-08-12T00:00:00+05:30",
  "request_id":        "req_rec_2k7p_...",
  "status":            "completed",
  "subscription_started_at": "2026-05-12T00:00:00+05:30",
  "subscription_ended_at":   "2026-08-11T23:59:59+05:30",
  "total_periods_billed": 3,
  "total_rides_completed": 126,
  "total_rides_skipped":   12,
  "total_rides_failed":    1,
  "currency":          "INR",
  "fare_breakdown_total_inr": 50400,
  "ratings_pending":     true,
  "notes":              ""
}

Status enum:

  • per-period: period_completed | period_partial_refund_credited | period_billing_failed
  • subscription: completed | cancelled_by_user_within_grace | cancelled_by_user_after_grace | cancelled_by_partner | expired_no_renew | failed

8. WIDGET

WIDGET TYPE:        recurring_commute_options
SOURCE:             src/widgets/types.ts
TYPE NAME:          RecurringCommuteOptionsPayload
RENDERED IN:        components/widgets/RecurringCommuteOptionsWidget.tsx

Default: 3 stacked rows showing subscription kind label (e.g., "Monthly weekday pass — 22 rides"), vehicle_kind, fare with per-ride-avg + period-total, driver assignment kind badge (dedicated/preferred_pool/any), discount %, on-time rate strip. Tap row → confirmation card with full subscription terms (period, billing day, refund policy, cancel grace) → "Subscribe". Then RecurringCommuteDashboard with period progress + pause/resume + skip-day controls.


9. CACHING POLICY

Call TTL Rationale
get_recurring_commute_estimates 300s inventory + pricing move slowly for subscriptions
get_subscription_state 30s period summary doesn't change minute-to-minute
book_recurring_commute 0s
pause_subscription / resume_subscription 0s
skip_ride_for_date 0s
update_subscription_window 0s
cancel_subscription 0s
get_ride_for_date 5s
track_ride 0s always live during ride
Failure responses 0s

10. ERROR CODES

Code HTTP Meaning TOMO behavior
NO_DRIVERS_AVAILABLE 503 no driver pool covers the route + window retry or fall back
OUT_OF_SERVICE_AREA 400 pickup or drop outside coverage surface
OPTION_EXPIRED 410 option_token invalid re-quote
PAYMENT_METHOD_REQUIRED 402 no upi_autopay / card mandate set up surface
MANDATE_AUTH_FAILED 402 bank mandate auth declined surface, retry
SUBSCRIPTION_NOT_FOUND 404 subscription_ref doesn't exist surface
ALREADY_CANCELLED 409 duplicate cancel idempotent return
ALREADY_PAUSED 409 duplicate pause idempotent return
BILLING_FAILED 402 last billing cycle declined surface, dunning flow
MANDATE_REVOKED 401 user revoked mandate at bank surface, re-onboard
WINDOW_INFEASIBLE 400 requested pickup window not coverable by any driver surface
RIDE_NOT_SCHEDULED 404 get_ride_for_date for non-scheduled day surface
DEDICATED_DRIVER_UNAVAILABLE 503 dedicated driver unavailable; fallback partner reassigns from pool

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All §2 inputs validated, request_id echoed
[ ] get_recurring_commute_estimates returns ≥3 options for "weekday HITEC City → Banjara Hills"
[ ] All §4 required fields populated with REAL data
[ ] driver_kyc + vehicle_meta truthful + verifiable
[ ] permit_kind valid for daily commercial commute
[ ] fitness_certificate valid through active_until
[ ] book_recurring_commute returns subscription_ref + first_ride within SLA
[ ] UPI Autopay or card mandate setup tested in sandbox
[ ] First ride dispatched within window on active_from_iso
[ ] track_ride works for any ride within subscription
[ ] pause/resume/skip flows tested with billing adjustments
[ ] update_subscription_window respects effective_from_iso boundary
[ ] cancel respects free_cancel_within_first_n_rides grace
[ ] Per-period CPC webhook fires on each billing close within 24h
[ ] Subscription-closure CPC webhook fires within 60s of cancel/expire
[ ] HMAC verification passes
[ ] No forbidden fields anywhere
[ ] Dedicated-driver assignment honored across periods
[ ] Fallback-to-pool tested when dedicated driver unavailable
[ ] partner_subscription_completion_rate_30d audited against TOMO sample
[ ] No paid placement / sponsored signals
[ ] customer_support_24x7 verified by TOMO field call

12. ANTI-FABRICATION RULES

RULE 1 — No paid placement signals.

RULE 2 — No fake driver ratings.

RULE 3 — No fake on_time_arrival_rate_30d.
  Rate must come from partner's CPC ledger of completed rides. TOMO samples
  1% rides for late vs on-time validation. Mismatch >5% = breach.

RULE 4 — No fake recurring_subscriptions_served inflation.

RULE 5 — No fake subscription_completion_rate_30d.

RULE 6 — Vehicle plate must match dispatched vehicle.

RULE 7 — Fare displayed must be honored.
  is_upfront_fare=true means fare_locked_until_iso. Per-period fare cannot
  change mid-period without explicit user consent + re-onboarding.

RULE 8 — Driver KYC + permit claims must be verifiable.

RULE 9 — Cancel charges must match displayed thresholds.
  Free cancel within first N rides cannot be reduced retroactively.

RULE 10 — No commission-based response shaping.

RULE 11 — Skipped rides are credited honestly.
  rides_skipped_by_user must be reflected in billing_credit_inr per the
  proration method declared at booking. Charging full period when user
  skipped 8 of 22 rides = breach.

RULE 12 — Failed rides are not silently absorbed.
  rides_failed_by_partner > 0 must trigger user-visible billing credit AND
  partner-side service-level penalty per partner contract. Hiding failed
  rides to preserve completion_rate_30d = severe breach.

RULE 13 — Dedicated driver promise honored or compensated.
  assigned_driver_kind=dedicated requires same driver across rides except
  in declared exception cases (driver leave, illness). Substituting silently
  > 5% of rides = breach + automatic refund tier.

RULE 14 — Auto-renew is honest.
  billing.auto_renew=true requires partner to notify user 7 days before next
  charge. Auto-charging without notification + ability to cancel = breach.

RULE 15 — Pause/resume math is honest.
  pause_subscription credit = (paused_days / total_period_days) × period_inr,
  rounded down to whole rupees. Inflating denominator or undercounting
  paused_days = billing fraud.

RULE 16 — Mandate revocation respected.
  If user revokes mandate at bank, partner cannot continue charging via
  fallback method without explicit user re-onboarding.

VERSION HISTORY

v1.0.0 — 2026-05-10 — Initial spec (V2 marker — partner integration cautious until 1 partner ships end-to-end)