Token Vault
Connect Agents

Automate with API Keys

Run tvault from CI and scripts with a least-privilege tvkey_ API key.

Use an API key when a pipeline or script needs more than reading credentials, for example creating tokens or agents. Give it only the scopes the job needs.

1. Create the key

Run this as yourself, once:

tvault keys create --name ci --scopes tokens:create-read,agents:create --expires 90d
# tvkey_...   <- printed once, never retrievable

2. Store it as a CI secret

gh secret set TVAULT_KEY --body "tvkey_..."

3. Use it in GitHub Actions

.github/workflows/provision.yml
jobs:
  provision:
    runs-on: ubuntu-latest
    steps:
      - name: Install tvault
        run: curl -fsSL https://raw.githubusercontent.com/c-lgrant/tvault/main/install.sh | bash

      - name: Log in
        env:
          TVAULT_KEY: ${{ secrets.TVAULT_KEY }}
        run: printf %s "$TVAULT_KEY" | tvault login --key-stdin --as ci

      - name: Show identity
        run: tvault whoami

      - name: Create a token
        run: tvault tokens create --service deploy-svc --value "${{ secrets.DEPLOY_VALUE }}"

The value goes from the runner straight to your webhook on a signed store ticket. Token Vault never sees it.

4. Mint a scoped agent for a job

With agents:create, the key can create agents, each limited to a subset of its own scopes:

tvault agents create --name job-agent --kind scoped --scopes tokens:create

Rotate and revoke

tvault keys rotate <id>    # then update the TVAULT_KEY secret
tvault keys revoke <id>    # kills this key only; what it created keeps working

tvault keys revoke <id> --force (aliases --yes, -y) skips the confirmation prompt in scripts.

A key can rotate itself with no scope. A scheduled CI job can rotate its own key and store the new one (the CLI switches its context to the new key and prints it once on stdout):

printf %s "$TVAULT_KEY" | tvault login --key-stdin --as ci
NEW_KEY=$(tvault keys rotate --self)
# Store $NEW_KEY in your secret manager as TVAULT_KEY, then discard it. Never log it.

Rotating another key is always a human action; a key can only rotate itself (--self). Revoking another key needs keys:revoke, and only a human can give a key that scope. Revoking a key does not revoke what it created: use "Created by" to find those and revoke them too.

Failure handling

A failing step exits with a distinct code: 8 scope denied, 9 human only, 10 expired, 11 suspended, 12 invalid key. See exit codes.

On this page