Skip to content
Solo ProTeamBusinessEnterprise

Authenticating

Storing an API key, pointing the CLI at your own deployment, checking that a key still works, and signing out.

Commands on this page: login · logout · whoami · status

A profile is a credential set stored on your machine: an API key plus the deployment host it belongs to, under a name you choose. The built-in prod profile points at https://api.pulsvault.com/api/v1 and is the default, so a plain pulsvault login needs no flags:

bash
pulsvault login                                        # paste the key at the prompt; input is hidden
pulsvault get acme-web/prod/product/web/db/password

Where there is no terminal to prompt on, pipe the key in — login reads it from stdin:

bash
printf '%s' "$PULSVAULT_KEY" | pulsvault login --profile ci-deploy

:::caution Keep the key off the command line --token pv_live_… works, but the argument is written to your shell history and is readable by every user on the machine for as long as the process runs (ps, /proc/<pid>/cmdline). The CLI says so each time it is used. In a pipeline, set PULSVAULT_TOKEN as a masked variable instead; on a workstation, let login prompt. :::

login verifies the key against the server before storing it, and prints the identity it authenticated as:

text
✓ authenticated to https://api.pulsvault.com/api/v1
  as ci-bot@service-account · space acme
  can: read-prod   (1 of 3 — this key is limited)
