Skip to content

Personal Access Tokens

Personal access tokens (PATs) let scripts, CI/CD pipelines, and integrations authenticate with WarmHub without interactive login.

PropertyDetail
FormatSigned JWT
Default expiry30 days
Maximum expiry1 year (set via --expires)
ManagementCLI (wh token) or SDK (client.token.*)
Management authInteractive session (manages all your tokens) or a PAT (manages only tokens in its own descendant subtree)
Create → Use → [Expire] → Revoke
  1. Create — Generate a named token via wh token create. The token value is displayed once — save it immediately.
  2. Use — Set the WH_TOKEN environment variable or pass the token in the Authorization: Bearer header. WarmHub validates the token on every request.
  3. Expire — Tokens expire after 30 days by default. Use --expires to set a custom duration (max 1 year). Expired tokens are rejected immediately.
  4. Revoke — Revoke a token by name via wh token revoke. Takes effect immediately.

Expired and revoked tokens are preserved for audit visibility.

Scopes limit what a token can do. They are optional — a token created without scopes has the full permissions of its owning user.

Each scope entry binds a resource to a set of permissions:

TierResource formatExampleMeaning
Repo-scopedorg/repomyorg/myrepoSpecific repo only
Org-levelorgmyorgAll repos in org, capped by role
Global wildcard(omitted)All resources, capped by role

More specific entries take precedence. Scopes can only narrow access, never escalate beyond the user’s role.

Each --scope entry lists one or more permissions: repo:read, repo:write, repo:configure, repo:admin, org:read, org:configure, or org:admin. Scopes are independent — repo:write does not include repo:read — so request the specific permissions your token needs. For what each permission grants and the minimum scope per task, see the access reference.

Instead of listing individual permissions, a scope entry can name a role with role:<name>. The role expands to a fixed set of permissions when the token is created:

RoleRepo permissionsOrg-level entry also grants
role:ownerrepo:read, repo:write, repo:configure, repo:adminorg:read, org:configure, org:admin
role:adminrepo:read, repo:write, repo:configure, repo:adminorg:read, org:configure
role:editorrepo:read, repo:writeorg:read
role:viewerrepo:readorg:read

A role: entry requires a concrete repo or org resource — it is rejected on the global wildcard. (Omit --scope entirely for full user access.) On a repo-scoped entry, role:owner and role:admin grant the same permissions; they differ only on an org-level entry. Like any scope, a role can only narrow access, never escalate beyond your own role.

The role is expanded at creation time, so redefining a role later does not change tokens already issued.

You can run wh token commands from an interactive session — which manages all your tokens — or with a PAT, which manages only the tokens in its own descendant subtree (the tokens it created, and theirs). Component (action) tokens cannot manage tokens.

The --scope flag accepts three forms:

FormMeaningExample
org/repo=permsRepo-scoped--scope myorg/myrepo=repo:read,repo:write
org=permsOrg-level (all repos in org)--scope myorg=repo:read
permsGlobal wildcard (all resources)--scope repo:read,repo:write

Separate multiple permissions with commas. Repeat --scope for multiple entries. In the repo-scoped and org-level forms, the permission list can be a role:<name> shorthand instead of individual permissions.

Terminal window
# Full user access, default 30-day expiry
wh token create --name ci-bot
# Repo-scoped token
wh token create --name ci-bot --scope myorg/myrepo=repo:read,repo:write
# Role shorthand — same as myorg/myrepo=repo:read,repo:write
wh token create --name ci-bot --scope myorg/myrepo=role:editor
# Org-level wildcard (all repos in org, capped by role)
wh token create --name org-reader --scope myorg=repo:read
# Global wildcard with description and custom expiry
wh token create -n deploy --scope repo:write -d "Deploy pipeline" --expires 90d
# Multiple scopes — repeat --scope for each entry
wh token create --name ci-bot \
--scope myorg/private-repo=repo:read,repo:write \
--scope myorg=repo:read \
--expires 90d

For advanced use cases, the --scopes-json flag accepts a JSON array of scope entries with optional allowedMatches patterns that restrict which thing names the token can access. A thing name is the WarmHub object name portion of a wref, such as Signal/temp-1 or Config/settings.

--scopes-json is mutually exclusive with --scope — you cannot use both in the same command.

Each entry in the JSON array has:

FieldTypeRequiredDescription
resourcestringNoResource name: "org/repo", "org", or omitted (global wildcard)
permissionsstring[]YesPermissions for this resource
allowedMatchesstring[]NoGlob patterns restricting which thing names this entry can access. Repo-scoped entries only ("org/repo"). Ignored on org-level and global entries.

