Intent Spec — auto.book_car_wash
FULL ID: auto.book_car_wash
VERSION: v1.0.0
STATUS: draft
LAST UPDATED: 2026-05-11
DOMAIN: auto
PRIMARY AGENT: AutoServicesAgent
TTBS WEIGHTS: time=0.35 taste=0.15 budget=0.30 safety=0.20
User books a car or two-wheeler wash — exterior, interior, or both. Covers basic foam wash, machine wash, doorstep wash, premium wash with polish. Excludes full detailing (coating, paint correction) which is its own intent (auto.book_detailing).
Partner exemplars: Exppress Car Wash, Wash Me Doorstep, GoMechanic Wash, Pitstop Wash, MyTVS Wash, neighbourhood multi-brand garages.
SECTION 1 — INTENT IDENTITY
This intent fires when a user wants their vehicle washed. Distinct from:
auto.book_detailing— coating, polish, paint correction, longer turnaroundauto.book_general_service— washing is a side-effect of service, not the goallifestyle.book_at_home_service— not vehicle-related
Single intent fires per booking. If user wants wash + service same day, multi-intent fan-out kicks in.
SECTION 2 — NATURAL LANGUAGE COVERAGE
CLASSIFIES IN
- "Book a car wash"
- "Need a car wash this evening"
- "Doorstep car wash near me"
- "Wash my bike tomorrow morning"
- "Cheapest foam wash in Hyderabad"
- "Premium car wash with interior cleaning"
- "Get my Swift washed and vacuumed"
- "Quick car wash in 30 minutes"
- "Schedule a car wash at home"
- "Wash and polish my car this Saturday"
CLASSIFIES OUT — BORDERLINE NO
- "Full detailing for my car" →
auto.book_detailing - "Service my car" →
auto.book_general_service - "Paint touch-up" →
auto.book_paint_job - "Tyres need cleaning" → ambiguous; if standalone →
auto.book_car_wash, if alignment/rotation →auto.book_tyre_alignment
MULTI-INTENT TRIGGERS
- "Service and wash on Saturday" →
auto.book_general_service+auto.book_car_wash(same workshop preferred) - "Wash my car and then I need a ride to the airport" →
auto.book_car_wash+mobility.book_airport_transfer
SECTION 3 — INPUT (TOMO → PROVIDER)
{
"intent": "auto.book_car_wash",
"request_id": "req_01J9Z...",
"user_locale": "en-IN",
"user_currency": "INR",
"user_location": { "lat": 17.4475, "lng": 78.3563, "max_radius_km": 8, "city": "Hyderabad" },
"vehicle": {
"type": "car", // STRICT ENUM §6
"size_class": "sedan", // STRICT ENUM §6
"make": "Maruti Suzuki",
"model": "Swift",
"registration_number_last4": "1234"
},
"wash_preferences": {
"wash_type": "premium", // STRICT ENUM §6, nullable for "anything"
"include_interior": true,
"include_polish": false,
"preferred_window": {
"start": "2026-05-13T16:00:00+05:30",
"end": "2026-05-13T19:00:00+05:30"
},
"doorstep_only": false, // true = filter to mobile providers only
"max_duration_minutes": 60
},
"ttbs_user_band": {
"time": "fast",
"taste": "balanced",
"budget": "ok",
"safety": "balanced"
},
"session_context": {
"tomo_session_id": "ses_01J9Z...",
"user_dna_hash": "dna_v3_a7c9..."
}
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
intent |
string | REQUIRED, STRICT ENUM | Always auto.book_car_wash |
request_id |
string | REQUIRED, ULID | Idempotency |
vehicle.type |
enum | REQUIRED | car | two_wheeler |
vehicle.size_class |
enum | REQUIRED, STRICT ENUM §6 | Pricing varies by size |
vehicle.registration_number_last4 |
string | REQUIRED, len 4 | Privacy |
wash_preferences.wash_type |
enum | REQUIRED nullable, STRICT ENUM §6 | When null, return all types |
wash_preferences.include_interior |
bool | REQUIRED | Filter / pricing differentiator |
wash_preferences.preferred_window.* |
string | REQUIRED, ISO_DATETIME | |
wash_preferences.doorstep_only |
bool | REQUIRED | |
wash_preferences.max_duration_minutes |
int | REQUIRED, 15-240 | Filter by typical_duration_minutes |
Anti-fabrication preamble: Same as §3 of auto.book_general_service.
SECTION 4 — PROVIDER TOOLS
Tool 1: search_wash_slots
PURPOSE: Return up to 20 wash slots
INPUT: §3
OUTPUT: array<WashSlot> per §5
SLA: p50 ≤ 500ms, p95 ≤ 1500ms, p99 ≤ 3000ms
RATE LIMIT: 60 req/min
IDEMPOTENCY: request_id; 60s cache
RETRY: 1 on 429 / 2 on 5xx
Tool 2: create_wash_booking
PURPOSE: Confirm slot + price; lock provider availability
INPUT: { request_id, slot_id, vehicle.*, address?, contact_phone }
OUTPUT: WashBooking per §5
SLA: p50 ≤ 1500ms, p95 ≤ 4000ms
RATE LIMIT: 30 req/min
IDEMPOTENCY: request_id
RETRY: TOMO does NOT retry create
Tool 3: cancel_wash_booking
PURPOSE: Cancel pre-arrival; honour cancellation policy
INPUT: { request_id, booking_id, reason_code }
OUTPUT: CancellationResult
SLA: p50 ≤ 600ms, p95 ≤ 1800ms
RATE LIMIT: 30 req/min
RETRY: 1 on 5xx
No get_quote tool because wash pricing is itemized inline in WashSlot.price. The two-call pattern (search → create) keeps friction low for a single-product intent.
SECTION 5 — RESPONSE SHAPE
WashSlot
WashSlot:
slot_id: { type: string, constraint: REQUIRED }
provider:
provider_id: { type: string, constraint: REQUIRED }
name: { type: string, constraint: REQUIRED }
provider_type:
type: enum
constraint: REQUIRED, STRICT ENUM §6
values: [workshop_bay, doorstep_mobile, fuel_station_attached, automated_tunnel]
address: { type: string, constraint: REQUIRED }
location: { type: object, shape: { lat, lng }, constraint: REQUIRED }
distance_from_user_km: { type: float, constraint: REQUIRED, 0-30 }
water_source:
type: enum
constraint: REQUIRED, STRICT ENUM §6
values: [tap, recycled, bottled, dry_clean]
semantics: TOMO surfaces "recycled" / "dry_clean" as sustainability badge
slot_window:
start: { type: string, constraint: REQUIRED, ISO_DATETIME }
end: { type: string, constraint: REQUIRED, ISO_DATETIME }
typical_duration_minutes: { type: int, constraint: REQUIRED, 15-240 }
wash_type:
code: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
label: { type: string, constraint: REQUIRED }
includes:
type: array<string>
constraint: REQUIRED, ≥2 entries
example: ["exterior_foam", "tyre_shine", "interior_vacuum"]
excludes:
type: array<string>
constraint: REQUIRED, may be empty
example: ["under_chassis", "engine_bay"]
price:
base_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
surcharge_inr:
type: int
constraint: REQUIRED, INR_INTEGER, ≥0
semantics: extra for doorstep / oversize / out-of-hours
gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
total_inr: { type: int, constraint: REQUIRED, INR_INTEGER, base + surcharge + gst }
fixed_price_guaranteed: { type: boolean, constraint: REQUIRED }
logistics:
user_present_required: { type: boolean, constraint: REQUIRED }
drop_off_pickup_available: { type: boolean, constraint: REQUIRED }
while_you_wait_acceptable: { type: boolean, constraint: REQUIRED }
ratings:
avg_rating: { type: float, constraint: REQUIRED, 0-5 }
review_count: { type: int, constraint: REQUIRED, ≥0 }
repeat_customer_pct_last_30d: { type: int, constraint: REQUIRED, 0-100 }
partner_reference:
source: { type: string, constraint: REQUIRED }
deeplink: { type: string, constraint: REQUIRED, HTTPS URL }
WashBooking
WashBooking:
booking_id: { type: string, constraint: REQUIRED, immutable }
slot_id: { type: string, constraint: REQUIRED }
scheduled_start: { type: string, constraint: REQUIRED, ISO_DATETIME }
provider_name: { type: string, constraint: REQUIRED }
contact_phone: { type: string, constraint: REQUIRED, E.164, partner-side dispatcher }
arrival_eta: { type: string, constraint: REQUIRED nullable, ISO_DATETIME, required for doorstep_mobile }
qr_or_code: { type: string, constraint: REQUIRED nullable, semantics: "for self-service tunnels only" }
payment_due_at: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [now, on_arrival, on_completion] }
CancellationResult
CancellationResult:
booking_id: { type: string, constraint: REQUIRED }
cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
cancellation_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
refund_eta_days: { type: int, constraint: REQUIRED, 0-7 }
FORBIDDEN FIELDS
paid_placement_score,ad_bid,sponsored_rank,promotion_prioritykickback_amount,referral_fee_kickback,_partner_revenue_shareartificial_urgency_textai_generated_photofor provider imagerycommission_padded_price
SECTION 6 — CONTROLLED VOCABULARIES
vehicle.size_class:
values:
hatchback: "Compact hatchback (e.g. Swift, i10)"
sedan: "Sedan (e.g. City, Verna)"
suv: "SUV / crossover (e.g. Creta, Seltos)"
luv: "Large utility vehicle (e.g. Innova, Fortuner)"
mpv: "Multi-purpose van"
two_wheeler_small: "Up to 150cc bike / scooter"
two_wheeler_large: "Above 150cc bike / cruiser"
wash_type.code:
values:
basic_exterior: "Foam + rinse exterior only"
basic_full: "Exterior + quick interior dust"
premium: "Premium foam + interior vacuum + tyre shine"
polish: "Premium + machine polish"
interior_deep: "Interior deep clean only (no exterior)"
dry_clean: "Waterless detailing wipe-down"
provider_type:
values:
workshop_bay: "Brick-and-mortar workshop wash bay"
doorstep_mobile: "Mobile crew at user's location"
fuel_station_attached: "Wash bay attached to fuel station"
automated_tunnel: "Drive-through automated tunnel"
water_source:
values:
tap: "Standard tap water"
recycled: "Water reclaimed from previous washes (sustainability)"
bottled: "Bottled / filtered (premium)"
dry_clean: "Waterless"
payment_due_at:
values:
now: "Pay at booking"
on_arrival: "Pay when crew arrives or vehicle dropped"
on_completion: "Pay after wash done"
SECTION 7 — TTBS DIMENSIONS
TIME (weight = 0.35):
signals_used:
- provider.distance_from_user_km (for non-doorstep)
- slot_window.start vs user's window center
- typical_duration_minutes vs max_duration_minutes
weighting:
distance: 0.30
fit: 0.40
duration: 0.30
user_band_handling:
fast: prefer slots in next 60min; relax distance
balanced: standard
flexible: any in window
TASTE (weight = 0.15):
signals_used:
- provider_type matching user DNA history
- wash_type.code matching declared preference
- water_source sustainability (recycled / dry_clean bonus for sustainability-leaning DNA)
weighting:
type_match: 0.40
wash_match: 0.40
sustainability: 0.20
user_band_handling: standard across bands
BUDGET (weight = 0.30):
signals_used:
- price.total_inr
- price.fixed_price_guaranteed
weighting:
price: 0.75
lock_bonus: 0.25
user_band_handling:
ok: prefer cheapest basic_exterior
good: balance price vs wash_type
great: prefer premium / polish even at 3x base
SAFETY (weight = 0.20):
signals_used:
- ratings.avg_rating
- ratings.repeat_customer_pct_last_30d
- logistics.user_present_required (false = lower trust requirement)
weighting:
rating: 0.50
repeat: 0.30
presence_bonus: 0.20 # if user doesn't have to be present, slight bonus
user_band_handling:
fast: relax repeat threshold
balanced: standard
flexible: prefer high-trust providers (high repeat %)
SECTION 8 — COMPLETION CONTRACT
POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
"intent": "auto.book_car_wash",
"external_id": "<booking_id>",
"request_id": "<request_id>",
"amount_inr": 250, // NET (base + supplier-kept surcharge)
"gst_inr": 45, // government — excluded
"tips_inr": 50, // tip to wash crew — excluded (driver/staff money)
"pass_through_inr": 0,
"closed_at": "2026-05-13T17:35:00+05:30",
"status": "completed", // STRICT ENUM: completed | cancelled_by_user | cancelled_by_partner | no_show
"wash_type": "premium"
}
HMAC over ${timestamp}.${body}. 5-min replay. 10% × amount_inr is commission (NET-only).
SECTION 9 — WIDGET
AutoWashWidget (planned in src/widgets/AutoWashWidget.tsx). Interim: generic ListingsWidget.
Field mapping:
- WashSlot.provider.name → header
- WashSlot.provider_type + distance_from_user_km → subline 1
- WashSlot.slot_window.start + typical_duration_minutes → subline 2
- WashSlot.price.total_inr → price row
- WashSlot.water_source = "recycled" or "dry_clean" → sustainability pill (green)
- WashSlot.ratings.avg_rating → rating badge
L2/L3 deeplinks inline; L1 fallback per locked policy.
SECTION 10 — CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
search_wash_slots |
30s | Wash slots churn faster than service slots |
create_wash_booking |
NO CACHE | Idempotent by request_id |
cancel_wash_booking |
NO CACHE | — |
| Provider static metadata | 24h | Static |
SECTION 11 — ERROR CODES
| Code | HTTP | Meaning | Retry |
|---|---|---|---|
INVALID_REQUEST |
400 | Payload malformed | No |
INVALID_AUTH |
401 | Bad creds | No |
RATE_LIMITED |
429 | Throttle | 1, 2s |
INTERNAL_ERROR |
500 | Partner failure | 2, exp |
IDEMPOTENCY_VIOLATION |
409 | request_id reused | No |
SIGNATURE_INVALID |
401 (webhook) | HMAC fail | No |
VEHICLE_TOO_LARGE |
422 | size_class outside provider's accepted | No |
NO_SLOTS_IN_WINDOW |
200 (empty) | Valid, no matches | n/a |
SLOT_GONE |
409 (create) | Slot booked between search and create | No; UI re-prompts |
PROVIDER_OFFLINE |
422 | Provider not currently operating | No |
DOORSTEP_UNAVAILABLE_AT_LOCATION |
422 | User location outside doorstep service area | No |
CANCELLATION_FEE_DUE |
200 (cancel) | Non-zero cancellation_fee | n/a |
SECTION 12 — SANDBOX → PRODUCTION CHECKLIST
[ ] All three tools (search/create/cancel) implemented
[ ] 5+ real providers + 10+ slots in 24h window
[ ] vehicle.size_class pricing differentiated correctly (hatchback < sedan < SUV < LUV)
[ ] All wash_type codes return non-empty includes[] array
[ ] HMAC signing verified
[ ] amount_inr is NET in CPC payload
[ ] tips_inr field correctly captures user-paid tips (not zero by default)
[ ] No forbidden fields anywhere
[ ] SLA p95 ≤ 1500ms on search
[ ] Idempotency tested
[ ] Doorstep providers return arrival_eta in WashBooking
[ ] water_source claims verifiable (TOMO inspects on-site for "recycled" claim)
[ ] Compliance docs: GSTIN, shop license, privacy policy
SECTION 13 — ANTI-FABRICATION RULES
RULE 1: No paid_placement / ad / kickback fields. Rejects entire response.
RULE 2: price.fixed_price_guaranteed=true means total_inr is the final amount.
Partners who upsell on-arrival above fixed_price face suspension after
3 incidents (verified via CPC amount_inr vs slot total_inr).
RULE 3: water_source claim of "recycled" or "dry_clean" must be verifiable.
TOMO field-tests on randomly selected bookings; mismatches result in
compliance review and water_source claim revocation.
RULE 4: ratings.repeat_customer_pct_last_30d must be computed from real
booking data, not aspirational. TOMO cross-checks via its own
completion ledger for that provider.
RULE 5: typical_duration_minutes must reflect actual median completion time
for the wash_type. Sustained mismatch >50% triggers review.
RULE 6: No fast_selling / artificial_urgency in any subline text.
RULE 7: Photos of provider must be real. AI-generated forbidden.
RULE 8: provider_type=automated_tunnel must actually be automated — partners
can't tag manual wash as "automated" to game TIME ranking.
RULE 9: includes[] array must reflect actual service performed. Partners
who skip listed steps (e.g. promise interior_vacuum but skip it)
face suspension; verified via customer post-wash reports.
RULE 10: No "Top Rated" / "Editor's Choice" badges; TTBS ranks source-blind.
VERSION HISTORY
v1.0.0 — 2026-05-11 — Initial spec. NET commission base in §8.