Workspaces

The unit of billing, audit, and member access.

A workspace is the top-level tenant in Ventus Sessions. Everything else — sessions, API keys, members, audit entries — lives inside one.

Properties

  • Slug: lowercase, URL-safe (acme, acme-staging). Used in dashboard URLs (/ws/<slug>/sessions) and in audit log filters.
  • Name: human-readable display name. Can be changed; the slug cannot.
  • Members: at least one. The creator is the first owner. See Members for the role matrix.
  • API keys: zero or more. Each key is scoped to one workspace. See API keys.
  • Created_by: the user who created the workspace. Cannot be deleted from the workspace; you can demote them via the Members screen.

Lifecycle

  1. Created when a signed-in user fills out the onboarding form. The

first workspace for a user is created at /onboarding; subsequent workspaces come from a tenant-admin endpoint that returns 403 for non-admins.

  1. Active while it has at least one owner. You cannot remove the

last owner — there's a guard at the API level.

  1. Suspended is currently not exposed in the dashboard. The backend

supports it (sessions auto-terminate; API keys 401), used by Ventus ops for billing-block scenarios.

  1. Deleted is hard-delete with a 30-day soft-delete grace. Sessions,

audit log, and members all cascade. Profiles are scrubbed.

Switching workspaces

The top-left workspace switcher lists every workspace where you're a member. Switching changes the URL prefix (/ws/foo/.../ws/bar/...) and re-issues a workspace-scoped session token under the hood.

If you only have one workspace, the switcher just shows its name.

Why not "organizations" or "teams"?

We considered the org / team / workspace three-tier model that GitHub uses. We deliberately stopped at one tier because:

  • Every cross-tenant boundary is one more place PHI can leak.
  • Most healthcare orgs operate as one-billing-entity, one-audit-trail.
  • Teams within a workspace are achievable via Member roles (viewer /

operator / owner).

If you genuinely need two-level isolation (e.g., a parent group billing multiple subsidiary workspaces), that's a use case we'd support via a parent-workspace SSO + a billing roll-up — not a hardcoded "org" tier. Talk to us.

Common operations

  • Create: at /onboarding for a first workspace, or via POST /v1/dashboard/workspaces for any signed-in user. The slug must be unique across the entire deployment.
  • Switch: pick from the top-left dropdown.
  • Rename (display name): Workspace settings → Name.
  • List my workspaces: GET /v1/dashboard/workspaces.
  • Delete: Workspace settings → Delete workspace. Requires typing the slug. 30-day grace; audit retained 7 years for HIPAA.

Where it lives in the data model

workspaces (id, slug, name, created_at, created_by_user_id)
  ↓
workspace_members (workspace_id, user_id, role)
  ↓
api_keys (id, workspace_id, masked_prefix, hashed_secret, label, created_at, revoked_at)
  ↓
sessions (id, workspace_id, created_by_api_key_id, status, …)
  ↓
admin_audit_log (id, workspace_id, actor_user_id, action, target, created_at)

Every row in every table below workspaces carries workspace_id and participates in workspace-scoped Row-Level Security on the Postgres side. That means even a bug in the application layer can't return a session from Workspace B to a request authenticated against Workspace A.