Workspaces & teams
Workspace = billing/admin boundary + owns apps. Team = member sub-group inside the workspace. Grants link teams to apps.
The model
Workspace ─── billing + admin boundary, owns apps
├── plan (Free / Pro / Enterprise)
├── 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 thing this diagram does not show is "apps belonging to a team" — because they don't. Apps belong to the workspace. A team's relationship to an app is granted, not owned. 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 + 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 a 1–3 person account, one team is usually enough. Add more when:
- You want grant isolation: members of Team A shouldn't pick up app grants from Team B.
- You're modeling org structure: each team mirrors a real-world group (squad, department, contractor).
- You want to share many apps with one grant: "everyone on the Web team can deploy to all marketing apps" is one grant per app on the team, not N grants per email.
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 appear in every generated app URL (<app>-<slug>.vibehost.space), so pick a short DNS-safe one.
Team member roles
| Role | What it unlocks |
|---|---|
member | Be in the team; receive whatever app grants the team has |
manager | Manage team members + 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
Invites work at two layers — workspace and team — and the right one depends on how broad you want the access to be.
Invite to the current workspace (workspace owner/admin only). Use this 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/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 auto-join — 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.
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 auto-joins:
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>Two tiers:
verified— DNS TXT proves the workspace owns the domain. Matching users auto-join at thememberrole.member— no DNS, must match an existing member's domain. Matching users see a prompt to join, no auto-join.
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 + admins can read it:
Plain vibehost audit returns the latest 100 rows; --since 7d --json is handy for analytics / external SIEM; --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, at. We never delete audit rows; retention is workspace-lifetime.
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 — that's how it knows which workspace to target.
For PAT callers: PATs are bound to a specific workspace (and optionally a specific team) at issuance time. 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 otherwise-unified "access denied" message: 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
- Apps are unique per workspace — apps are owned by the workspace; the per-app "primary team" tag is just optional metadata.
- Team slugs appear in URLs — picking a short DNS-safe slug matters; rename with
vibehost team rename-slug <new-slug>(the slug stays stable when the team's display name changes, by design). - 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. Create separate PATs per workspace. (See
TOKEN_WORKSPACE_MISMATCH.) - A workspace admin doesn't auto-pick up team membership — admins see all apps, but actions that check team-based grants still require the admin to be a team member. (Usually a non-issue because workspace admins are also team members of every relevant team.)
- Last owner can't be removed. Trying to demote / 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.
Chrome Extension — One-Click Deploy from ChatGPT, Claude, Gemini, and v0
Deploy from Gemini, AI Studio, ChatGPT, Claude Artifacts, and v0.dev with one click — or snapshot any webpage as a static deploy.
Grants and visibility
How VibeHost decides who can see and act on an app. Password is the only true AND-gate; share links bypass visibility/grant but never password.