Saved credentials for profile "prod" (https://api.pulsvault.com/api/v1) to ~/.pulsvault/config

A key the server rejects is not stored — you see rejected this API key (HTTP 401) — nothing was saved immediately, rather than a puzzling failure on some later command.

When the check cannot be completed the key is stored, and the reason is printed as a warning so "verified" is never assumed: the server has no identity endpoint, the check was rate-limited, the identity response could not be parsed, or you passed --skip-verify yourself:

bash
pulsvault login --profile mycorp --host https://vault.mycorp.com/api/v1 --skip-verify

Use --skip-verify for offline or bootstrap setup, where the deployment is not reachable yet. It stores the key without asking the server whether it works, so the first real command becomes the test.

A profile is a credential set — not an account, and not an environment

It is a local label in ~/.pulsvault/config; the name never reaches the server. Two things it is often mistaken for:

  • Not a PulsVault account. An API key authenticates as a service account (a machine identity). There is no user login in the CLI.
  • Not the {env} in a path. Which environment you read is decided by the path ({project}/{env}/{namespace}/{secret}), never by the profile — --profile prod can address …/dev/… paths and vice versa.

Pointing the CLI at your own deployment#

If you run PulsVault yourself — self-hosted, on-premises, or a dedicated instance — give the CLI your own endpoint by naming a profile and passing the API base once:

bash
pulsvault login --profile mycorp --host https://vault.mycorp.com/api/v1
  • --profile mycorp — the local name for this credential set. Any name works; it is yours.
  • --host — the API base including the /api/v1 suffix. Needed only the first time: it is saved in the profile, so a later plain pulsvault login --profile mycorp refreshes the key and keeps the host, which means the profile cannot be repointed by accident.

Then make it the default, or select it per command:

bash
pulsvault config use mycorp                                  # every later command uses it
pulsvault --profile mycorp get mycorp-app/prod/api/db/password
PULSVAULT_PROFILE=mycorp pulsvault list mycorp-app/prod/api   # e.g. in CI

Several deployments coexist — one profile each, no interference between them:

bash
$ pulsvault config list
* mycorp         https://vault.mycorp.com/api/v1
  prod           https://api.pulsvault.com/api/v1

* = active profile (used when --profile / PULSVAULT_PROFILE is absent)

Keys are per-deployment: a key issued by one PulsVault instance never authenticates against another, and login tells you so immediately instead of storing a key that cannot work.

While testing an endpoint that still has a self-signed certificate, --insecure skips TLS verification and prints a warning on every run. Never use it against a production endpoint.

Selecting a profile#

Once several profiles exist, which one a command uses — and which project its paths are relative to — is its own subject: see Profiles & defaults for pulsvault config, the config file, and the full resolution order. The short version is --profile → PULSVAULT_PROFILE → the config's active profile.

Storing a credential and selecting a profile are separate steps

login --profile mycorp writes that profile but does not repoint the other commands — except for the very first profile stored on a machine, which also becomes the active one. Afterwards use pulsvault config use mycorp, login --set-default, or pass --profile mycorp per command. If a command resolves to a profile with no token, the error names the profiles that do have one.

Because that gap is easy to miss, login says so when it applies:

text
Saved credentials for profile "mycorp" (https://vault.mycorp.com/api/v1)
note: your active profile is still "prod", so commands without --profile keep using it.
      run `pulsvault config use mycorp` to switch, or re-run login with --set-default.

Working with several keys#

One profile holds one key for one deployment. That pairing is not a limitation to work around — a key is issued by a specific PulsVault instance and carries a specific set of permissions, so the two travel together. More keys simply means more profiles.

A realistic setup — a personal key, a narrow CI key, and a customer deployment:

bash
pulsvault login --profile ci-deploy                    # read-only key, production paths
pulsvault login --profile mycorp --host https://vault.mycorp.com/api/v1
bash
$ pulsvault config list
* prod           https://api.pulsvault.com/api/v1
  ci-deploy      https://api.pulsvault.com/api/v1
  mycorp         https://vault.mycorp.com/api/v1

From there, Profiles & defaults covers choosing between them — per command, per shell, or persistently.

Name profiles after the key's job, not the environment

ci-deploy, release-bot, mycorp age well. A profile named after an environment invites the assumption that switching profile switches environment — it does not. The environment is a segment of the path ({project}/{env}/…), so one key can address several environments, and two keys can address the same one. Keeping the two ideas separate is what stops a "staging" profile from being trusted to be incapable of touching production.

A bare login re-credentials the profile you are already using. If ci-deploy is active, pulsvault login replaces that key and leaves the others alone — which is what rotating a key looks like. Name a different profile with --profile to add or refresh one of the others.

Two habits worth having when several keys are in play:

  • pulsvault whoami before anything destructive. Its first line is the profile, and the rest is the identity and exactly what that key may do. Cheaper than discovering it from a failed write.
  • pulsvault config remove <name> once a key is revoked. A stored credential that no longer works is a confusing failure waiting to happen; removing it makes the next command's error name the profiles that do have a working key.

Checking a key without reading a secret#

Two commands answer "is this credential still good?", and both exit non-zero when it is not — so a pipeline can gate on them:

bash
pulsvault status      # can I reach the host, and does the key authenticate?
pulsvault whoami      # who am I, what may this key do, when does it expire?

status is the first thing to run when a pipeline breaks. It separates cannot reach the host from token rejected from token expired — three failures that otherwise look identical from inside a build log:

text
$ pulsvault status
profile: prod
host:    https://api.pulsvault.com/api/v1
status:  connected (HTTP 200)

whoami reports what this key may do, which is not always what its machine identity may do — a key can be narrowed to a subset of its service account's policies:

text
$ pulsvault whoami
profile:         prod
host:            https://api.pulsvault.com/api/v1
service account: ci-bot@service-account
space:           acme
type:            CI
permissions:     read-prod   (1 of 3 — this key is limited)
last used:       2026-08-16T09:12:44Z
key:             pv_live_abcd…
key expires:     2026-09-01T00:00:00Z

Add -o json for the full record, including the soonest grant expiry. A permission can lapse while the key itself stays valid, which otherwise surfaces as an unexplained outage.

Two exit-code details worth knowing before you gate a pipeline
  • status -o json always exits 0. Under structured output the command reports the outcome in the payload and returns success, so pulsvault status -o json && ./deploy.sh deploys even when the key was rejected. Gate on the plain pulsvault status, or parse the connected field:
    bash
    pulsvault status -o json | grep -q '"connected": true' || exit 1
  • whoami exits 0 against a server with no identity endpoint. An older deployment returns 404, and the CLI falls back to printing your local profile, host and key prefix with a note on stderr. Exit 0 there means "the key was not checked", not "the key is good". A rejected or expired key still exits non-zero.

When the refusal is the subscription, not the key#

A 403 on the machine plane used to mean one thing: this key's policies did not cover the request. It can now also mean the plan covering the secrets has lapsed — the server enforces the same post-expiry ceremony on /api/v1/secrets/** that it enforces in the console. The two look identical in a build log and want opposite fixes, so the CLI tells them apart and says which one you are looking at.

There are two states, and the difference decides whether you are facing a deadline or an outage.

Read-only window. Reads keep working; only writes are refused:

text
$ pulsvault set web/prod/db/password 'hunter2'
error: Your account is read-only. Subscribe to a plan to make changes, or export your data. (403)

this is the subscription, not this key
  The plan covering these secrets has ended and the account is in its read-only window.
  Reading secrets still works — only writes are refused, so a pipeline that just fetches
  values keeps deploying.
  Renew the plan in the console to restore writes. Nothing here needs changing.

A deployment pipeline that only fetches values is unaffected for the whole window. A pipeline that writes them — a rotation job, an import, a bootstrap step — fails from the first day of it.

Locked. The window has closed and reads stop too:

text
$ pulsvault get web/prod/db/password
error: Your account is locked. Your data is safe — subscribe to a plan to restore access. (403)

this is the subscription, not this key
  The read-only window has closed and the account is locked, so reads are refused too.
  Nothing has been deleted. Subscribe again in the console and access returns at once.
  This key stays valid throughout — do not revoke or re-issue it.
Do not re-issue the key

Both messages exist because the obvious reaction to a 403 is to suspect the credential — revoke it, mint a new one, re-run. That is wasted work here, and it makes the next incident harder to read: the key is intact, its policies are intact, and a fresh one is refused in exactly the same way. Nothing on this machine needs changing. The fix is on the account, in the console.

pulsvault login reflects this too. It verifies the key by default, and against a locked account it cannot read the identity endpoint — so it says the key could not be verified and stores it unchanged rather than reporting it as rejected. You will not have to log in again after renewing.

Signing out#

bash
pulsvault logout                  # clear the active profile's token, keep its host
pulsvault logout --profile ci     # a specific profile
pulsvault logout --all            # every stored token on this machine
The API key is stored as text on the machine

login writes the key to ~/.pulsvault/config with mode 0600 — readable only by your user account — and re-applies that mode on every write, so a config file whose permissions were loosened is repaired the next time you log in rather than trusted as-is. The CLI is a single static binary with no OS-keychain dependency, so it behaves the same on a laptop, in a container, and on a CI runner. The key is a machine credential — it carries only the actions and paths its policies grant, it can never unlock a human vault, and it is revocable at any moment from Machine Identities. Secret values never touch disk.

In CI, set PULSVAULT_TOKEN as a masked variable and skip login entirely — then no credential is written at all. On a shared or retired machine, pulsvault logout removes the stored token.

A profile never silently falls back to production

If a profile has no host — a typo like --profile prd, or a profile you never gave a --host — the command fails with an error instead of resolving to production. Re-running pulsvault login on an existing profile also keeps that profile's host, so it cannot be repointed by accident. Commands that write print their target as → mycorp https://vault.mycorp.com/api/v1 on stderr before they run; -q suppresses it.

Was this page helpful?