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:

  1. Create the new key, label it <old-label>-v2.
  2. Roll your config to use the new key.
  3. Verify the old key's "last used at" stops advancing in the dashboard.
  4. Revoke the old key.

Scope of one key

ActionAllowed?
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. .gitignore your .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 echo it.

What gets logged?

Every authenticated request records:

  • key_prefix (the vs_<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_id is 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.