Intent Spec — entertainment.book_movie_ticket
FULL ID: entertainment.book_movie_ticket
VERSION: v1.0.0
STATUS: draft
LAST UPDATED: 2026-05-11
DOMAIN: entertainment
PRIMARY AGENT: EntertainmentAgent
TTBS WEIGHTS: time=0.25 taste=0.35 budget=0.20 safety=0.20
User books a movie ticket for one or more seats at a specific show, on a specific screen, at a specific venue. Includes 2D / 3D / IMAX / 4DX format selection, seat selection, food & beverage add-ons where the venue offers them, and combo offers tied to a payment instrument.
Partner exemplars: BookMyShow, Paytm Insider, PVR/INOX direct apps, District by Zomato, AMC India.
SECTION 1 — INTENT IDENTITY
This intent fires when a user wants to reserve a seat at a scheduled movie screening. It is distinct from:
entertainment.book_theatre_play— live theatre, no screenentertainment.book_comedy_show— live stand-up, no screenentertainment.subscribe_streaming— at-home, no venuefood.book_dine_in— F&B-primary, even if at a cinema dine-inentertainment.book_event_venue— booking the entire screen for a private event
A single intent fires per booking. Group bookings (>10 seats) still classify here; bulk corporate bookings (>30 seats) escalate to entertainment.book_event_venue.
SECTION 2 — NATURAL LANGUAGE COVERAGE
CLASSIFIES IN (min. 8)
- "Book me a movie ticket for tonight"
- "2 tickets for Pushpa 2, IMAX, Phoenix mall, 9pm show"
- "Reserve 4 seats for the new Marvel movie tomorrow evening"
- "Hyderabad PVR, Saturday matinee, any good film"
- "I want to watch a Telugu movie this weekend with my family of 5"
- "Book the cheapest 3D show of Avatar near me"
- "Find a 4DX show in Bangalore for Friday night"
- "Get me 3 tickets for the late-night horror screening"
- "Anything playing at INOX Forum at 7pm?"
- "Children's movie matinee for Sunday morning"
CLASSIFIES OUT — BORDERLINE NO
- "Tickets to the Drake concert" →
entertainment.book_concert_ticket - "Reserve a private theatre" →
entertainment.book_event_venue - "Subscribe to Netflix" →
entertainment.subscribe_streaming - "Book a comedy night" →
entertainment.book_comedy_show - "Buy popcorn at PVR" → not a TOMO intent (we don't sell concessions standalone)
- "Movie review for Pushpa 2" → not a transactional intent; redirect to
discovery.research_topic
MULTI-INTENT TRIGGERS (fan-out)
- "Book a movie and dinner near Forum" → fans out to
entertainment.book_movie_ticket+food.book_dine_in - "Movie at PVR Inorbit, then cab home" →
entertainment.book_movie_ticket+mobility.book_intracity_ride(with the movie's end-time as ride pickup) - "Movie tickets and parking" →
entertainment.book_movie_ticket+mobility.book_parking(when live)
The SLM dataset includes these phrasings tagged with both intent IDs and the temporal/causal relationship (AFTER, BEFORE, WITH).
SECTION 3 — INPUT (TOMO → PROVIDER)
{
"intent": "entertainment.book_movie_ticket",
"request_id": "req_01J9Z...", // ULID; idempotency key
"user_locale": "en-IN",
"user_currency": "INR",
"user_location": {
"lat": 17.4475,
"lng": 78.3563,
"max_radius_km": 12, // user-tunable, default 10
"city": "Hyderabad"
},
"preferences": {
"movie_title": "Pushpa 2", // OR null when user is open
"language": ["te", "hi"], // BCP-47, ordered preference
"formats": ["IMAX", "3D", "2D"], // ordered preference, STRICT ENUM §6
"showtime_window": {
"start": "2026-05-11T18:00:00+05:30",
"end": "2026-05-11T23:59:59+05:30"
},
"seat_count": 2, // 1-20 inclusive; >20 rejects
"seat_class_preference": ["recliner", "premium", "standard"],
"accessibility": {
"wheelchair_seats_required": 0,
"hearing_assistance_required": false
}
},
"ttbs_user_band": {
"time": "fast", // STRICT ENUM: flexible | balanced | fast
"taste": "balanced",
"budget": "ok", // STRICT ENUM: ok | good | great
"safety": "balanced"
},
"session_context": {
"tomo_session_id": "ses_01J9Z...",
"user_dna_hash": "dna_v3_a7c9..." // opaque to partner
}
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
intent |
string | REQUIRED, STRICT ENUM | Always entertainment.book_movie_ticket |
request_id |
string | REQUIRED, ULID | Partner uses for idempotency. Same ID + ≠ payload → reject IDEMPOTENCY_VIOLATION |
user_location.lat / lng |
float | REQUIRED | WGS84 |
user_location.max_radius_km |
int | REQUIRED, 1-50 | Partner MUST filter venues by this radius |
preferences.movie_title |
string | null | REQUIRED nullable | When null, partner returns top venues regardless of film |
preferences.language |
array |
REQUIRED, BCP-47, ≥1 | Order = user preference |
preferences.formats |
array |
REQUIRED, STRICT ENUM §6, ≥1 | |
preferences.showtime_window.start / end |
string | REQUIRED, ISO_DATETIME | Window in user's local TZ. Partner returns shows fully within window |
preferences.seat_count |
int | REQUIRED, 1-20 | >20 → escalate to event venue intent |
preferences.seat_class_preference |
array |
REQUIRED, STRICT ENUM §6 | |
preferences.accessibility.* |
bool/int | REQUIRED | Partner MUST honour or omit listing |
ttbs_user_band.* |
enum | REQUIRED, STRICT ENUM | Used by partner for sort order only; partner cannot game by re-weighting |
Anti-fabrication preamble: TOMO will never inject paid_placement signals, urgency text, or commission-influenced fields into this payload. Provider may not reject the request based on TOMO's commission rate or seek to surface higher-margin venues over lower-margin ones.
SECTION 4 — PROVIDER TOOLS
Tool 1: search_movie_shows
PURPOSE: Return up to 20 showtimes matching the input window + location + preferences
INPUT SHAPE: §3 above
OUTPUT SHAPE: array<ShowListing> per §5
SLA: p50 ≤ 600ms, p95 ≤ 1500ms, p99 ≤ 3000ms
RATE LIMIT: 60 req/min per partner (TOMO will not exceed)
IDEMPOTENCY: request_id; cached response for 30s
RETRY: TOMO retries on RATE_LIMITED (1 retry, 2s backoff) or 5xx (2 retries, exp backoff). Never on 4xx.
Tool 2: get_seat_map
PURPOSE: Return the live seat map for a specific show_id (must be from search_movie_shows result)
INPUT: { request_id, show_id, language? }
OUTPUT: SeatMap per §5
SLA: p50 ≤ 300ms, p95 ≤ 800ms
RATE LIMIT: 120 req/min per partner
IDEMPOTENCY: None — must reflect live availability
RETRY: 1 retry on 5xx
Tool 3: create_booking
PURPOSE: Lock + confirm specific seats for a show; returns booking confirmation
INPUT: { request_id, show_id, seat_ids[], addons[], payment_method_hint }
OUTPUT: Booking per §5
SLA: p50 ≤ 1500ms, p95 ≤ 4000ms, p99 ≤ 8000ms
RATE LIMIT: 30 req/min per partner
IDEMPOTENCY: request_id; same ID returns the same booking confirmation
RETRY: TOMO does NOT retry on this tool. Partner is responsible for clean failure semantics.
Tool 4: cancel_booking
PURPOSE: Cancel a confirmed booking before showtime; honour partner's cancellation policy
INPUT: { request_id, booking_id, reason_code }
OUTPUT: CancellationResult per §5
SLA: p50 ≤ 1000ms, p95 ≤ 3000ms
RATE LIMIT: 30 req/min per partner
IDEMPOTENCY: booking_id
RETRY: 1 retry on 5xx
SECTION 5 — RESPONSE SHAPE
ShowListing (returned by search_movie_shows, one per show)
ShowListing:
show_id:
type: string
constraint: REQUIRED, opaque, partner-namespaced (e.g. "bms_sh_872361")
semantics: stable identifier for this specific showing
null_handling: REJECTED
movie:
title:
type: string
constraint: REQUIRED, real movie title in user_locale where available
example: "Pushpa 2: The Rule"
title_localized:
type: object<locale,string>
constraint: REQUIRED for each language partner supports; min 1 entry
example: { "te": "పుష్ప 2: ది రూల్", "hi": "पुष्पा 2: द रूल", "en": "Pushpa 2: The Rule" }
duration_minutes:
type: int
constraint: REQUIRED, 30-360
semantics: total runtime including credits
rating:
type: enum
constraint: REQUIRED, STRICT ENUM §6 (CBFC certification)
values: [U, UA, A, S]
genre:
type: array<enum>
constraint: REQUIRED, STRICT ENUM §6, 1-4 entries
language:
type: enum
constraint: REQUIRED, BCP-47
release_date:
type: string
constraint: REQUIRED, ISO_DATE
poster_url:
type: string
constraint: REQUIRED, HTTPS URL, ≥400x600 PNG/JPG/WebP
semantics: official distributor artwork only; AI-generated rejected (§13)
venue:
venue_id:
type: string
constraint: REQUIRED, opaque partner-namespaced
name:
type: string
constraint: REQUIRED, public venue name
example: "AMB Cinemas, Gachibowli"
chain:
type: enum
constraint: REQUIRED, STRICT ENUM §6
values: [pvr, inox, amc, asian, miraj, mukta, independent]
address:
type: string
constraint: REQUIRED, full street address
location:
type: object
constraint: REQUIRED
shape: { lat: float, lng: float }
distance_from_user_km:
type: float
constraint: REQUIRED, computed against user_location, 0-50
semantics: Haversine OR routed; partner declares method in manifest
screen:
screen_number:
type: int
constraint: REQUIRED, 1-30
format:
type: enum
constraint: REQUIRED, STRICT ENUM §6
values: [2D, 3D, IMAX, IMAX_3D, 4DX, DOLBY_ATMOS, PRIVATE_THEATRE]
sound_system:
type: enum
constraint: REQUIRED, STRICT ENUM §6
values: [stereo, dolby_5_1, dolby_7_1, atmos, dts_x]
accessibility:
wheelchair_accessible:
type: boolean
constraint: REQUIRED
hearing_loop:
type: boolean
constraint: REQUIRED
audio_description_available:
type: boolean
constraint: REQUIRED
showtime:
start:
type: string
constraint: REQUIRED, ISO_DATETIME with +05:30 offset
end:
type: string
constraint: REQUIRED, ISO_DATETIME (start + duration_minutes + ad_minutes)
advance_booking_cutoff:
type: string
constraint: REQUIRED, ISO_DATETIME, bookings refused after this point
semantics: usually start - 15min; partner-policy
pricing:
classes:
type: array<SeatClassPricing>
constraint: REQUIRED, ≥1 entry
shape:
class_id: { type: string, constraint: REQUIRED, opaque }
class_label: { type: enum, constraint: REQUIRED, STRICT ENUM §6 }
base_price_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
convenience_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
total_per_seat_inr: { type: int, constraint: REQUIRED, INR_INTEGER, MUST equal base + convenience + gst }
surge_active:
type: boolean
constraint: REQUIRED
semantics: whether dynamic-pricing markup is currently applied
surge_multiplier:
type: float
constraint: REQUIRED when surge_active=true, 1.0-3.0
null_handling: REJECTED when surge_active=true
availability:
seats_available_total:
type: int
constraint: REQUIRED, ≥0
seats_available_by_class:
type: object<class_id,int>
constraint: REQUIRED, all class_ids from pricing.classes
fast_selling:
type: boolean
constraint: REQUIRED
semantics: TRUE only when seats_available_total / seats_total_capacity < 0.20
policies:
cancellation:
cutoff_minutes_before_start:
type: int
constraint: REQUIRED, ≥0
semantics: 0 = non-cancellable
refund_percent:
type: int
constraint: REQUIRED, 0-100
food_allowed_from_outside:
type: boolean
constraint: REQUIRED
age_restriction_enforced:
type: boolean
constraint: REQUIRED
semantics: TRUE = ID check at entry for A/UA films
partner_reference:
source:
type: string
constraint: REQUIRED
example: "bookmyshow"
deeplink:
type: string
constraint: REQUIRED, HTTPS URL to partner's confirmation surface
semantics: TOMO embeds this for L2/L3 partners; never the L1 web-redirect fallback
SeatMap (returned by get_seat_map)
SeatMap:
show_id: { type: string, constraint: REQUIRED, matches search result }
rows:
type: array<Row>
constraint: REQUIRED, ≥1
shape:
row_label: { type: string, constraint: REQUIRED, e.g. "A", "AA" }
class_id: { type: string, constraint: REQUIRED, references pricing.classes }
seats:
type: array<Seat>
shape:
seat_id: { type: string, constraint: REQUIRED, opaque }
seat_label: { type: string, constraint: REQUIRED, e.g. "A12" }
status: { type: enum, constraint: REQUIRED, STRICT ENUM §6, values: [available, occupied, blocked, social_distance_buffer] }
accessibility: { type: enum, constraint: REQUIRED, values: [none, wheelchair, hearing_aid_compatible] }
legend:
type: object
constraint: REQUIRED, maps every class_id to a hex color + label for widget rendering
Booking (returned by create_booking)
Booking:
booking_id: { type: string, constraint: REQUIRED, opaque, immutable }
show_id: { type: string, constraint: REQUIRED }
seat_labels: { type: array<string>, constraint: REQUIRED, matches requested seats }
total_inr: { type: int, constraint: REQUIRED, INR_INTEGER, breakdown below }
total_breakdown:
base_total_inr: { type: int, constraint: REQUIRED }
convenience_total_inr: { type: int, constraint: REQUIRED }
gst_total_inr: { type: int, constraint: REQUIRED }
addons_total_inr: { type: int, constraint: REQUIRED, ≥0 }
qr_payload: { type: string, constraint: REQUIRED, base64 PNG ≤200KB OR a signed URL to fetch }
entry_instructions: { type: string, constraint: REQUIRED, locale-aware, ≤280 chars }
partner_booking_reference: { type: string, constraint: REQUIRED, partner-internal id for ops }
CancellationResult (returned by cancel_booking)
CancellationResult:
booking_id: { type: string, constraint: REQUIRED }
cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
refund_eta_days: { type: int, constraint: REQUIRED, 0-14 }
FORBIDDEN FIELDS (TOMO ingest rejects entire response if any of these appear)
paid_placement_score,ad_bid,sponsored_rank,promotion_prioritykickback_amount,referral_fee_kickback,_partner_revenue_shareartificial_urgency_text(or any field whose value mentions "Hurry!", "Only X left!" without a numeric backing)ai_generated_photo(must be absent; poster_url must be official distributor artwork)commission_padded_priceor any field that varies pricing by TOMO's take rate
SECTION 6 — CONTROLLED VOCABULARIES
formats:
values:
"2D": "Standard digital projection"
"3D": "Stereoscopic 3D, requires glasses"
"IMAX": "IMAX 2D large-format"
"IMAX_3D": "IMAX 3D large-format"
"4DX": "4DX motion + environmental effects"
"DOLBY_ATMOS": "Dolby Atmos sound, standard projection"
"PRIVATE_THEATRE": "Reserved screen, capacity-locked"
resolution_order: partner maps internal codes to these; ambiguous codes default to closest superset
genre:
values:
action: "Action"
adventure: "Adventure"
animation: "Animation"
biography: "Biography"
comedy: "Comedy"
crime: "Crime / thriller"
drama: "Drama"
documentary: "Documentary"
family: "Family / kids"
fantasy: "Fantasy"
historical: "Historical / period"
horror: "Horror"
musical: "Musical"
mystery: "Mystery"
romance: "Romance"
science_fiction: "Sci-Fi"
sports: "Sports"
war: "War"
western: "Western"
rating:
values:
U: "Universal"
UA: "Parental guidance under 12"
A: "Adult only (18+)"
S: "Restricted to a special class (professional)"
seat_class_label:
values:
standard: "Standard seating"
premium: "Premium / club"
recliner: "Recliner / lounger"
box: "Private box (4-6 seats)"
accessible: "Wheelchair-accessible position"
chain:
values:
pvr: "PVR Cinemas"
inox: "INOX Leisure"
amc: "AMC India"
asian: "Asian Cinemas (regional, AP/TS)"
miraj: "Miraj Cinemas"
mukta: "Mukta A2 Cinemas"
independent: "Independent / single-screen"
seat_status:
values:
available: "Bookable now"
occupied: "Already booked"
blocked: "Held by partner — not available regardless of class"
social_distance_buffer: "Held empty for distancing policy"
Vocabulary changes require a v1.x bump and 30-day partner notice.
SECTION 7 — TTBS DIMENSIONS
TIME (weight = 0.25):
signals_used:
- venue.distance_from_user_km
- showtime.start (proximity to user's stated window center)
- showtime.advance_booking_cutoff (urgency proxy)
weighting:
distance: 0.40 # closer = better
showtime_fit: 0.45 # closer to window center = better
cutoff_proximity: 0.15 # not-too-close-to-cutoff = better
user_band_handling:
fast: prioritise next-available show even if farther venue
balanced: even mix
flexible: ignore time-of-day fit; spread across window evenly
TASTE (weight = 0.35):
signals_used:
- movie.genre intersected with user's historical genre preferences (DNA-derived, opaque to partner)
- movie.language matching preferences.language order
- screen.format matching preferences.formats order
- venue.chain matching user's frequent venues (DNA)
weighting:
genre_match: 0.35
language_match: 0.30
format_match: 0.25
chain_familiarity: 0.10
user_band_handling:
fast: not applied — taste is binary, not speed
balanced: standard weights
flexible: widen genre_match window to include adjacent genres
BUDGET (weight = 0.20):
signals_used:
- pricing.classes total_per_seat_inr (min across classes)
- pricing.surge_active + surge_multiplier
weighting:
base_price: 0.70
surge_penalty: 0.30 # surge_active=true incurs 30% rank penalty by default
user_band_handling:
ok: prefer cheapest available class
good: balance price vs class quality (e.g. recliner at moderate price wins)
great: prefer premium / recliner / IMAX even at 2-3x base price
SAFETY (weight = 0.20):
signals_used:
- venue.chain (chain-level safety reputation, internal TOMO score)
- screen.accessibility flags matching user's accessibility requirements
- policies.age_restriction_enforced (when user_dna indicates minor in party)
weighting:
chain_safety_score: 0.50
accessibility_match: 0.40
age_appropriate: 0.10
user_band_handling:
fast: relax accessibility unless explicitly requested
balanced: standard
flexible: relax chain_safety_score (allow independent screens)
Locked in server/lib/domain-agent-map.ts under domain entertainment. Partners cannot influence weights through any spec field; user-DNA shifts are silent.
SECTION 8 — COMPLETION CONTRACT
When the user has entered the cinema and the booking is finalised (or a terminal failure has occurred), the partner POSTs to:
POST https://www.automobnxt.com/api/v1/cpc/mcp_provider/<your_partner_id>
Headers:
Content-Type: application/json
X-TOMO-Timestamp: <unix_ms>
X-TOMO-Signature: sha256=<hex_hmac>
Body:
{
"intent": "entertainment.book_movie_ticket",
"external_id": "<booking_id from Booking>",
"request_id": "<request_id from input>",
"amount_inr": 420, // NET supplier revenue = base × seat_count + supplier-kept convenience
"gst_inr": 76, // GST passed to government — excluded from commission
"tips_inr": 0, // no tips in cinema bookings
"pass_through_inr": 0, // any third-party fee partner does not keep
"closed_at": "2026-05-11T21:18:00+05:30",
"status": "completed", // STRICT ENUM: completed | cancelled_by_user | cancelled_by_partner | no_show
"seat_count": 2,
"format": "IMAX"
}
HMAC computed over ${X-TOMO-Timestamp}.${rawBodyJSON} using your webhook_signing_key (issued at sandbox signup). Replay window: 5 minutes.
Retry policy on partner side: if TOMO returns 5xx, retry with exponential backoff (1s, 2s, 4s, 8s) up to 4 retries. Idempotency by external_id — TOMO will not double-bill.
10% × amount_inr is the commission. Not 10% × (amount + gst + tips + pass_through). Confirmed by founder doctrine 2026-05-11.
SECTION 9 — WIDGET
Rendered via MovieShowtimesWidget in src/widgets/MovieShowtimesWidget.tsx (planned; not yet shipped — falls back to a generic ListingsWidget pattern in interim).
Source file: src/widgets/types.ts → MovieShowtimesPayload
Renderer: src/widgets/MovieShowtimesWidget.tsx
Field mapping:
- ShowListing.movie.title → widget header
- ShowListing.movie.poster_url → poster thumbnail
- ShowListing.venue.name + distance_from_user_km → subline 1
- ShowListing.showtime.start (formatted local) + screen.format → subline 2
- ShowListing.pricing.classes[min].total_per_seat_inr → "From ₹X" row
- ShowListing.availability.fast_selling → red "Fast selling" pill (only when true AND seats_available < 20% capacity)
On tap, widget opens the partner's deeplink in the embedded provider host (L2/L3) or, for L1 partners only, in a tab. Seat map (get_seat_map) renders inline below the listing card.
Partners cannot influence widget design or override field mappings.
SECTION 10 — CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
search_movie_shows |
60s | Showtimes are stable; seat counts fluctuate but listings themselves don't |
get_seat_map |
NO CACHE | Must reflect live availability; cache would over-sell |
create_booking |
NO CACHE | Idempotent by request_id but never cached |
cancel_booking |
NO CACHE | Same as create |
| Movie metadata (title, poster, duration) | 24h | Static; only changes on re-release |
Failure responses (4xx/5xx) NEVER cache.
SECTION 11 — ERROR CODES
| Code | HTTP | Meaning | TOMO retry |
|---|---|---|---|
INVALID_REQUEST |
400 | Payload malformed; field missing or wrong type | No retry |
INVALID_AUTH |
401 | Sandbox/prod credentials invalid | No retry; partner status review |
RATE_LIMITED |
429 | Partner-side throttle | 1 retry, 2s backoff |
INTERNAL_ERROR |
500 | Partner-side failure | 2 retries, exp backoff |
IDEMPOTENCY_VIOLATION |
409 | request_id reused with different payload | No retry |
SIGNATURE_INVALID |
401 (webhook) | HMAC mismatch on CPC POST | No retry; alert |
MOVIE_NOT_FOUND |
404 | Title in preferences doesn't match any current run | No retry |
NO_SHOWS_IN_WINDOW |
200 (empty array) | Valid response, no matches | n/a |
SHOW_SOLD_OUT |
409 (on create_booking) | Seats were available at search time, gone at booking | No retry; UI surfaces |
SEATS_PARTIALLY_UNAVAILABLE |
409 (on create_booking) | Some requested seats taken; partner returns which | No retry; UI re-prompts |
BOOKING_WINDOW_CLOSED |
410 (on create_booking) | Past advance_booking_cutoff | No retry |
AGE_VERIFICATION_FAILED |
403 (on create_booking) | Adult certification + user DNA suggests minor | No retry; UI surfaces |
CANCELLATION_WINDOW_CLOSED |
410 (on cancel_booking) | Past cancellation cutoff | No retry |
REFUND_NOT_PERMITTED |
403 (on cancel_booking) | Show policy explicitly non-refundable | No retry |
Free-text errors forbidden. Partner must map internal failure modes to these codes.
SECTION 12 — SANDBOX → PRODUCTION CHECKLIST
[ ] All four tools (search/get/create/cancel) implemented and return ShowListing/SeatMap/Booking/CancellationResult shape per §5
[ ] At least one real venue + 3 real shows in 24h window populated with non-test data
[ ] poster_url returns valid distributor artwork (TOMO image-checks via official BMS poster registry)
[ ] All controlled vocabularies respected (no free-text drift in format / genre / rating / chain / seat_class_label)
[ ] HMAC signing verified on test CPC webhook (TOMO posts a synthetic close, partner reflects it back)
[ ] amount_inr in CPC webhook is NET (verified by reviewer against partner's invoice format)
[ ] gst_inr / tips_inr / pass_through_inr fields populated correctly (not zero by default — must reflect reality)
[ ] No forbidden fields anywhere in any of 100+ sandbox responses
[ ] SLA p95 met: search ≤1500ms, seat_map ≤800ms, create_booking ≤4000ms
[ ] Cancellation policy honoured: test cancel after cutoff returns CANCELLATION_WINDOW_CLOSED
[ ] Idempotency tested: same request_id returns same booking_id; differing payload rejects
[ ] Accessibility flags (wheelchair_accessible, hearing_loop, audio_description_available) on every screen
[ ] Surge pricing transparent: when surge_active=true, surge_multiplier numeric and shown to user
[ ] Compliance docs uploaded: GSTIN, entertainment license (state-specific), privacy policy URL live
[ ] Privacy policy explicitly mentions QR ticket retention period
[ ] Customer support contact verified (email + phone), responds within 5min during showtime hours
SECTION 13 — ANTI-FABRICATION RULES
RULE 1: No paid_placement_score, ad_bid, sponsored_rank, promotion_priority,
kickback_amount, referral_fee_kickback, _partner_revenue_share fields
anywhere in any response. TOMO ingest scans field names AND semantic
equivalents. Single occurrence rejects the entire response.
RULE 2: availability.fast_selling=true ONLY when seats_available_total /
seats_total_capacity < 0.20. Partner CANNOT mark fast_selling true
to game ranking. TOMO cross-checks by also fetching seat map and
verifying the ratio.
RULE 3: pricing.surge_active=true requires surge_multiplier to be a real
number reflecting the actual markup applied. Cannot be "1.0" while
actually surcharging. TOMO compares declared price vs the receipt
on completed bookings — sustained mismatches result in suspension.
RULE 4: poster_url must be official distributor artwork. AI-generated posters
are forbidden. TOMO field-tests against the BookMyShow/distributor
official registry on a sample of listings; mismatches > 3% in a 30-day
window result in compliance review.
RULE 5: movie.duration_minutes must equal the actual runtime including
credits. Cutting credits to make a film look shorter (and thus
artificially earlier in a "showtime ends by 22:00" filter) is
a fabrication. TOMO verifies against IMDb/distributor public data.
RULE 6: review_score (when partner returns one in a future v1.1+) must be
unrounded floats from raw user reviews. No "rounded up to 4.5 for
marketing" — TOMO checks recent scores against historical distribution.
RULE 7: venue.distance_from_user_km must be computed from real coordinates,
not flat-rate estimates. TOMO cross-checks via Google Maps API
distance for a random 1% of listings; >10% deviation indicates
fabrication.
RULE 8: No "Top Pick" / "TOMO Recommended" / "Editor's Choice" badges in
any field. Ranking is source-blind via TTBS; partner cannot self-badge.
RULE 9: artificial_urgency_text fields are forbidden even when seats_available
is genuinely low. The fast_selling boolean is the ONLY way to signal
scarcity; UI copy is rendered by TOMO's widget, not partner.
RULE 10: Format claims must be accurate: a screening marked IMAX must run on
a certified IMAX screen, not an "IMAX-branded" upgrade. TOMO maintains
a registry of certified IMAX venues; mismatch is grounds for immediate
status: suspended.
VERSION HISTORY
v1.0.0 — 2026-05-11 — Initial spec authored by founder + Claude (session 6)
NET commission base locked the same day; reflected
in §8 webhook payload fields.
Major version bumps require all live partners to re-verify within 30 days or get suspended.