Skip to content

Getting Access

WarmHub handles sign-up and sign-in through a hosted login flow — there are no credentials to configure locally. The first time you sign in, your account is created automatically.

There is no separate registration step. The first time you log in (via the CLI or web app), WarmHub creates your account. Supported sign-in methods include:

  • Email and password
  • Google
  • GitHub
  • SSO (SAML/OIDC) — if configured for your organization

Your identity works across the CLI, web app, SDK, and HTTP API.

Org invites are sent to a specific email address. Sign in with an account that uses that same address — otherwise you land in a different account than the one that was invited.

  • Signing in with GitHub uses the primary email on your GitHub account. If that primary email doesn’t match the invited address, GitHub sign-in authenticates as the wrong identity, and GitHub offers no way to pick a different email for sign-in. When the invited address isn’t your GitHub primary, sign in with email and password instead.
  • Each account is tied to a single email today. A personal email and a work email are separate accounts — there’s no way to consolidate them. Keep them distinct and sign in with the email that matches the org you’re joining.
  • Your sign-in provider must have verified your email. WarmHub only lets you accept an invited org membership once the provider you sign in with (GitHub, your SSO, or email-and-password) has confirmed that email address. If it hasn’t, the sign-in is rejected and asks you to verify first — verify your email with that provider, then sign in again.

First-time sign-in also provisions a personal org for you automatically — a namespace you own where you can create repos immediately without waiting to be added to a team org. WarmHub picks its slug in this order:

  1. If you signed in with GitHub and already own an org named after your GitHub login, that org becomes your personal org.
  2. Otherwise WarmHub uses your GitHub login, lowercased, if that name is free.
  3. If it is taken or reserved, WarmHub adds a numeric suffix: alice-2, alice-3, and so on.
  4. Without a usable GitHub login, or when those are all taken, the slug is user- followed by characters from your account ID, such as user-1a2b3c4d5e6f.

Personal orgs are regular orgs in every other respect — you own them, you can create repos and invite members, and they appear in wh org list. One constraint applies:

  • Reserved names. Certain slugs are reserved and rejected on create and rename (case-insensitive). This applies to all orgs, not just personal ones.

Personal orgs — including those linked to a GitHub login — can be renamed with wh org rename like any other org.

When you sign in, WarmHub publishes a public identity for you — the identity your commits are attributed to, and the one other records point at when they name you as an author. It shows a display name derived from your account and never includes your email address.

This identity is globally readable and can be referenced from anywhere in WarmHub. It is published the first time you sign in and refreshed periodically; publishing is best-effort, so a delay never blocks sign-in or any write.

Terminal window
wh auth login

This starts a device authorization flow:

  1. The CLI displays a verification URL and one-time code.
  2. The CLI attempts to open your browser to the login page. If the browser does not open, visit the printed verification URL manually.
  3. You authenticate and confirm the code.
  4. The CLI receives a token and saves it to ~/.warmhub/auth.json.

Tokens auto-refresh in the background for active device-flow sessions. You should only need to log in once per machine.

If your refresh token expires, or your session is revoked server-side, the CLI surfaces a Session expired error and prompts you to run wh auth login again. Revocation takes effect at your next refresh — your current token may keep working until it expires.

If a refresh attempt fails due to a transient provider or network error, the CLI retries within a bounded window and then surfaces a Token refresh failed (transient). error. This does not mean your session has been revoked — it means the refresh could not complete at that moment. Check your network connectivity and try your command again. If the problem persists, run wh auth login to start a fresh session.

Non-TTY and machine-readable challenge output

Section titled “Non-TTY and machine-readable challenge output”

When wh auth login runs in a context where its output isn’t connected to an interactive terminal — for example, a remote session, a wrapper script, or an agent process (technically: when stdout is not a TTY) — the CLI writes the device-flow challenge to stdout before it begins polling. This gives wrappers and agents a reliable way to capture the verification URL and code without scraping interactive prompts.

Pretty mode (non-TTY stdout): the verification URL and user code are printed to stdout as human-readable text, mirroring what an interactive session would display in the terminal.

JSON mode: pass --format json to receive a structured object:

{
"deviceChallenge": {
"verificationUri": "https://signin.warmhub.ai/device?user_code=ABCD-1234",
"userCode": "ABCD-1234",
"expiresInSeconds": 300
}
}

verificationUri is the complete, prefilled link — it already embeds the user_code as a query parameter so the user can open it directly without typing the code. userCode repeats the code separately so your UI can display it alongside the link (for example, as a fallback if the user prefers to navigate to the activation page manually).

The command remains interactive after emitting this object — it continues polling until the user completes the flow or the challenge expires. A wrapper can read the JSON from stdout, surface the verificationUri and userCode to the end user through its own UI, and then wait for the command to exit.

expiresInSeconds is the lifetime of the challenge from the moment it is issued. If the user does not complete the flow within that window, the challenge expires and the command exits with an error; run wh auth login again to get a fresh challenge.

Non-interactive environments (CI, containers, headless servers):

For automation, use a personal access token via the WH_TOKEN environment variable instead of interactive login. See the wh token commands for creating tokens.

