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 / adminThe 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
| Role | What it unlocks |
|---|---|
member | Be in the workspace; see what app-level grants give you |
admin | Create teams, manage workspace settings, see all apps |
owner | Plus 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 infoTeam 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
| Role | What it unlocks |
|---|---|
member | Be in the team; receive whatever app grants the team has |
manager | Manage team members and team settings |
owner | Plus 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 memberInvite 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 memberIf 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-siteNow 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-siteWorkspace-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:
verifieddomains are proven with a DNS TXT record. Matching users join automatically with thememberrole.memberdomains 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.comEach 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
ownerof a workspace or team returns409 LAST_OWNER. Promote someone else first. vibehost workspace deleterefuses if apps still exist or if other members are present. Delete (or transfer) apps first, remove members, then delete the workspace.
Referrals
Invite someone with your referral link: they add a card and start Business free for a month, and once their first payment goes through you get a free month to claim.
Does a reviewer need an account to open a private preview?
On most platforms, yes, because a private preview works by making the viewer sign in. Compared across Vercel, Netlify, Cloudflare Pages and GitHub Pages, quoted from each vendor's docs, with what VibeHost does instead.