Skip to main content

Monetization — Companion Paywall & Token Allocation

Status: Proposed 2026-07-09 — ready to build · researched against July-2026 store rules and live COGS

The model in one paragraph: free users get a sample — Kaori visible and usable with a small daily voice allowance, other companions shown locked. Paid tiers unlock the full roster plus a monthly voice-token allotment (1 token = 1 spoken reply). Cosmetics (boards, outfits, environments) unlock through BOTH usage (XP/streaks) and payment tiers. Web sells via our existing Stripe; mobile sells via RevenueCat IAP; the backend Mongo user doc is the single source of truth.

Why tokens — the unit economics

Voice is where the money burns. Per Kaori reply (~250–350 chars TTS, ~5k-token prompt):

Cost driverPer voice replyPer text reply
OpenRouter LLM (Gemini Flash class)~$0.008–0.012~$0.01
ElevenLabs TTS (current model)~$0.05–0.10
ElevenLabs TTS (Flash-class model)~$0.03–0.05
All-in~$0.04–0.11~$0.01

Two COGS levers to pull before revenue work (halves cost, better latency):

  1. Switch stage TTS to a Flash-class ElevenLabs model (0.5 credits/char vs 1)
  2. Enable OpenRouter prompt caching for Kaori's big static persona block (60–80% cheaper repeated context)

The token model (v1)

  • 1 token = 1 voice reply (one TTS synthesis of one bot message). Text is NOT tokenized — it gets a coarse anti-abuse daily cap (~50/day free). Simple mental model: voice costs tokens.
  • Metering lives server-side: one requireVoiceTokens middleware at the places a /speak fires (routes/botChat.js, routes/dm.js, kaori-live path). Atomic decrement (findOneAndUpdate with $gte: 1 guard) before calling kith. On empty wallet: still return the text reply with voiceExhausted: true so the stage shows a top-up prompt instead of going silent.
  • Every debit logs to usage_events (userId, botId, chars, ts) — this doubles as the usage-analytics foundation the UX audit needs.
  • Free daily grant is lazy (a dailyGrant date field checked on first request of the day) — no cron.
  • The Kith sidecar stays dumb; Express is the only gate.

Tiers (starting point — tune with usage_events data)

TierCompanionsVoice tokensTextPrice
FreeKaori only (sample)5/day50 msgs/day$0
PlusFull roster200/mo (rollover ≤2×)Unlimited*~$10/mo (existing TrickBook Plus)
Top-up packse.g. 100 tokens~$4.99 (consumable IAP / Stripe)

Sanity check: 200 voice tokens ≈ $5–7 COGS on Flash+caching (~40% margin before store fees). Free tier worst case ≈ $6/mo per maxed-out user — acceptable sampling cost.

Platform rules (verified July 2026)

  • Apple: in-app digital purchases still require IAP (guideline 3.1.1). Post-Epic US anti-steering: apps MAY link out to web checkout, currently 0% Apple commission — but the fee question is back in district court, don't build anything that assumes 0% is permanent. Web-bought tokens are legal to consume in-app (multiplatform rule).
  • Google (US): alternative billing + external links allowed since Dec 2025; Epic settlement (reduced fees) pending approval.
  • Practical: sell tiers + token packs via IAP in-app on both stores, AND surface a "buy on thetrickbook.com" link on US storefronts to the Stripe checkout. Identical pricing; keep the margin difference.

Architecture

  • RevenueCat for mobile IAP (react-native-purchases — Expo-compatible, needs the dev-client build we already need for the stage; free until ~$2.5k MTR). One webhook → backend.
  • Keep the existing Stripe flow (routes/payments.js already implements the full TrickBook Plus lifecycle) — extend with tier price IDs + one-time token-pack prices. Don't migrate it into RevenueCat in v1.
  • Mongo is canonical: user.subscription (existing) + user.wallet.voiceTokens (new) + unlocks collection (new). Both webhooks (Stripe, RevenueCat) write into the same fields, idempotent on event id. The app gates from GET /api/payments/subscription, never from client-side store state.

Unlockables

One unlocks collection: {userId, itemId, source: 'xp' | 'tier' | 'purchase', grantedAt}. Client only ever asks "is itemId unlocked", never "how". Tier-sourced unlocks deactivate on downgrade; XP- and purchase-sourced are permanent.

Build order by asset cost:

  1. Boards (easiest, most on-brand) — separate .glb props, no VRM surgery; deck graphics are cheap content
  2. Colorways — runtime MToon base-color texture swaps on the existing VRM; dozens of recolors from one avatar
  3. Full outfits (heaviest) — one complete .vrm per outfit (runtime mesh-swapping inside a VRM is fragile); device-cached, lazy-downloaded, keep to 3–5 per companion
  4. Environments — independent of the VRM: skybox/ground/props swap in the scene. Kaori gets snow; each sport gets its own vibe. Unlockable the same way.

Apple compliance note: XP/streak-earned cosmetics are fine; directly-purchased cosmetics on iOS must be IAP products — pricing cosmetics in voice tokens is the elegant move (tokens are already a compliant consumable).

Build phases

  1. Metering foundation + cost control (backend only): wallet + usage_events + requireVoiceTokens + lazy daily grant + voiceExhausted; flip TTS to Flash-class; enable prompt caching
  2. Free-sample gating: companion list returns locked/unlocked per plan; mobile picker + web locked states; text daily cap; adminOverride for QA
  3. Web purchases first (fastest to revenue, zero store review): extend payments.js with tier + token-pack prices; paywall + wallet UI on the site
  4. Mobile IAP via RevenueCat: store products, RC webhook → same Mongo fields, server-driven paywall screen (bundle into the Kaori-stage EAS build)
  5. US steering escape hatch: external purchase links on iOS/Play US storefronts (re-check the Apple fee ruling first)
  6. Unlockables v1: unlocks collection, board props + colorway swaps, cosmetics priced in tokens, XP earn rules from usage_events
  7. Unlockables v2 + world-building: full outfit VRMs, snowy Kaori environment, per-sport stages
  8. Tune with data: usage dashboards, re-price allotments from observed chars/reply + cache hit rates; every new companion (Tony, etc.) is then content, not new billing plumbing