Organizations and teams
The tenant model. Workspaces are the wall. Teams are org-local groups. Users are global with multi-org membership.
The Viewport tenant model is single-database, row-level, with workspace as the tenant. Users are global. Memberships are explicit. Teams are org-local groups. Every customer-data row carries a workspace_id and is filtered through a global tenant scope at the database layer.
If you've used Slack: same shape. One person, many workspaces, each workspace is a hard wall.
The shape
User (global)
├── membership → Workspace A (role: owner)
│ ├── Team: Platform
│ ├── Team: Reviewers
│ ├── Workflows, plans, vaults, inbox …
│ └── Audit log (org-scoped)
├── membership → Workspace B (role: admin)
│ ├── Team: Engineering
│ └── Workflows, plans, vaults, inbox …
└── membership → Workspace C (role: viewer)
└── …Workspace = the tenant
A workspace is the hard wall. It owns:
- Members (workspace_memberships).
- Teams (org-local groups).
- Machines (paired daemons).
- Workflows, plans, vaults, inbox items, runs, audit events.
- Integrations, webhooks, settings.
Workspaces cannot see each other. Resources cannot be shared across workspaces. A team in workspace A cannot receive an ACL entry for a workspace B resource.
Internally referenced by ULID. The slug is display-only.
User = global identity
One user account per email. The same user can belong to many workspaces with different roles in each. There's no users.tenant_id. Sessions cookies are tenant-agnostic; the current org is a per-request context.
Active organization context
For every API request that touches tenant data, the platform resolves the active organization:
- Route binding: if the route has
{workspace}, that wins. - Request header:
X-Viewport-Organization: <ULID>. - Token claim: if the auth token is org-bound, that's used.
- Else: fail closed. The request is denied or the page asks the user to pick an org.
The platform verifies the actor has a membership row in the chosen org. No membership = 403 tenant_forbidden.
This means:
- Web, mobile, CLI, daemon, integrations. They all pass org context in the same way.
- One user can have web on org A and mobile on org B simultaneously without confusing the server.
- API tokens can be org-bound (recommended for CI) or user-bound (require explicit header per request).
Team = org-local group
Teams are workspace-scoped collaboration groups. They're not tenants. They're ACL principals.
Rules:
- A team has exactly one
workspace_id. Cannot move. - Same slug can repeat across orgs (each "Platform" team is its own thing).
- A team member must already be an org member. The platform enforces this at write time.
- Team grants on resources are workspace-scoped: an ACL entry where
team.workspace_id != resource.workspace_idis rejected. - Teams are never auto-created. An org starts at zero teams.
Roles
| Layer | Roles |
|---|---|
| Org | owner, admin, member, viewer |
| Team | lead, member |
| Resource | owner, admin, editor, reviewer, viewer |
Roles compose: a workspace admin who's also a team member can do everything an admin can plus whatever team membership grants. Multiple grant paths give a user the most permissive resulting role.
The daemon's org binding
A daemon can be paired to one or more orgs. Each org binding has its own:
- Crypto identity (separate keypair file).
- Machine ID (different ULIDs in different orgs).
- Install ID, runtime target ID, relay endpoint.
- Bound directories.
So the same physical laptop can stream from ~/work to Acme and from ~/personal to Personal without either org ever seeing the other org's metadata or machine identity. See Concepts: Machines and pairing and Concepts: Trust and privacy.
What multitenancy gets you
- Personal data stays personal. A consultant working on three clients keeps three clean inboxes, three audit logs, three sets of policies.
- A compromise of one org doesn't cascade. Audit log compromise in Acme reveals nothing about Personal.
- Different teams, different rules. Aggressive auto-approval for OSS, strict gates for the production org.
- Clean offboarding. Removing a user from one org leaves their other memberships untouched.
What multitenancy does not do (today)
- Schema-per-tenant or DB-per-tenant isolation. Single shared DB with row-level scope. If that's a non-starter for your security posture, talk to us about on-prem.
- Org-to-org sharing. You can't share a workflow from org A into org B. Export/import is on the roadmap.
- Cross-org search. Each org search is its own. We don't have a global "search across all my orgs."