#Organizations
The organization is the primary resource context. Servers, members, roles, providers and the audit log all belong to one organization. Backend Phase 4 "Organization & Members" is now complete (2026-10-03, by Pair 1) V8 requirement, so this module finally has rules: see FE: Organization & Members screens for the screen spec. The old gap D-25 is closed.
The V7 detail below stays as background for the parts Phase 4 doesn't spell out.
#V7 data model (reference)
| Table | Key fields | Notes |
|---|---|---|
organizations | user_id (creator/owner), name, description, logo, key, main, soft delete | Each organization gets its own SSH key pair in V7 (used by the V7 agent; not needed for OSS) |
organization_members | organization_id, user_id (null while invited), server_id (set = shared-server member), email, designation, invitation_token | One row per person per organization |
organization_roles | organization_id, role | owner and admin created with every organization |
member_role | member_id, role_id | A member can hold several roles |
organization_metas | name/value | e.g. onboarding_skipped |
#Operations
| Operation | V7 behaviour | V8 status |
|---|---|---|
| List my organizations | Organizations where I'm a member, with members and their roles. Also GET /auth/user/organizations | Missing |
| Create | name (required, validated), description, logo (jpeg/png, otherwise a generated avatar). Creates owner + admin roles with default permissions and adds the creator as Owner. The first organization gets main = 1. Audit "Organization Create" | Missing |
| Rename / settings | name, description, logo. Owner only ("You don't authorize…") | Missing |
| Delete | Refused when: it's the main organization; it's the user's only organization; it still has servers ("Please delete servers before deleting organization"). Members are removed, the organization soft-deleted, audit written | ✅ BUILT 2026-10-09 (Pair 1) V8 requirement — and it keeps V7's shape with the detail now published: owner only (an Admin gets 403), the owner types the organization name to confirm, and it is refused while the organization is the owner's default, which is what keeps the last one alive. Roles, memberships, pending invitations and the logo are removed with it. The servers check arrives in Phase 8 (422 organization_has_servers), so it is not enforced yet. V7's "only organization" rule stays implied — the default always exists (D-2) |
| Onboarding | essential-point checklist and onboarding-skip | Missing Open question keep? |
| My permissions | GET /organizations/{org}/my-permissions/{level} returns the permission list for the current user (owner, member, plan-gated) | Missing |
#Ownership transfer readiness
B4.7 built (Pair 1, 2026-10-09), including the previously pending deletion-safety extension. Current owner starts/cancels; only the chosen existing member accepts within 48 hours. Both must be active and verified and independently confirm with their own password/email code plus their configured 2FA. Cancellation needs no new credential challenge. Previous owner becomes Admin; recipient becomes Owner; both defaults stay unchanged.
A former owner cannot delete an organization after transfer completes, even from a stale dialog. A transfer completed during the former owner's account deletion preserves that organization for its new owner.
Unpaid-charge and recipient-plan checks are not enforced yet (Phases 6–7). Do not promise these blocks today. Preview email delivery is not verified. Full fields, errors and states: Ownership transfer spec.
#Switching organization
V7 puts the organization in the URL of every API call (/organizations/{organization}/servers…), and the frontend keeps the current one. V8 should keep that: it's explicit, cacheable and safe. V7 only Assumption
What happens on switch:
- The switcher (top bar) lists organizations from the session payload, with the user's role in each.
- The frontend stores the choice (cookie, so server components can read it) and cancels pending requests for the old organization.
- Every organization-scoped resource reloads: servers, providers, members, roles, blueprints (if organization-scoped), audit log, dashboard, subscription and usage, permission map.
- The user stays on the same section (e.g. Servers) of the new organization. Detail pages (
/servers/:id) go back to the list, because that ID belongs to the old organization. - The backend never trusts the stored choice: every request is checked for membership of the organization in the URL → 403/404 otherwise.
#Organization settings page
| Field | Rule | Source |
|---|---|---|
| Name | Required, unique per owner (V7 CheckNameValidation) | V7 only |
| Description | Optional text | V7 only |
| Logo | jpeg/png upload. Generated avatar otherwise | V7 only |
| Danger zone | Delete, with the V7 refusal rules above, plus delete protection (V8 3.6: checked before any delete) | V7 only V8 requirement |
#Organization-scoped vs user-scoped
| Organization-scoped | User-scoped |
|---|---|
| Servers, server links | Profile, password, 2FA, IP whitelist, sessions |
Providers (V7 cloud_server_providers.organization_id) | Notification channels (V7 notification_channels.user_id) |
| Members, invitations, roles | Wallet credits, cards, transactions (V7 user-level) |
| Audit log | Subscription (V7), see the conflict above |
Invoices (V7 invoices.organization_id, nullable) | Blueprints (V7 wordpress_blueprints.user_id) Open question |
#Edge cases
| Case | Expected behaviour |
|---|---|
| User has no organization | Only "Create organization" (and account pages) are reachable |
| Current organization deleted by its owner | Next request → 404/403 → frontend clears it, picks the main/first organization, toast "That organization is no longer available" |
| Member removed while working | Same as above. In-flight actions fail with 403 |
| Two tabs on different organizations | URLs carry the organization, so each tab stays consistent. "Current" is the last switched |
| Organization with servers, delete clicked | Refused, with a link to the server list |
#Built list filters (B4.1, Pair1 2026-10-10)
GET /api/v1/organizations supports literal name search(max100) and type=all|mine|invited. All includes memberships plus valid pending invitations to the current verified email; Mine means current owner+membership; Invited means joined non-owned+eligible pending. Unverified users see memberships only. Sorted name/ID, one row/org, no pagination. Pending rows show basic name/public logo only and give no access; acceptance still uses the invitation email link, not a token from this list. Profile/login lists stay joined-only, so pending cards never enter the switcher. Screen states and safeguards. Pair2 now reports this update pulled through a7aefc9.