VibeHost
Guides

Workspaces & teams

A workspace owns apps and is the billing and admin boundary. A team is a group of members inside it, and grants give teams access to apps.

The model

Workspace ─── billing + admin boundary, owns apps
  ├── plan (Free / Business)
  ├── billing contact, invoices
  ├── workspace-level audit log
  ├── Workspace members ──► role: owner / admin / member
  ├── Teams ──► sub-groups of members
  │     └── Team members ──► role: owner / manager / member
  └── Apps ──► owned by the workspace
        └── App-team grants ──► viewer / deployer / admin

The diagram doesn't show apps belonging to a team, because they don't. Apps belong to the workspace. A team gets access to an app through a grant and never owns it. This matters when you're thinking about access:

  • A workspace admin sees all apps in the workspace.
  • A team member sees only the apps that the team (or their email) has been granted.

Workspace member roles

RoleWhat it unlocks
memberBe in the workspace; see what app-level grants give you
adminCreate teams, manage workspace settings, see all apps
ownerPlus billing, plan changes, workspace deletion

You can belong to multiple workspaces (e.g. personal and work). The CLI defaults to the workspace stored in ~/.config/vibehost/config.json. Switch with vibehost workspace use <slug>.

When to add a team

For an account of one to three people, one team is usually enough. Add more when:

  • Members of Team A shouldn't pick up app grants from Team B.
  • You want teams to mirror real groups in your organization (a squad, a department, contractors).
  • You want to share apps with a group in one step. "Everyone on the Web team can deploy to all marketing apps" is one grant per app on the team, not one grant per person per app.

The slug here (web) must be DNS-safe and lowercase. vibehost team switch web makes it the current team, and vibehost team info prints the current team's settings.

vibehost team create web
vibehost team ls
vibehost team switch web
vibehost team info

Team slugs don't appear in app URLs. A generated URL ends in the workspace slug instead (<app>-<workspace>.vibehost.space), because apps belong to the workspace, not to a team.

Team member roles

RoleWhat it unlocks
memberBe in the team; receive whatever app grants the team has
managerManage team members and team settings
ownerPlus delete the team

These are roles within the team. They don't grant anything on apps by themselves. The team has to receive an app grant separately (see Grants and visibility).

Inviting people

You can invite people to the workspace or to a team, depending on how broad you want their access to be.

Invite to the current workspace. Only workspace owners and admins can do this. Use it when the invitee's email isn't covered by the workspace's allowed-domain list. It prints a single-use link. The --role is admin or member:

vibehost workspace invite teammate@x.com --role member

Invite to the current team. This prints a sign-in link to paste into Slack or email. Only the invitee's email can accept; forwarded links are rejected server-side. The --role is member or manager:

vibehost team invite teammate@x.com --role member

If your workspace has a verified domain (see below), users matching that domain join automatically, so you don't need to invite them individually.

Linking teams to apps

A team gaining access to an app is a separate operation from team membership. Use app grants:

vibehost app grants add-team <team-slug> deployer --app my-site

Now every member of the team has the deployer role on my-site. Add more grants for more apps; revoke per-app with app grants remove-team.

For one-off invites without a team, grant by email:

vibehost app grants add-email contractor@x.com viewer --app my-site

Workspace-level actions

ls lists the workspaces you belong to, use <slug> switches the active workspace, info prints the current workspace detail, members shows who's in this workspace, and role <email> <role> changes a member's role.

members lists everyone sorted by role (owner, then admin, then member) and marks your own row with *. Pass --search <query> to filter by a case-insensitive substring of a name or email, for example to look up a colleague's address before vibehost app grants add-email.

vibehost workspace ls
vibehost workspace use <slug>
vibehost workspace info
vibehost workspace members
vibehost workspace role <email> <role>

Workspace verified domains (auto-join)

For company workspaces, add a verified domain so anyone signing up with a matching email joins automatically.

vibehost workspace domain add prints a DNS TXT record. Add it at your registrar, then run vibehost workspace domain verify <id>:

vibehost workspace domain ls
vibehost workspace domain add yourcompany.com --tier verified
vibehost workspace domain verify <id>

There are two tiers:

  • verified domains are proven with a DNS TXT record. Matching users join automatically with the member role.
  • member domains need no DNS record but must match an existing member's domain. Matching users see a prompt to join instead of joining automatically.

Audit log

Every mutation on a workspace, team, or app (grants, deletes, password changes, custom-domain attach, role updates, self-grants) writes to a workspace-scoped audit log. Workspace owners and admins can read it.

Plain vibehost audit returns the latest 100 rows. --since 7d --json suits analytics or an external SIEM, and --actor filters by actor.

vibehost audit
vibehost audit --since 7d --json
vibehost audit --actor teammate@x.com

Each row carries actor, action, targetType, targetId, before, after and at. We never delete audit rows, so they last as long as the workspace.

URL scope

Every team-scoped and workspace-scoped API route lives under explicit URL params:

/api/v1/workspaces/:workspaceId/...
/api/v1/workspaces/:workspaceId/teams/:teamId/...

The CLI derives :workspaceId / :teamId from ~/.config/vibehost/config.json and refuses to call the API if they're missing, since that's how it knows which workspace to target.

A PAT is bound to one workspace, and optionally one team, when it is issued. Hitting another workspace's URL with the wrong PAT returns 403 TOKEN_WORKSPACE_MISMATCH (or 403 TOKEN_TEAM_MISMATCH). These dedicated codes are an intentional exception to the single "access denied" message used elsewhere. The token holder already knows their token's bound scope, so the code carries no information an attacker without the token could obtain.

Common gotchas

  • App names are unique per workspace. The workspace owns its apps. No team owns one, not even as a label: the old per-app "primary team" tag has been removed, so a team's grants are its only link to an app.
  • Team slugs appear in URLs. Pick a short DNS-safe slug. You can rename it with vibehost team rename-slug <new-slug>, and by design the slug doesn't change when the team's display name does.
  • PATs are bound to one workspace. A PAT created in workspace A can't call workspace B's API even if you also belong to B, so create a separate PAT per workspace (see TOKEN_WORKSPACE_MISMATCH).
  • A workspace admin isn't automatically a team member. Admins see all apps, but actions that check team-based grants still require the admin to be on the team. This rarely matters, because workspace admins are usually members of every relevant team.
  • Last owner can't be removed. Trying to demote or delete the last owner of a workspace or team returns 409 LAST_OWNER. Promote someone else first.
  • vibehost workspace delete refuses if apps still exist or if other members are present. Delete (or transfer) apps first, remove members, then delete the workspace.

On this page