API keys
Workspace-scoped Bearer tokens for programmatic access.
Each API key is bound to one workspace and grants the bearer the permissions of that workspace's operator role: create sessions, list sessions, save profiles, release sessions. They cannot create members, mint other keys, or read the audit log.
Format
vs_01JC1AMQX4N3PWV9MR2BCKDH7E.x4P2NRZ5tD7BvUe3cFa8KgT1HoMnQXjW
└─┘ └────── ULID api_key_id ──────┘ └────── 32-char secret ──────┘- The
vs_prefix lets log scanners detect a leaked key. - The ULID identifies the API-key record, not the workspace
directly — the workspace is resolved server-side from the key record.
- A literal dot (
.) separates the prefix-and-ID portion from the
secret. We hash the secret on receipt and never store it. Lose it and you mint a new key.
The dashboard "masked prefix" column shows everything up to the dot (vs_01JC…) so you can identify which key did what in the audit log without ever needing to reveal the secret.
Create
API keys → Create key in the dashboard, or POST /v1/dashboard/ws/<slug>/api-keys:
curl -X POST https://api.sessions.ventus.ai/api/v1/dashboard/ws/$WS_SLUG/api-keys \
-H "Authorization: Bearer $DASHBOARD_SESSION_JWT" \
-d '{"label": "ci-job"}'Response includes a single secret field — copy it now, you won't see it again.
Revoke
From the dashboard list view, click Revoke next to the key, or DELETE /v1/dashboard/ws/<slug>/api-keys/<key_id>. Revocation is immediate — the next request authenticated with that key returns 401.
Revoked keys remain visible in the list (greyed out) for 90 days for audit purposes; after that they're hard-deleted.
Rotation
There's no automatic rotation in v0.4. The current best practice:
- Create the new key, label it
<old-label>-v2. - Roll your config to use the new key.
- Verify the old key's "last used at" stops advancing in the dashboard.
- Revoke the old key.
Scope of one key
| Action | Allowed? |
|---|---|
| Create / get / release / keepalive sessions | ✓ |
| List sessions in the key's workspace | ✓ |
| Save / reuse profiles | ✓ |
| List or audit other workspaces | ✗ (403) |
| Create members or invitations | ✗ (403 — dashboard auth required) |
| Mint other API keys | ✗ (403) |
| Read the audit log | ✗ (403 — dashboard auth required) |
Anything in the right-hand "✗" column is a dashboard surface, gated by a Supabase magic-link session, not a Bearer token. That separation is deliberate: a leaked API key cannot escalate to "read everything an owner can read".
Storing the key safely
- Treat it like a database password.
- Never commit to git.
.gitignoreyour.env. - Use your platform's secret manager: AWS Secrets Manager, GCP Secret
Manager, Doppler, 1Password CLI, etc.
- For CI: store as a masked variable; never
echoit.
What gets logged?
Every authenticated request records:
key_prefix(thevs_<ULID>portion left of the dot — no secret).action(e.g.session.create,session.release).target(the resource ID acted on).created_at.actor_user_idis NULL for API-key calls — keys aren't people.
You can filter the audit log by key_prefix to see exactly what one key did.