Personal access tokens
Long-lived bearer tokens for CI and external integrations. Scoped per resource group, scoped per workspace.
PATs are how non-browser, non-OAuth callers authenticate against the VibeHost API. CI pipelines, custom scripts, integrations that can't run an OAuth dance — they all use PATs.
PATs are created and managed from the dashboard only. The PAT management API itself is browser-session-only, so a PAT cannot mint more PATs — limiting blast radius if one leaks.
Create a PAT
Open vibehost.com/settings/access-tokens → Create token. The dashboard asks for:
- Name — identifies the token in audit logs and the token list.
- Scopes — pick the minimum (see below). Leave empty for full-access (equivalent to the dashboard session).
- Resources — optionally restrict to specific app IDs.
- Expiry — 30, 60, 90, 365 days, or never.
The plaintext token (vh_pat_…) is shown exactly once on creation. The server only stores sha256(plaintext); there is no recovery. Copy it to your secrets manager immediately.
Scopes
Scope names follow <resource>:<verb>. Pick the minimum. The full list:
| Scope | What it unlocks |
|---|---|
apps:read | Read app metadata, list deployments, read logs |
apps:write | Create / rename / delete apps |
apps:deploy | Upload deploy artifacts, promote, rollback (the most common CI scope) |
domains:read | List custom domains |
domains:write | Add + verify + remove custom domains |
env:read | List environment variables |
env:write | Set + remove environment variables |
members:read | List team + workspace members and per-app grants |
members:write | Invite + remove members, add + remove per-app grants |
billing:read | Read current plan + usage |
workspace:read | Read workspace settings |
workspace:write | Update workspace settings |
Notes:
- No
billing:write— billing mutations (subscribe, change plan, cancel) go through Stripe-hosted checkout, which re-authenticates against Stripe directly. Long-lived API tokens shouldn't bypass that. apps:deployis a distinct scope fromapps:writebecause deploying is the most common CI action and is materially different from create/rename/delete.- PATs created with empty scopes are full-access — equivalent to the dashboard session. Restrict by picking specific scopes.
A PAT for "GitHub Actions that deploys my-app to production" typically needs only apps:deploy + apps:read, restricted to that specific app. Less surface area = smaller blast radius if leaked.
Resources
The "resources" field restricts a PAT to a specific set of resource IDs:
{
"apps": ["app_abc123", "app_def456"]
}A token with that resources field can only act on those two apps. Calls to other apps return 403 TOKEN_RESOURCE_MISMATCH.
v1 only enforces the apps key. domains and env keys are accepted in the schema for forward-compat but don't gate yet.
Workspace + team binding
Every PAT is bound to one workspace at issue time — the workspace you were in when you clicked Create. Calls to URLs under a different workspace return 403 TOKEN_WORKSPACE_MISMATCH.
Similarly, a PAT can optionally be team-bound. Calls to URLs under a different team return 403 TOKEN_TEAM_MISMATCH.
(These are the documented exceptions to the unified-error-message rule — the token holder already knows their token's bound scope, so the 403 code reveals nothing an attacker without the token could learn.)
Use a PAT
curl -H "Authorization: Bearer vh_pat_..." \
https://api.vibehost.com/api/v1/workspaces/<id>/appsOr with the CLI:
export VIBEHOST_TOKEN=vh_pat_...
vibehost app listThe CLI reads VIBEHOST_TOKEN if set; otherwise it uses the device-flow token in ~/.config/vibehost/config.json.
CI examples
.github/workflows/deploy.yml — deploys ./dist to production on every push to main. Smoke-checks the URL afterwards.
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with: { node-version: 24 }
- run: npm ci
- run: npm run build
- name: Install VibeHost CLI
run: curl -fsSL https://vibehost.com/install.sh | sh
- name: Deploy
env:
VIBEHOST_TOKEN: ${{ secrets.VIBEHOST_TOKEN }}
run: |
~/.vibehost/bin/vibehost deploy ./dist \
--app my-site \
--json | tee deploy.json
URL=$(jq -r '.data.url' deploy.json)
echo "Deployed: $URL"
# Smoke check — fail the job if homepage isn't 200
curl -fsS -o /dev/null -w "%{http_code}\n" "$URL"Store the PAT as a GitHub repository / environment secret named VIBEHOST_TOKEN. Issue it with scopes apps:deploy + apps:read, restricted to app_my_site_id.
For PR previews, swap production for the channel:
run: |
~/.vibehost/bin/vibehost deploy ./dist \
--app my-site \
--channel pr-${{ github.event.pull_request.number }} \
--json.gitlab-ci.yml:
deploy:
image: node:22
script:
- npm ci && npm run build
- curl -fsSL https://vibehost.com/install.sh | sh
- export PATH="$HOME/.vibehost/bin:$PATH"
- vibehost deploy ./dist --app my-site --json | tee deploy.json
- URL=$(jq -r '.data.url' deploy.json)
- curl -fsS -o /dev/null -w "%{http_code}\n" "$URL"
variables:
VIBEHOST_TOKEN: $VIBEHOST_TOKEN
only:
- mainSet VIBEHOST_TOKEN as a masked + protected CI/CD variable.
.circleci/config.yml:
version: 2.1
jobs:
deploy:
docker:
- image: cimg/node:22.11
steps:
- checkout
- run: npm ci && npm run build
- run: curl -fsSL https://vibehost.com/install.sh | sh
- run:
name: Deploy
command: |
export PATH="$HOME/.vibehost/bin:$PATH"
vibehost deploy ./dist --app my-site --json
workflows:
main:
jobs:
- deploy:
context: vibehost-prod
filters:
branches:
only: mainAdd the PAT as a context env var (vibehost-prod context → VIBEHOST_TOKEN).
Minimal "deploy from a cron job on a VM":
#!/usr/bin/env bash
set -euo pipefail
export VIBEHOST_TOKEN="$(cat /run/secrets/vibehost_pat)"
cd /srv/my-site
git pull --ff-only
npm ci && npm run build
vibehost deploy ./dist --app my-site --json > /var/log/vibehost-deploy.json
URL=$(jq -r '.data.url' /var/log/vibehost-deploy.json)
echo "$(date -u +%FT%TZ) deployed: $URL" >> /var/log/vibehost-deploy.log
# Smoke check
curl -fsS -o /dev/null "$URL"systemd timer file for daily 03:00 deploys:
[Unit]
Description=Daily VibeHost deploy
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetScope matrix — which PAT for which CI job
| Job | Minimum scopes | Resource restriction |
|---|---|---|
| Deploy + smoke (most CI) | apps:deploy, apps:read | Specific app IDs |
| Per-PR preview channel | apps:deploy, apps:read | Specific app IDs |
| Read-only audit / analytics | apps:read, members:read, billing:read | Workspace-wide |
| Custom domain provisioning automation | domains:read, domains:write, apps:read | Specific app IDs |
| Env-var rotation from secrets manager | env:read, env:write, apps:read | Specific app IDs |
| Member onboarding bot | members:write, members:read | Workspace-wide (no resource restriction — needs to invite to many apps) |
The principle: smallest possible scope, restricted to specific app IDs whenever possible. Re-issue a new PAT rather than widen an existing one.
Rotation
PATs don't auto-rotate. Recommended:
- Create a new PAT with the same scopes + resources.
- Roll your secrets manager / CI to the new PAT.
- Revoke the old PAT after one deploy cycle confirms the new one works.
Revoking is dashboard-only too — open vibehost.com/settings/access-tokens, click Revoke on the row. Revoked PATs immediately stop authenticating, but the row survives for audit (with deletedAt set).
Audit
Every PAT-authenticated request lands in the workspace audit log with the PAT's id and name — owner / admin only. Read it with vibehost audit or open the Audit tab on your workspace in the dashboard.
If you suspect a leak, revoke immediately, then review the audit log for unexpected actions.
See also
- MCP server — OAuth alternative for interactive agents
- CLI reference — every command a PAT can drive. To call the REST API directly (
Authorization: Bearer vh_pat_…againsthttps://api.vibehost.com/api/v1), each CLI command maps to the underlying endpoint.
MCP Server — Connect Any Coding Agent or Chat Client
Wire any MCP-compatible client to VibeHost. The MCP server is the path for coding agents (Claude Code, Codex, Cursor, Antigravity, Grok Build) and chat clients (ChatGPT, Claude Desktop) that don't have a native CLI tool channel.
Deploy from Claude Desktop — Connect VibeHost in 60 Seconds
Add VibeHost as a custom connector in Claude Desktop. Your Claude artifacts get a permanent, shareable URL — no terminal, no code, no copy-paste.