--- 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: 1. Doubles the cost for no clear product reason 2. Creates billing friction (two invoices, two payment methods) 3. Discourages expansion 4. Misses the wedge story — "one account, multiple academies" ## Goals 1. Let a customer create multiple workspaces (full orgs) under a single subscription 2. Enable centralized billing — one Stripe customer, one invoice 3. Share quotas (learners, AI tokens) across linked workspaces 4. Surface this as a paid tier feature: Growth includes 2 workspaces, Business 3, Enterprise unlimited; extras as paid add-on 5. 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 `organization` row (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 usage - `DELETE /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 1. **Workspace switcher** — existing; needs to scope to "workspaces in my account" plus invited-as-member orgs 2. **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) 3. **Per-workspace settings** — billing UI hidden on secondary workspaces; shows "Billing managed by [primary workspace name]" 4. **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 `parentOrganizationId` to `organization` - 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 `account` table (Option B) if data model strain emerges ## Open questions 1. **Should free-tier users be able to create multiple workspaces?** Recommend: no (1-workspace cap). 2. **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. 3. **Are workspaces transferrable between accounts?** v1: no, manual support. v2: yes, with consent flow. 4. **Subdomain vs custom domain per workspace:** already supported per-org — confirm SSO/auth flows scope correctly. 5. **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