Plan Model¶
Plan lives on the org, never on the user. A user's effective tier is always the tier of their active org.
See also: docs/SPEC.org-plan-model.md in relay-platform for the full contract.
Two planes¶
| Plane | Org type | is_personal |
Tiers |
|---|---|---|---|
| A — Personal | Single-member | true |
beta · free · pro |
| B — Team | Multi-member, seat-billed | false |
team · enterprise |
Plane guards (shipped in PR #1241): personal orgs cannot gain members or a team plan. These are enforced in org_billing.tier — do not write to org_quotas for enforcement.
Tier reference¶
| Tier | Who | How assigned |
|---|---|---|
beta |
EAP cohort — grandfathered early access | Admin-set at signup via joined_via='eap' |
free |
Default after EAP graduation (no card) or grace expiry | System sweep job or admin |
pro |
Paying personal org | Self-serve upgrade (step 6) or admin-set |
grace |
EAP graduated, no card, within grace window | Admin-triggered graduation, no card on file |
team |
Seat-billed org | Admin or Stripe |
enterprise |
Custom contract | Admin only |
Back-compat: standard is a permanent alias for pro in all code paths — existing rows with tier='standard' resolve as Pro. Never write standard for new records.
Early Access Program (EAP)¶
EAP users land at tier='beta' with joined_via='eap'. Beta is indefinite — no auto-expiry until graduation.
Graduation (Step 4 — in progress)¶
Admin triggers graduation via console. Platform logic:
Grace sweep job (in loop.py, daily):
- Query orgs where grace_expires_at <= now()
- Set tier = 'free', clear grace_expires_at
- Notify user
Notifications on plan change¶
- Beta → Pro: "Your account has been upgraded to Pro"
- Beta → Grace: "Your trial ends {date} — add a payment method to continue on Pro"
- Grace → Free: "Your trial has ended — you're now on the free plan"
- Free → Pro: upgrade confirmation
Admin controls (console)¶
All plan changes go through POST /api/admin/v1/orgs/{id}/plan (or org_plan_update). Changes are two-way synced with Stripe — a console change updates Stripe, and a Stripe webhook mirrors back.
Available admin actions:
- Set tier directly (any tier)
- Trigger EAP graduation
- Set/clear grace_expires_at
- View plan history (Stripe events via processor_events table)
Self-serve upgrade (Step 6 — planned)¶
Personal org owners will be able to upgrade free → pro via:
1. Plan selection screen in app (or settings)
2. Stripe checkout session
3. On checkout.session.completed webhook: tier → pro immediately
Downgrade (pro → free) at end of billing period.
Loop program¶
EAP users get joined_via='eap' stamped at signup. Loop enrollment is gated behind LOOP_ENABLED flag (currently off). When enabled, this hook fires at EAP signup — program details TBD.
Finance tracking¶
Plan-related revenue flows through:
- processor_events — raw Stripe webhook payloads
- finance_transactions — normalized money movements
- discount_commitments — Loop early access, referral, coupon discounts
- revenue_snapshots — daily MRR/ARR (pulled live from Stripe subscriptions)
Known gaps: churn tracking fields (churned_revenue, new_subs, cancelled_subs) are always 0 in snapshots — not yet wired. checkout.session.completed not handled in StripeAdapter.
Last updated: 2026-06-14