Skip to content

Pricing overrides

Every episode has a price at unlock time. That price is resolved through a strict cascade:

episode override → series override → market override → global default (30 coins)

Higher in the cascade wins. All prices are expressed in coins (1 coin = $0.01 USD).

Monetization → Pricing (/monetization/pricing) is the master surface. Three tabs:

  • Default: read-only; shows the global 30-coin constant. Cannot be edited via UI (constant per CONSTANTS.md).
  • Market overrides: per-country adjustments.
  • Series/Episode overrides: per-title adjustments (edit from the series detail page too).

Example: launch pricing in Egypt at 25 coins per episode.

  1. Pricing → Market overrides → New override
  2. Fill:
    • scope='market'
    • scope_ref='EG' (ISO-2)
    • coins=25
    • currency_hint='EGP' (informational: the mobile app already knows the user’s currency)
    • active_from (optional) / active_to (optional): leave blank for indefinite
  3. Save.

Every EG user will now see 25 coins per episode unless the series or episode has a more-specific override.

From the Series detail → Pricing tab:

  • scope='series'
  • scope_ref=<series_id>
  • coins=<N>

A series-level override wins over market. Common use: premium-priced flagship shows.

From the Episode editor:

  • scope='episode'
  • scope_ref=<episode_id>
  • coins=<N>

Wins over series and market. Common uses: finale episodes at a premium, special one-shot free episodes (coins=0, though the free-episode-count on the series usually covers this).

active_from and active_to are ISO datetimes. A override with:

  • active_from=null, active_to=null → indefinite
  • active_from=2026-08-01, active_to=null → starts on 2026-08-01, never expires
  • active_from=null, active_to=2026-08-31 → active until end of Aug
  • both set → windowed

The resolver at unlock time filters by active_from ≤ now() ≤ active_to.

  1. Look up all price_overrides for the target episode’s ID (scope=episode, scope_ref=episode_id, active window includes now).
  2. If none, look up series overrides (scope=series, scope_ref=episode.series_id).
  3. If none, look up market override for the user’s country (scope=market, scope_ref=user.country_code).
  4. If none, use the global default (30 coins).
  5. The first hit wins: no averaging, no summing.
  • PricingService.resolveForEpisode(episodeId, userCountry) in the API.
  • Called every time the mobile app hits /v1/episodes/:id/unlock/quote and /v1/episodes/:id/unlock.
  • Also read at admin catalog list time to show the effective price per episode in the panel.

Every override create/update/delete writes an AuditLog entry with the full before/after JSON. Bulk price changes: use /v1/admin/pricing/overrides with the bulk endpoint (planned for launch): until then, individual writes.

  • The default 30 cannot be edited: it’s the constant. Change it globally would require a code change.
  • coins=0 overrides: treated as “free” and skip the paywall. Use sparingly; usually the free-episode-count series field is a better lever.
  • Currency hint is informational. The mobile app knows the user’s currency from their locale. currency_hint is only for admin UI display convenience.
  • Overrides don’t apply retroactively to unlocks already purchased. Only new unlocks feel the new price.
ActionEndpoint
Get pricing configGET /v1/admin/pricing/config
Create overridePOST /v1/admin/pricing/overrides
Update overridePUT /v1/admin/pricing/overrides/:id
Delete overrideDELETE /v1/admin/pricing/overrides/:id

Full details in API: Monetization.