12 KiB
name: Multi-Workspace (Multiple Orgs Under One Account) overview: Allow a single customer account to operate multiple organizations ("workspaces") under one subscription. The customer pays in their primary workspace; secondary workspaces inherit the subscription, share pooled quotas (learners, AI tokens), and don't bill separately. Surfaces as a paid tier feature — Growth includes 2 workspaces, Business 3, Enterprise unlimited; extras as a paid add-on. Wedge story: "one account, multiple academies" — customer-facing + internal + partners under one bill. todos:
- id: schema-decision content: Decide between Option A (parentOrganizationId on organization) and Option B (separate account table). Recommendation: Option A for v1. status: pending
- id: data-model content: Add parentOrganizationId column to organization table; backfill existing orgs to NULL (primary) status: pending
- id: billing-routing content: Refactor Stripe subscription handling so the primary workspace holds the subscription and webhook events propagate state to all linked workspaces status: pending
- id: pooled-quotas content: Move learner-count and AI-token quota enforcement from per-org to per-account (sum across linked workspaces) status: pending
- id: account-endpoints content: Build POST/GET/DELETE /account/workspaces and GET /account/usage endpoints status: pending
- id: account-settings-ui content: Build account settings page accessible from primary workspace — list workspaces, per-workspace usage, "Create workspace" CTA status: pending
- id: workspace-switcher content: Update existing org switcher to clearly distinguish "workspaces in my account" vs "orgs I'm invited to" status: pending
- id: secondary-billing-ui content: Hide or alias billing UI in secondary workspaces — show "Billing managed by [primary workspace name]" status: pending
- id: downgrade-grace content: Implement 7-day read-only grace period when a customer downgrades below their current workspace count status: pending
- id: free-tier-cap content: Enforce 1-workspace cap on Free tier to prevent abuse status: pending
- id: pricing-page-update content: Update marketing pricing page to surface "Workspaces included" row and add-on pricing status: pending
- id: homepage-section content: Add "One account, multiple academies" section to apps/website homepage (after feature ships) status: pending isProject: true
Multi-Workspace PRD
Status
- Draft (2026-05-15)
- Owner: TBD
Problem
Today, every ClassroomIO organization is its own billing entity. A customer who wants:
- A customer-facing academy on
learn.acme.com, AND - An internal employee training portal on
team.acme.com
…must create two separate orgs with two separate paid subscriptions. This:
- Doubles the cost for no clear product reason
- Creates billing friction (two invoices, two payment methods)
- Discourages expansion
- Misses the wedge story — "one account, multiple academies"
Goals
- Let a customer create multiple workspaces (full orgs) under a single subscription
- Enable centralized billing — one Stripe customer, one invoice
- Share quotas (learners, AI tokens) across linked workspaces
- Surface this as a paid tier feature: Growth includes 2 workspaces, Business 3, Enterprise unlimited; extras as paid add-on
- Keep each workspace fully independent operationally (own brand, domain, members, courses, certificates)
Non-goals (v1)
- Cross-workspace data sharing (a learner enrolling in courses across both workspaces with a single identity)
- Cross-workspace analytics roll-ups
- Reparenting a workspace to a different primary mid-subscription (manual support op for v1)
- Self-serve "split off" of a secondary workspace into its own subscription (manual for v1)
- Per-workspace seat limits separate from pooled quota
Users & jobs
| Persona | Job |
|---|---|
| Account owner (founder / VP CX) | "I want a second academy for our internal team without paying for a second plan." |
| Workspace admin (delegated owner of a secondary workspace) | "I want to manage my workspace fully — branding, courses, members — without seeing billing." |
| Learner | No change. Sees only their workspace. |
Key concepts
- Account — the billing entity. Tied to one Stripe customer. Owns 1+ workspaces.
- Workspace — a full
organizationrow (custom domain, branding, members, courses). Each workspace has exactly one parent account. - Primary workspace — holds the subscription. The first org a user creates becomes their primary by default.
- Secondary workspace — any workspace beyond the primary in the same account. Inherits subscription state.
User stories
Onboarding
- As an existing paid customer on Growth, I can create a second workspace from a "Workspaces" tab in account settings without entering payment info again.
- The new workspace counts against my plan's workspace allowance (Growth = 2 included).
- If I exceed the allowance, I'm prompted to add a paid workspace add-on or upgrade tier.
Day-to-day
- I can switch between workspaces via the org switcher.
- I can see pooled usage (X / 1,000 learners, Y / 10M AI tokens) and a per-workspace breakdown.
- A workspace admin (not the account owner) sees their workspace normally; billing UI is hidden or shows "Managed by [primary workspace name]".
Billing
- The Stripe subscription lives on the primary workspace.
- One invoice lists: base plan + workspace add-ons + overages.
- If the primary workspace cancels or fails payment, all linked workspaces are affected (downgrade to free or readonly per existing policy).
Downgrade / overage handling
- If a customer downgrades from Growth (2 included) to Starter (1 included) while having 2 workspaces:
- All workspaces remain readable but lock to read-only until the customer either deletes a workspace or upgrades back.
- 7-day grace period to resolve.
- If a customer exceeds pooled learner quota, existing per-tier overage policy applies.
Edge: primary owner leaves the company
- Support can manually transfer subscription ownership to another admin in the primary workspace. Self-serve in v2.
Functional requirements
Data model (proposed)
Option A — direct parent pointer (recommended for v1):
organization.parentOrganizationId → organization.id (nullable)
- Primary =
parentOrganizationId IS NULL - All workspaces with same parent (or themselves as parent) belong to one account
- ✅ Minimal migration
- ⚠️ Reparenting requires updating all children
Option B — explicit account table (cleaner long-term):
account (id, stripe_customer_id, plan_id, created_at)
organization.account_id → account.id
- ✅ Cleaner billing semantics; future-proof for transfers
- ⚠️ Migration: every existing org needs an account row
Recommendation: Option A for v1. Migrate to Option B in v2 if data-model strain shows up.
Subscription / billing
- One Stripe subscription per account, on the primary workspace.
- Subscription line items:
- 1× base plan
- N× workspace add-on (N = total workspaces − tier's included count, ≥ 0)
- Overage line items
- Webhook handlers must propagate subscription state to all linked workspaces.
Quota enforcement
- Learner count = SUM of active learners across all workspaces in account.
- AI token usage = SUM of tokens consumed across all workspaces in account.
- Quotas checked at the account level on every usage event.
- Per-workspace usage tracked for visibility, not enforced separately.
Routes & API surface
New endpoints:
POST /account/workspaces— create a secondary workspace (account-level auth required)GET /account/workspaces— list all workspaces in account with per-workspace usageDELETE /account/workspaces/:id— delete a secondary workspace (cannot delete primary while account exists)POST /account/workspaces/:id/transfer-primary— promote secondary to primary (v2)GET /account/usage— pooled usage breakdown
Existing endpoints affected:
- All
organization/*endpoints already scope to the active workspace — no change - Billing/subscription endpoints operate on the account/primary workspace
Dashboard UI
- Workspace switcher — existing; needs to scope to "workspaces in my account" plus invited-as-member orgs
- Account settings page (new) — accessible from primary workspace only:
- List of workspaces with status, learner count, AI usage
- "Create workspace" button (tier-aware: "1 of 2 included" / "Add another for $79/mo")
- Billing tab moved here from per-workspace settings (or aliased)
- Per-workspace settings — billing UI hidden on secondary workspaces; shows "Billing managed by [primary workspace name]"
- Pooled usage banner — in account settings: "1,247 of 5,000 active learners across 3 workspaces"
Pricing logic
| Tier | Workspaces included | Extra workspace |
|---|---|---|
| Free | 1 | – (must upgrade) |
| Starter $49 | 1 | – (must upgrade) |
| Growth $149 | 2 | +$79/mo each |
| Business $399 | 3 | +$149/mo each |
| Enterprise | Unlimited | included |
- Extras prorate normally.
- Annual subscriptions: workspace add-ons priced at 10× monthly (2 months free), matching plan annual logic.
Success metrics
- Activation: % of paid customers who create a 2nd workspace within 90 days. Target: 20% by month 6, 30% by month 12.
- Expansion ARR lift:
workspace_addon_mrr / base_plan_mrr. Target: 10%+ blended lift. - Retention: Net retention on customers with 2+ workspaces vs 1. Hypothesis: 2+ workspace customers churn ~50% less.
- Support tickets: No more than 1 ticket per 10 workspace creations in v1.
Rollout plan
v1 (4 weeks)
- Schema: add
parentOrganizationIdtoorganization - Backend: secondary workspace endpoints, pooled quota enforcement, billing updates
- Dashboard: account settings page, workspace switcher tweak, pooled usage banner
- Marketing: ship "one account, multiple academies" homepage section
- Ship to existing paid customers first (manual feature flag), then GA
v2 (post-launch, scope TBD)
- Self-serve "split workspace into own subscription"
- Cross-workspace analytics roll-up
- Account-level RBAC (single user as admin across all workspaces without per-org invites)
- Migrate to
accounttable (Option B) if data model strain emerges
Open questions
- Should free-tier users be able to create multiple workspaces? Recommend: no (1-workspace cap).
- Quota fairness: if Workspace A burns all the AI tokens, Workspace B has zero — do we expose per-workspace caps as an optional admin control? Recommend: not in v1.
- Are workspaces transferrable between accounts? v1: no, manual support. v2: yes, with consent flow.
- Subdomain vs custom domain per workspace: already supported per-org — confirm SSO/auth flows scope correctly.
- Billing edge case: when adding a paid workspace add-on mid-cycle, prorate immediately or at next renewal? Recommend: immediate prorate (Stripe default).
Risks
- Confusing UX if terminology isn't tight — pick one term ("workspace" or "academy") and use it everywhere
- Quota gaming — customers stuffing free workspaces; mitigated by 1-workspace cap on Free
- Per-workspace SSO config — each workspace's OIDC config is independent; confirm no cross-pollination of identities
- Backwards compat — existing orgs become primary by default; migration is a single backfill (no-op, NULL is already default)
Out of scope
- Cross-workspace shared course library
- Account-level admin role separate from per-workspace admin
- Public marketplace of shared courses across accounts