If you need to pipe a raw JWT (e.g., from another auth system):

Terminal window
echo "$JWT" | wh auth login --with-token

Piped tokens cannot auto-refresh — you’ll need to provide a new one when it expires. PATs are the preferred approach for most non-interactive use cases.

You can manage multiple credential sets with the --profile (-P) flag. Each profile stores its own tokens, the API endpoint used at login time, and any client flags set at login.

Terminal window
# Log in with a named profile (e.g., for a separate account)
wh auth login --profile work
wh auth login --profile personal
# Use a profile for any command
wh thing list --repo myorg/myrepo --profile work
# Or set via environment variable
export WH_PROFILE=work
wh thing list --repo myorg/myrepo

The CLI resolves the active profile in this order:

  1. --profile flag — explicit per-command override; always wins.
  2. WH_PROFILE environment variable — process-wide override, useful in scripts.
  3. A profile stored in the current directory’s .wh file — a repo can pin a saved profile so that commands run inside that directory automatically use the right credentials. See Default Repo (.wh file).
  4. The default profile — used when none of the above apply.

Each profile remembers the API URL it was logged in with — that stored URL applies when the profile is active, unless you override it with --api-url. The WARMHUB_API_URL environment variable is the fallback when no profile is loaded (for example, on a fresh machine before login).

Profiles can store optional login-time flags alongside the token and API endpoint. When a flag is persisted on a profile, it is restored automatically on every command that uses that profile — you don’t need to re-supply it each time. Client flags are feature-specific tokens that enable or configure particular CLI behaviors. Flag names are not publicly documented — they are feature identifiers communicated to you directly when a flag applies to your situation.

The --flag option accepts a repeatable string token (matching [a-z0-9-]+). Pass it once per flag you want to store:

Terminal window
# Persist a client flag on the "work" profile at login time
# (use whatever flag token applies to your situation)
wh auth login --profile work --flag <flag-name>

Subsequent commands that use the work profile pick up the stored flags without any extra arguments.

To update which flags are stored on a profile, log in again and supply the full set of --flag values you want going forward. Note that omitting --flag on a subsequent login does not clear previously stored flags — the profile retains its existing flags.

To remove all stored flags from a profile, log out of that profile and log in again without any --flag arguments. Logging out removes the entire profile record from ~/.warmhub/auth.json — including the stored tokens, API URL, and any saved flags — so the subsequent login starts clean:

Terminal window
wh auth logout --profile work
wh auth login --profile work
Terminal window
wh auth status

Shows auth state for every profile and the WH_TOKEN environment variable. Here’s what each entry includes:

  • Saved profiles (including default) — auth method (device flow or piped token), token expiry, API endpoint, and any stored client flags (shown as a Client flags: line when flags are present on the profile).
  • Active profile — identity is resolved and shown.
  • Non-active saved profiles — listed without identity information.
  • WH_TOKEN entry — auth method and token expiry; no API endpoint and no identity are shown.

Use --profile <name> to check a specific profile.

Terminal window
wh auth logout

Removes the entire default profile record from ~/.warmhub/auth.json — including its tokens, stored API URL, and any saved settings such as client flags. Use --profile <name> to log out a specific profile. Other profiles are not affected. If WH_TOKEN is set as an environment variable, unset it separately.

For scripts and automation, set WH_TOKEN to a personal access token or JWT:

Terminal window
export WH_TOKEN=eyJhbGciOi...
wh thing list # authenticates automatically

WH_TOKEN takes precedence over saved credentials from wh auth login. The CLI checks for it on every command.

All API requests authenticate via Bearer tokens in the Authorization header. WarmHub supports three token types:

TypeObtained viaUse caseExpires
Session JWTwh auth login or web app sign-inInteractive useShort-lived (auto-refreshes)
Personal access tokenwh token create or SDK client.token.createScripts, CI/CD, integrations30 days default, 1 year max
MCP OAuth tokenAutomatic (OAuth flow in MCP client)MCP clients (Claude, Cursor, etc.)Session-based

All token types share a unified validation model — they go through the same server-side JWT verification and permission checking.

See Personal Access Tokens for creating and managing PATs.

Repositories are either public or private. Visibility is set at creation time and can be changed by org administrators.

PublicPrivate
Read (queries, HEAD, history)Anyone — no auth requiredAuthenticated, repo:read permission
Write (commits, shape mutations)Authenticated, repo:write permissionAuthenticated, repo:write permission
Manage (subscriptions, credentials)Authenticated, repo:configure permissionAuthenticated, repo:configure permission
Anonymous read requestsReturn data normallyReturn 404

All writes require authentication regardless of visibility. Private reads require repo:read permission on the target repo — sign-in alone is not enough; you need to be a member of the repo’s org. PATs can narrow what a member is allowed to access, but they don’t grant access on their own. Cross-repo reads to a private target apply the same check, so a canonical wh:org/repo/... wref only resolves when you can read the target repo.

The anonymous-access row covers ordinary reads (queries, HEAD, history). Some endpoints require management-level access even on a public repo: action observability (run history, attempts, live feed, notifications) needs repo:configure, so anonymous callers get an opaque 404 rather than data. See Actions API for the full contract.