Skip to content

Billing & Entitlements

Epic 13 replaced ad-hoc tier checks with a single entitlement system: a database catalog of limits, quotas, and features, resolved per billing account, enforced in the database, and translated into one canonical error envelope for the UI.

Mental model

Three rules explain almost every design decision in this section:

  1. The catalog is the single source of numbers. Every advertised and enforced limit lives in entitlement_catalog.plan_defaults (seeded in migration 20260608110521 §10, extended by later migrations). The public pricing page hydrates from the same table (buildPricingViewModel in src/lib/billing/pricing-display.ts, loaded by src/routes/pricing/+page.server.ts), so an advertised number and an enforced cap can never diverge. Dollar prices are the only exception: they live in code (pricing-display.ts for display, Stripe lookup_keys via src/lib/billing/price-catalog.ts for checkout), never in the DB.
  2. Resolution is per account, never per plan. The effective cap for (account, key) is computed by the SQL effective_cap() family: the catalog default (or an absolute per-account override), plus active additive override deltas, plus the remaining one-time add-on pool. Code must never infer a cap from plan_key; grandfathering, admin comps, Enterprise negotiated caps, and add-on packs all make a real account differ from its plan defaults.
  3. Fail closed (the C8 rule). A missing (account, key) row in account_entitlements is a DENY, distinct from a row whose cap resolves to NULL (intentionally unlimited). Enforcement lives in the database (BEFORE triggers and SECURITY DEFINER RPCs), so direct PostgREST writes hit the same walls as app code. App layers only pre-check for UX and translate DB raises into friendly errors.

Plans

Plan keys are defined in src/lib/billing/plan-mapping.ts and stored on billing_accounts.plan_key.

plan_keyDisplay namePLAN_ORDER rankCheckoutNotes
freeFree0None (signup default)Bootstrap plan for every new account
creatorCreator1Self-serve
studioStudio2Self-serve
foundersFounders2Self-serve (one-time offer)Resolves entitlements as Studio (see below)
production_houseProduction House3Self-serve
enterpriseEnterprise4Sales-led (invoiced, no self-serve)plan_key written only by provision_enterprise_account
  • SELF_SERVE_PLAN_KEYS = ['creator', 'studio', 'production_house', 'founders']. Free has no checkout; Enterprise is provisioned manually against a send_invoice subscription.
  • Founders → Studio remap: the catalog seeds no founders key. Feature baselines remap in TypeScript (remapPlanKey in src/lib/entitlements/core.ts); numeric caps remap inside entitlement_base_cap() in SQL. Founders ranks with Studio in PLAN_ORDER for upgrade/downgrade direction.
  • Legacy tier remap (legacyTierToPlanKey): free → free, starter → creator, professional → studio, enterprise → enterprise, unknown/NULL → free. Only used for grandfathered Stripe artifacts (old price.metadata.tier values and the legacy checkout body); the user_profiles.subscription_tier gate was decommissioned in GATE-3 (migration 20260611164202).

Display pricing

From PRICES in pricing-display.ts (display only; checkout resolves live Stripe prices by lookup_key):

PlanMonthlyAnnual (per month)Annual (per year)
Creator$24$19$230
Studio$59$47$566
Production House$149$119$1,430
Enterprisefrom $499, custom ("Tailored for you", contact sales)
Founders$399 one-time, 3 years of Studio-equivalent access, then $49/mo grandfathered (vs $59 Studio list)

The Founders "100 early adopters" seat count is marketing copy (FOUNDERS_DISPLAY.seats); there is no code enforcement of that cap.

Pages in this section

Recommended reading order:

  1. Entitlement Model: the catalog, effective_cap(), consume_quota(), the feature-grant union, the full limits/quotas/features matrices, and add-on packs.
  2. Slot Models & Enforcement Walls: podcast slots, managed-episode slots (PT429), the recording window (PT422), the publish meter (PT430), external hosting (PT403), the storage cap, and starter-content slot semantics.
  3. Gate Errors & UI Surfaces: the GateError envelope, the surface mapper (upsell dialog vs inline vs toast), usage meters, and click-time gating.
  4. Provisioning: free bootstrap, webhook-only Stripe grants, Founders and Enterprise provisioning, admin comps, and grandfathering.

Internal documentation - Not for public distribution