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.0entertainment.book_movie_ticket

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 screen
  • entertainment.book_comedy_show — live stand-up, no screen
  • entertainment.subscribe_streaming — at-home, no venue
  • food.book_dine_in — F&B-primary, even if at a cinema dine-in
  • entertainment.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_priority
  • kickback_amount, referral_fee_kickback, _partner_revenue_share
  • artificial_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_price or 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.