API: Public endpoints affected by admin state
These are public / mobile-facing endpoints (not under /admin/*) whose behavior is determined by decisions made in the admin panel. They’re documented here because operators need to know exactly what they’re affecting.
GET /v1/flags
Section titled “GET /v1/flags”Auth: none.
Returns feature flags whose key starts with public_. Non-public flags are stripped. Used by the mobile app at cold start.
{ "flags": { "public_vas_enabled_eg": true, "public_new_home_v2": { "enabled": true, "percentage": 25 } }}Admin edits this via PUT /admin/flags: see Feature flags guide.
GET /v1/feed
Section titled “GET /v1/feed”Auth: required (user JWT).
Query: market, lang, surface (default for_you).
Returns the composed feed for the user: rails, each with a resolved list of series. The composer reads:
- Active rails on the surface (from
railstable). - Each rail’s collection.
- If collection is
manual: thecollection_itemslist. - If
rule: the resolved query. - Free-today series for the market (from
free_series_rotations). - Preview-reel entries (from
series.editorial_hero_score).
Fallback: if the composer returns zero rails (e.g. curation empty in a new market), a hardcoded “newest published” collection is returned so the mobile app never renders empty.
Every rail includes an explanation string (non-null).
GET /v1/comments/preview
Section titled “GET /v1/comments/preview”Auth: required.
Query: episodeId, limit.
Returns the first N visible comments for an episode (default 10). Used for the paywall-adjacent comment preview strip.
status='hidden'andstatus='deleted'comments are excluded.- Users with 3+ strikes have their new comments marked
status='hidden'on creation (comment write endpoint, not this read).
POST /v1/promos/redeem
Section titled “POST /v1/promos/redeem”Auth: required.
Redeems a promo code entered by the user.
{ "code": "WELCOME2026" }- Validates window, segment eligibility, per-user uses.
- Mints coins (for
coin_grant) or applies the effect (forbonus_multiplieron next purchase,trial_ext). - Increments
promos.spent_coins.
Errors: promo_not_found, promo_expired, promo_budget_exhausted, promo_ineligible, promo_max_uses_reached.
GET /v1/wallet
Section titled “GET /v1/wallet”Auth: required.
Returns the user’s derived balance and recent ledger.
- Blocked with 403 if the user is
suspended,banned, ordeleted(see Suspend, ban, reactivate). - Otherwise returns
{ balanceTotal, balancePaid, balanceBonus, recentEntries: [...] }.
POST /v1/episodes/:id/unlock/quote
Section titled “POST /v1/episodes/:id/unlock/quote”Auth: required.
Returns the resolved price for the episode given the user’s country. The resolver walks the pricing cascade episode → series → market → default.
POST /v1/episodes/:id/unlock
Section titled “POST /v1/episodes/:id/unlock”Auth: required.
Spends coins to permanently unlock the episode. Writes double-entry ledger:
wallet_bonus(orwallet_paid) :-N(reason=‘unlock’)sink_unlock:+N(reason=‘unlock’, ref_type=‘episode’, ref_id=episodeId)
Spend order: bonus first, then paid.
POST /v1/subscriptions/webhook/*
Section titled “POST /v1/subscriptions/webhook/*”Auth: provider HMAC.
Provider webhooks (RevenueCat for IAP, card MID, dLocal, pawaPay). Each updates subscriptions + purchases rows and credits coins to the ledger. Admin visibility via transactions view.