Authoritative entitlement and quota design
Backend & Database Implementation Plan
Prepared 18 July 2026 · product requirements normalized from the placement artifact, current implementation status, and founder requirements
Target architecture
RevenueCat projects commerce state; billing grants overlay it; backend resolves one effective access snapshot; immutable quota ledgers enforce every expensive or paid action. iOS never decides whether the backend should spend generation resources.
Retain and change
| Existing component | Decision |
|---|---|
billing_events | Retain as immutable provider/client inbox; extend captured fields. |
billing_access | Retain provider projection; add trial/access phase, product/offer identifiers, environment, and trial dates. |
billing_grants | Retain overlay model; add optional permanent grant support and bulk-job provenance. |
users.subscription_tier and legacy subscriptions | Stop treating as writable truth. Migrate/derive or deprecate after consumers move. |
plan-config.ts | Replace provisional minute-centric values with versioned approved plan definitions. |
| Usage placeholders | Replace zeros with authoritative quota balance and immutable usage. |
Required schema concepts
- Plan definition/version: effective dates, tier, capabilities, quota rules, rollover caps, trial overrides.
- Billing access phase: free, trial, paid, grace, billing retry, promotional, complimentary, expired.
- Usage account: user, meter type, period start/end, base grant, rollover in/out, consumed, reserved, version.
- Usage ledger: immutable credit/debit/reserve/release/expire/adjust entries with idempotency key and source resource.
- Offer state: standard/rescue50/rescue65, last impression/dismissal, cooldown, campaign, eligibility result.
- Paywall exposure: placement, offering, stage, timestamps, outcome, app build, device/session.
- Grandfather batch: filter snapshot, dry-run count, actor, status, campaign, plan, start/end, errors.
- Grant provenance: optional batch ID, policy version, permanent flag or nullable expiry.
Meter definitions
| Meter | Debit moment | Refund/release |
|---|---|---|
| Recurring generation credit | Reserve before scheduled job enqueue | Release terminal technical failure; consume on generation acceptance/completion per policy |
| One-time generation credit | Reserve before job enqueue | Release failed/canceled job; prevent duplicate debit with request id |
| Explore listening seconds | Debit server-accepted playback heartbeat/progress deltas | Deduplicate by session and monotonic position; no debit for seeks/replays already counted per policy |
| File upload credit | Reserve on upload session creation | Release failed/abandoned session after timeout; consume after validated object commit |
| Episode duration limit | Validate before generation | No debit; structured denial |
Enforcement checklist
- Create one authorization service that resolves effective access and checks capability + meter atomically.
- Generation endpoint validates type, duration, recurring/one-time meter, then reserves a credit before queue submission.
- Scheduler reserves recurring credit before each generated episode.
- Document upload session checks capability, count credit, MIME/type policy, and maximum bytes before signed upload URL.
- Remix, transcript, source monitoring, creator export, speed-dependent server assets, and soundscape catalog endpoints filter by capability/tier.
- Explore playback service returns remaining seconds and stops issuing playable/signed URLs when the free limit is exhausted, while metadata remains visible.
- Return structured errors:
CAPABILITY_REQUIRED,QUOTA_EXHAUSTED,DURATION_LIMIT,FILE_LIMIT,ACCESS_UNKNOWN. - Every denial includes required tier, current balance, reset time, rollover balance, and recommended paywall placement.
Trial projection
- RevenueCat
period_type=TRIALsets access phase trial and exact expiry. - Tier remains Plus, but plan variant
plus_trial_v1overrides recurring generation to one credit for the weekly window and one total during the trial. - On normal renewal, switch to approved Plus plan version without resetting usage incorrectly.
- On trial cancellation, access remains through expiry;
willRenew=false. - On expiration, fall back to Free and open new Free allowance windows without duplicating rolled balances.
Offer and nudge decision API
Backend returns the eligible paywall stage and offering reference; RevenueCat/StoreKit remains final price eligibility authority.
- Persist standard→50→65 state across devices.
- Record dismissal and apply cooldown before another automatic/high-intent offer.
- Suppress acquisition offers for active trial/paid/grant users.
- Return lapsed/win-back state separately from new-user rescue.
- Expose nudge eligibility with reason, expiry, placement, priority, and frequency-cap token.
- Commit an exposure atomically so two devices cannot show duplicate nudges.
Subscription-change projection
- Persist current store product, service level, billing cadence, renewal/expiry, auto-renew state, and optional pending product/effective date.
- Treat RevenueCat
PRODUCT_CHANGEas informative; do not grant or remove capabilities until the corresponding active entitlement transaction is projected. - Apply an upgrade when the active higher-level product is confirmed; preserve the old plan until a scheduled downgrade or different-duration crossgrade becomes effective.
- Return pending-change details in the access API so iOS can say “Pro until [date], then Plus” without guessing.
- After manage-subscriptions dismissal or foreground, reconcile RevenueCat customer info and refresh the backend snapshot.
- Keep complimentary/grandfather grants as an independent overlay; a store downgrade or cancellation must reveal the correct remaining grant.
Migration plan
- Add new fields/tables without removing legacy fields.
- Backfill billing access for known paid/granted users and produce mismatch report against legacy tier.
- Move read consumers to canonical access snapshot.
- Implement quota ledgers in observe-only mode and compare expected vs actual usage.
- Turn on backend denials behind per-capability flags.
- Run grandfather batch before enabling affected gates.
- Freeze legacy writes, then remove/deprecate legacy tier paths after one full subscription cycle.
API checklist
- Expand
GET /billing/accesswith phase, plan version, trial dates, balances, rollover, reset times, offer state, and server time. - Provide idempotent quota preflight/reservation response to create flows.
- Provide authenticated paywall exposure/outcome endpoint if RevenueCat UI webhooks are insufficient for custom surfaces.
- Provide plan catalog endpoint for copy/limits, versioned and localized only where safe.
- Provide admin cohort preview, batch apply, status, audit export, revoke, and grant extension endpoints.
- Provide internal reconciliation/sweeper for missed webhook/expiry conditions.
Backend acceptance criteria
- Concurrent duplicate creation requests consume at most one credit.
- A failed job releases its reservation exactly once.
- Rollover caps and expiry work across DST, timezone, and billing-date changes.
- Trial→paid, trial→free, Plus→Pro, Pro→Plus, refund, transfer, and grant overlay produce correct snapshots.
- A modified client cannot exceed generation, file, playback, transcript, remix, or source limits.
- Usage and admin audit reconcile to ledger entries.