Skip to content

Constants & business rules

Anything on this page is canonical: set in CONSTANTS.md at the repo root and enforced across API + mobile + admin. Changing any of them requires a coordinated change across all three surfaces.

ConstantValue
1 coin$0.01 USD (invariant across the platform)
Standard episode price30 coins ($0.30) (tunable per market via price_overrides)
Free episodes per series3 (per-series override allowed)
Daily free passes5 (global config)
Rotating free series1 per day (per market or global)
Rewarded ads/day≤3 per user (server-side cap, config-tunable in reward_configs.rewarded_ads_daily_cap: do not exceed 3)
TwistCue+ pricing$3.99/wk · $9.99/mo · $59.99/yr · 7-day trial · 200 bonus coins/mo
UnlocksPermanent (never expire)
ConstantValue
Launch languagesEN, AR, ID
Beachhead marketsPhilippines (PH), Indonesia (ID), Egypt (EG)
Catalog floor40 series / ~800 episodes minimum before launch

Enforced in code, not in policy:

  1. No paywall or ad at cliffhanger episodes. Episodes with cliffhanger_score ≥ 70 skip both the unlock prompt and rewarded-ad prompt on the following episode transition. Configurable via reward_configs.rewarded_ads_blackout_at_cliffhanger.

  2. Coin ledger is double-entry. Balance is derived from SUM(coin_ledger.amount). Never stored as source of truth. Zero-sum invariant per event_id. Enforced in LedgerService.

  3. Guest data survives account creation. Saves, watch progress, coins, streaks are preserved when a guest signs up. On coin balance conflict (guest had bonus, signup had bonus), higher balance wins.

  4. Every recommendation carries an explanation. Every collection has a required non-null explanation field. Enforced at the collection service.

  5. Max 3 pushes/day per user. Server-side hard cap in the push-scheduler’s filter step. Users beyond 3 are silently dropped from sends. Configurable per-user via push category opt-outs.

RailValue
Support daily coin-grant cap500 coins/user/day (per admin-users.service)
Coin-grant approval threshold> 2000 coins requires two-person approval
Refund approval threshold> $50 (5000 cents) requires two-person approval
Series deletealways requires two-person approval
Role changesalways require two-person approval (superadmin approver)
GDPR anonymizealways requires two-person approval
Admin JWT TTL12 hours
Login rate limit10 attempts / 5 min / IP

The 5 rails and their constraints:

RailStore policy
Apple / Google IAPThe only payment method advertised in the iOS app (Apple policy).
Cards (web)Direct via card MID, tokenized for recurring.
dLocalLocal wallets & cash vouchers (Fawry EG, OVO/DANA ID, GCash PH).
pawaPayMobile money (M-Pesa, MTN MoMo, Airtel).
VAS / DCBVia adbilling.me MSISDN+PIN funnel. Grants app entitlements.

Store rule: never link/advertise external payment options inside the iOS app. Web/LP-acquired entitlements are consumed in-app (multiplatform rule). Android in beachhead markets may use user-choice billing where available.

ConstraintValue
API runtimeNode 22, NestJS
DBPostgres 15+, snake_case, Prisma
Redis7+, feed cache / rate limiting
VideoCloudflare Stream (MVP); custom pipeline is phase 2. HLS-only, signed URLs.
Admin stackNext.js 16 (app router, React 19, Turbopack default), Tailwind
API prefix/v1
API response formatcamelCase JSON
API route formatkebab-case
DatetimesUTC ISO-8601
Moneycents (integer)
Coinsinteger count (never fractional)