allowedMatches behavior:

  • Repo-scoped entries only. allowedMatches on org-level or global wildcard entries is stripped with a warning — name restrictions require a specific repo target.
  • Patterns use glob syntax, for example Signal/* or Config/**.
  • When present, the token can only read or write thing names that match at least one pattern.
  • Omitting allowedMatches means no name restriction for that entry.
  • An empty array ([]) is valid and means deny-all for that entry.

With --scopes-json, you can assign different allowedMatches to different permissions on the same resource by using multiple entries. Each (resource, permission) pair must be unique — duplicate pairs are rejected with a validation error.

Terminal window
# Read Signal/* and Config/*, but only write Signal/*
wh token create --name scoped-bot --scopes-json '[
{"resource":"myorg/myrepo","permissions":["repo:read"],"allowedMatches":["Signal/*","Config/*"]},
{"resource":"myorg/myrepo","permissions":["repo:write"],"allowedMatches":["Signal/*"]}
]'
# Simple name restriction — read-only for Signal/*
wh token create --name reader --scopes-json '[
{"resource":"myorg/myrepo","permissions":["repo:read"],"allowedMatches":["Signal/*"]}
]'
# No allowedMatches — same as --scope shorthand
wh token create --name full --scopes-json '[
{"resource":"myorg/myrepo","permissions":["repo:read","repo:write"]}
]'

The token is printed once. Save it to a secret store or environment variable immediately.

A token can carry a default committer identity with --committer-identity. Writes made with the token are attributed to that identity unless a per-command --committer overrides it:

Terminal window
wh token create --name ci-bot --committer-identity wh:warmhub/users/Identity/<id>

The value is an identity wref. Without it, writes are attributed to your own user identity.

Terminal window
wh token list
wh token list --json

Shows all your tokens with their status (active, expired, or revoked) and scopes.

Terminal window
wh token get --name ci-bot

Shows token details: name, description, scopes, status, creation date, and expiry.

Terminal window
wh token revoke --name ci-bot

Revocation is immediate. The token is rejected on the next API request.

Pass the token in the Authorization header:

Terminal window
curl -H "Authorization: Bearer eyJhbGciOi..." \
https://api.warmhub.ai/api/repos/myorg/myrepo/head

Or with the CLI via WH_TOKEN:

Terminal window
export WH_TOKEN=eyJhbGciOi...
wh commit submit --add cave --shape Location \
--data '{"x":3,"y":7}' -m "Add cave"

MCP clients like Claude and Cursor normally authenticate via OAuth automatically. However, PATs are useful when:

  • Your organization restricts custom MCP connectors
  • You’re on WSL2, where the OAuth callback can’t reach the browser
  • You want to authenticate a dev instance without configuring OAuth

Use mcp-remote as a stdio bridge with your PAT:

{
"mcpServers": {
"warmhub": {
"command": "npx",
"args": [
"-y", "mcp-remote@latest",
"https://api.warmhub.ai/mcp",
"--header", "Authorization:${WH_TOKEN}",
"--transport", "http-only"
],
"env": {
"WH_TOKEN": "Bearer eyJhbGciOi..."
}
}
}
}

This works in both Claude Desktop (claude_desktop_config.json) and Claude Code (.mcp.json). The --transport http-only flag ensures HTTP Streamable transport.

See the Quickstart for standard OAuth-based setup.

  • No escalation. A PAT can manage only the tokens in its own descendant subtree — the tokens it created, and theirs. It cannot list, read, or revoke tokens outside that subtree, so a leaked PAT can’t reach the rest of your tokens. An interactive session manages all your tokens.
  • Scope enforcement. Each API endpoint checks the token’s scopes. A repo:read token cannot push commits.
  • Immediate revocation. Revoking a token takes effect on the next request — there is no grace period.
  • Name restrictions. Token names must be alphanumeric, hyphens, and underscores only (URL-safe).
  • No token rotation. To rotate, revoke the old token and create a new one.
  • No web UI. Tokens are managed via the CLI or SDK only.
  • Subtree-scoped under a PAT. wh token and client.token.* accept an interactive session (all your tokens) or a PAT (only its own descendant subtree); component (action) tokens are rejected. To manage tokens outside a PAT’s subtree, sign in with wh auth login (CLI) or use an interactive session before calling the SDK methods.