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.
Economics
Section titled “Economics”| Constant | Value |
|---|---|
| 1 coin | $0.01 USD (invariant across the platform) |
| Standard episode price | 30 coins ($0.30) (tunable per market via price_overrides) |
| Free episodes per series | 3 (per-series override allowed) |
| Daily free passes | 5 (global config) |
| Rotating free series | 1 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 |
| Unlocks | Permanent (never expire) |
Market
Section titled “Market”| Constant | Value |
|---|---|
| Launch languages | EN, AR, ID |
| Beachhead markets | Philippines (PH), Indonesia (ID), Egypt (EG) |
| Catalog floor | 40 series / ~800 episodes minimum before launch |
Non-negotiable product rules
Section titled “Non-negotiable product rules”Enforced in code, not in policy:
-
No paywall or ad at cliffhanger episodes. Episodes with
cliffhanger_score ≥ 70skip both the unlock prompt and rewarded-ad prompt on the following episode transition. Configurable viareward_configs.rewarded_ads_blackout_at_cliffhanger. -
Coin ledger is double-entry. Balance is derived from
SUM(coin_ledger.amount). Never stored as source of truth. Zero-sum invariant perevent_id. Enforced inLedgerService. -
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.
-
Every recommendation carries an explanation. Every collection has a required non-null
explanationfield. Enforced at the collection service. -
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.
Admin safety rails
Section titled “Admin safety rails”| Rail | Value |
|---|---|
| Support daily coin-grant cap | 500 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 delete | always requires two-person approval |
| Role changes | always require two-person approval (superadmin approver) |
| GDPR anonymize | always requires two-person approval |
| Admin JWT TTL | 12 hours |
| Login rate limit | 10 attempts / 5 min / IP |
Payment rails
Section titled “Payment rails”The 5 rails and their constraints:
| Rail | Store policy |
|---|---|
| Apple / Google IAP | The only payment method advertised in the iOS app (Apple policy). |
| Cards (web) | Direct via card MID, tokenized for recurring. |
| dLocal | Local wallets & cash vouchers (Fawry EG, OVO/DANA ID, GCash PH). |
| pawaPay | Mobile money (M-Pesa, MTN MoMo, Airtel). |
| VAS / DCB | Via 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.
Stack constraints
Section titled “Stack constraints”| Constraint | Value |
|---|---|
| API runtime | Node 22, NestJS |
| DB | Postgres 15+, snake_case, Prisma |
| Redis | 7+, feed cache / rate limiting |
| Video | Cloudflare Stream (MVP); custom pipeline is phase 2. HLS-only, signed URLs. |
| Admin stack | Next.js 16 (app router, React 19, Turbopack default), Tailwind |
| API prefix | /v1 |
| API response format | camelCase JSON |
| API route format | kebab-case |
| Datetimes | UTC ISO-8601 |
| Money | cents (integer) |
| Coins | integer count (never fractional) |