Skip to content
Solo ProTeamBusinessEnterprise

Command reference

Every pulsvault command, with its flags, aliases and examples — generated from the CLI's own command registry, so it can never drift from the binary.

This page lists every command the binary has, with its exact flags. It is generated from the CLI's own command registry — the same data that powers pulsvault help — so it cannot fall behind the binary you are running.

It is a reference, not a guide: it answers "what flags does rotate take", not "how should I rotate credentials in CI". For the latter, start at the CLI overview and follow the task pages.

The same information is in your terminal

pulsvault help lists the commands and pulsvault help <command> prints the entry below for one of them. Shell completion is available too — pulsvault completion zsh (or bash/fish).

All commands#

Command Group What it does
bulk-get Secrets Read many paths at once (masked unless --reveal)
cp Secrets Copy a secret value to another path
delete Secrets Soft-delete a secret (restorable for 30 days)
diff Secrets Compare two versions of a secret
export Secrets Print a namespace's secrets as env vars
gen Secrets Generate a strong random secret and store it
get Secrets Read a secret (masked on a terminal by default)
import Secrets Bulk-load a .env or JSON file into a namespace
list Secrets Browse the path tree one level down (add -R for the whole subtree)
mv Secrets Move a secret to another path, atomically, with its full history
patch Secrets Partially update a JSON secret: key=value sets, key- removes
path-diff Secrets Show what changed under a path between two points in time
rollback Secrets Restore an earlier version's value as a new version
rotate Secrets Force a new version (fresh data key, same value)
run Secrets Inject a namespace's secrets as env vars into a command
set Secrets Write/update a secret (creates a new version)
template Secrets Render a template file, resolving {{ secret "path" }} via the API
undelete Secrets Restore a soft-deleted secret (within 30 days)
versions Secrets List a secret's version history, newest first, with the total
envs Structure List environments in your space
namespaces Structure List namespaces in a project+environment you can access
projects Structure List projects this key can address
validate Validation Run the configured credential probe now (exit codes name the outcome)
validation-history Validation Show a secret's validation history
validation-status Validation What is validated, and was it working? An environment, a namespace, or one path
config Config Manage profiles: list | use

| project [] | remove

| path

login Config Verify an API key against the server and store it for a profile
logout Config Remove a profile's stored token (keeps its host)
status Config Check reachability + auth for the current profile
whoami Config Show the authenticated service account
completion Utility Emit a shell-completion script

Secrets#

bulk-get#

Read many paths at once (masked unless --reveal).

needs an API key

bash
pulsvault bulk-get <path>... | -f <file>  [--reveal|-o json]
Flag What it does
--file F, -f F read newline-separated paths from a file

cp#

Copy a secret value to another path.

needs an API key · writes to the vault

bash
pulsvault cp <src-path> <dst-path> [--if-match N]

delete#

Soft-delete a secret (restorable for 30 days).

needs an API key · writes to the vault · aliases: rm

bash
pulsvault delete <path> [--yes]
Flag What it does
--yes, -y skip the confirmation prompt

The secret stops answering reads immediately — CI gets a 404 — but it is not destroyed. It moves to the namespace's recycle bin and pulsvault undelete brings it back, with its version history, for 30 days. After that a nightly job removes it and the values are unrecoverable.

diff#

Compare two versions of a secret.

needs an API key

bash
pulsvault diff <path> [--from N] [--to N] [--reveal]

export#

Print a namespace's secrets as env vars.

needs an API key · aliases: export-env

bash
pulsvault export <project>/<env>/<namespace> [--format dotenv|json|yaml] [--out F] [--skip-missing]
Flag What it does
--format F dotenv (default) | json | yaml
--out F write to F with mode 0600 instead of stdout — a shell redirect creates the file under your umask (0664 on a typical login), which --out avoids
--skip-missing warn and continue when a secret fails to load (default: fail)
--allow-env NAME let a secret set a variable that is refused by default — PATH, LD_PRELOAD, NODE_OPTIONS, … (repeatable, or comma-separated)
bash
pulsvault export acme-web/prod/product/web
pulsvault export acme-web/prod/product/web --out .env     # 0600, not umask

Prefer run where you can: it injects the values into one process and leaves nothing behind. --out is for the case where a file is genuinely required — it writes 0600 even when the destination already existed with looser permissions, and replaces a symlink at that path rather than writing through it.

A secret whose name would become a variable that decides how a program starts — PATH, HOME, SHELL, IFS, ENV, BASH_ENV, NODE_OPTIONS, PYTHONPATH, JAVA_TOOL_OPTIONS and their kin, or anything starting LD_, DYLD_, GIT_ or PULSVAULT_ — fails the command instead of being written. Anyone who can write one secret in the namespace could otherwise run code wherever the file is sourced. --allow-env NAME lets a namespace that is meant to set one through.

If the server cut the namespace's listing at its cap (5000 secrets by default), export refuses instead of writing part of it — a file with missing variables is worse than no file. Export a narrower path.

gen#

Generate a strong random secret and store it.

needs an API key · writes to the vault

bash
pulsvault gen <path> [--length N] [--symbols] [--reveal|--copy]
Flag What it does
--length N length (8..256, default 24)
--symbols include punctuation
--content-type T set the content type
--metadata K=V attach metadata (repeatable)
--reveal / --copy surface the generated value

get#

Read a secret (masked on a terminal by default).

needs an API key

bash
pulsvault get <path> [--version N] [--reveal|--full-text|--copy] [--field K] [-o json]
Flag What it does
--reveal, -r / --full-text print the full plaintext (default masks it on a terminal)
--copy, -c copy the value to the clipboard instead of printing
--field K one key of a JSON value — or, on a folder, one secret inside it
--version N read an OLDER version (not a query — the version is always reported)
-o json print the full secret record (incl. value) as JSON
bash
pulsvault get prod/product/web/db/password            # masked preview
pulsvault get prod/product/web/db/password --reveal    # full value
pulsvault get prod/product/web/db/password | psql …    # piped: raw value
pulsvault get prod/product/web/db/creds --field username --reveal   # JSON key
pulsvault get prod/product/web/postgres --field url --reveal        # folder entry

Every read names the version it returned — v3 (current), or v1 (pinned) when --version chose it. There is no flag for asking: reading a secret tells you. (pinned) deliberately does not claim the version is stale — that is not knowable from one read, and finding out would cost a second one in the audit log.

That line goes to stderr and is skipped when the output is piped or -o json, so stdout stays exactly the value and $(pulsvault get …) is unaffected. To see every key's version at once without reading any value, use pulsvault list.

import#

Bulk-load a .env or JSON file into a namespace.

needs an API key · writes to the vault

bash
pulsvault import <file> --namespace <project>/<env>/<namespace> [--format dotenv|json] [--dry-run]
Flag What it does
--namespace NS, -n NS target namespace ({project}/{env}/{namespace}; the project may be omitted when --project is set)
--format F dotenv | json (default: by file extension)
--dry-run show what would be written without writing
--allow-empty import keys that have no value (refused by default)
bash
pulsvault import ./app.env --namespace dev/product/web --dry-run

A FOO= line is legal dotenv and means an empty value, but is far more often a placeholder nobody filled in. Such keys are listed by name and the import is refused; pass --allow-empty to write them as empty.

Keys are lowercased and _ becomes -, because a secret path must match ^[a-z][a-z0-9_/.-]*: IMPORTED_ONE lands at <namespace>/imported-one. Run --dry-run first if the resulting names matter to you.

list#

Browse the path tree one level down (add -R for the whole subtree).

needs an API key

bash
pulsvault list [<path>]  [-R] [-o json|yaml]

  Any prefix of a canonical path works, so a path copied from the console
  resolves as-is. Directories print with a trailing slash, keys without.

    pulsvault list                              projects you can address
    pulsvault list acme-web                     environments
    pulsvault list acme-web/prod                namespaces
    pulsvault list acme-web/prod/product/web    folders in that namespace
    pulsvault list acme-web/prod/product/web/db the keys db holds
    pulsvault list acme-web/prod -R             every secret in prod, full paths
Flag What it does
-R, --recursive every secret at or below , as full paths
--all deprecated alias for -R (was: walk every namespace in )

A one-level listing shows the TREE (folders and keys); -R shows the SECRETS. A folder holding none appears in the first and not the second, so on a terminal -R names it as (empty) rather than leaving the two listings to disagree. That note goes to stderr and is skipped when the output is piped or -o json, so stdout stays exactly what a script expects.

The server caps a recursive listing (5000 secrets by default). When it cuts one, -R still prints what it got and warns on stderr — even under -o json — that the list is incomplete; list a narrower path to see the rest.

mv#

Move a secret to another path, atomically, with its full history.

needs an API key · writes to the vault · aliases: move, rename

bash
pulsvault mv <src-path> <dst-path> [--if-match N] [--yes]
Flag What it does
--if-match N only move if the SOURCE's current version is N (412 otherwise)
--yes, -y skip the confirmation prompt
bash
pulsvault mv acme-web/prod/product/web/db/passwd acme-web/prod/product/web/db/password
pulsvault mv prod/api/db/old prod/api/db/new --yes        # with a default project set

Needs delete on the source AND write on the destination. Both are checked before anything changes, so a key holding only one of them moves nothing — it cannot half-move and leave the secret at both paths.

Every version is carried and re-encrypted under the new path, so the destination keeps the version number it had. write does not include delete — the only starter permission granting it is -manage-prod, so a key that can set may still be refused here.

The project and the environment cannot change — {project}/{env} is a fixed mount, and promoting a secret between environments is a separate decision. Moving between namespaces under one mount is fine.

--if-match pins the SOURCE's version, which is the end a concurrent write can change between reading it and moving it.

patch#

Partially update a JSON secret: key=value sets, key- removes.

needs an API key · writes to the vault

bash
pulsvault patch <path> <key=value|key->... [--if-match N] [--metadata K=V]
Flag What it does
key=value set/overwrite one field of the JSON object
key- remove one field
--if-match N only patch if the current version is N (auto-pinned to the version read otherwise)
--metadata K=V attach metadata (repeatable)
bash
pulsvault patch prod/product/web/db/creds password=new stale-

The secret it patches is a JSON OBJECT stored as text/plain — write it with a plain set, WITHOUT --content-type application/json. That content type is for a number, boolean or list, and rejects an object, because { } is reserved to mean a folder.

If the fields are separate credentials rather than one document, prefer a FOLDER: db/creds/user and db/creds/password are two secrets you can grant, rotate and validate independently, which one JSON blob cannot be.

path-diff#

Show what changed under a path between two points in time.

needs an API key

bash
pulsvault path-diff <path> --from <timestamp> [--to <timestamp>] [-o json]

    pulsvault path-diff acme-web/prod/product/web/db --from 2026-08-01T00:00:00
    pulsvault path-diff acme-web/prod/product/web --from 2026-08-01T00:00:00 --to 2026-08-20T00:00:00
Flag What it does
--from start of the comparison (RFC3339); required
--to end of the comparison (RFC3339); defaults to now

There is no version number for a whole path, and that is deliberate: versions belong to keys here, not to folders, so two systems writing different keys under one path never overwrite each other. What a folder-level version number would have been FOR is this question — what changed between then and now — so this answers it directly.

Structural: it names which keys were written, added or removed, and never what they contain. Reading a value stays pulsvault get, one key at a time and audited as such — a whole-path comparison that printed plaintext would put every credential under a folder on screen at once.

Needs list on the path rather than read, because it discloses names and version numbers only.

A deleted key shows as removed. A soft delete leaves every version row behind, so this reads the deletion record rather than the versions — without that a deleted key would count as present at every later point and the removed rows could never appear at all.

rollback#

Restore an earlier version's value as a new version.

needs an API key · writes to the vault

bash
pulsvault rollback <path> [--to N]
Flag What it does
--to N version to restore (default: the one before current)
bash
pulsvault rollback prod/product/web/db/password           # back to previous
pulsvault rollback prod/product/web/db/password --to 3    # back to v3

rotate#

Force a new version (fresh data key, same value).

needs an API key · writes to the vault

bash
pulsvault rotate <path> [--if-match N]
Flag What it does
--if-match N only rotate if the current version is N (412 otherwise)

run#

Inject a namespace's secrets as env vars into a command.

needs an API key

bash
pulsvault run --namespace <project>/<env>/<namespace> [--skip-missing] [--clean] [--watch] [--dry-run] [--allow-env NAME] [--pass-token] -- <command> [args…]
Flag What it does
--namespace NS, -n NS the namespace to load
--dry-run print the variable NAMES this namespace would inject and run nothing
--skip-missing warn and continue when a secret fails to load (default: fail)
--watch restart the command when the namespace's secrets change
--interval SEC poll interval for --watch (default 30)
--stop-timeout SEC grace period before the child is SIGKILLed (default 10, 0 = at once)
--clean give the child only the secrets plus HOME, PATH, TERM, TMPDIR and TZ
--keep NAME carry one more variable through --clean (repeatable, or comma-separated)
--allow-env NAME let a secret set a variable that is refused by default — PATH, LD_PRELOAD, NODE_OPTIONS, … (repeatable, or comma-separated)
--pass-token give the child PULSVAULT_TOKEN — the key this command used — for a child that itself runs pulsvault
bash
pulsvault run --namespace prod/product/web -- ./deploy.sh
pulsvault run -n prod/product/web -- npm run migrate -- --verbose
pulsvault run -n prod/product/web --clean --keep HTTPS_PROXY -- ./build.sh
pulsvault run -n prod/product/web --dry-run                    # names only, runs nothing

On a terminal, run names what it handed over — injected 4 secret(s) from <ns>: DB_PASSWORD, …. Names only, never values. It goes to stderr and is skipped when stderr is redirected or under -q, so a CI job log is unchanged; --dry-run is the way to ask the same question from a script, and prints one name per line on stdout.

--dry-run needs no command: pulsvault run -n <namespace> --dry-run answers "what does this namespace put in my environment?" without starting anything. It also reports two secrets whose names collide on one variable before a real run does, and refuses the same flag combinations a real run would. Combined with --watch it prints the plan once and says so — a plan does not restart.

Exit status is the child's. A child killed by a signal reports 128+signum, the same number a shell reports — 137 for an OOM kill, 143 for SIGTERM — so a CI retry rule can tell those apart from an ordinary non-zero exit.

SIGINT, SIGTERM, SIGHUP and SIGQUIT are forwarded to the child, which then has --stop-timeout seconds to exit before it is SIGKILLed. A second signal skips the wait.

A command that never started reports 127 when it could not be found and 126 when it was found but could not be run — the same statuses a shell uses, so a pipeline can tell a mistake in the job definition from a command that ran and failed.

The command runs directly, without a shell: no globbing, no pipes, no ${VAR} expansion by pulsvault. Use -- bash -c '…' when you want a shell to interpret the line.

By default the child inherits your whole environment and the secrets are added on top, so a secret shadows a stale variable of the same name already exported in the shell. --clean replaces that inheritance with HOME, PATH, TERM, TMPDIR and TZ only — no locale variables, so the child runs in the C locale and sorts identically wherever it runs. --keep adds back what a particular command needs (--keep HTTPS_PROXY,NO_PROXY).

PULSVAULT_TOKEN is never inherited, with or without --clean: the key that read the secrets stays out of the child's environment, where crash dumps, error reporters and /proc//environ would carry it. --pass-token hands it over deliberately.

A secret whose name would become a variable that decides how a program starts — PATH, HOME, SHELL, IFS, ENV, BASH_ENV, NODE_OPTIONS, PYTHONPATH, JAVA_TOOL_OPTIONS and their kin, or anything starting LD_, DYLD_, GIT_ or PULSVAULT_ — stops the run before anything starts. Anyone who can write one secret in the namespace could otherwise run code in this process. --allow-env NAME lets a namespace that is meant to set one through.

If the server cut the namespace's listing at its cap (5000 secrets by default), run stops before starting the command: a process started with part of its environment missing fails in ways that are hard to trace back here. Run it on a narrower path.

set#

Write/update a secret (creates a new version).

needs an API key · writes to the vault

bash
pulsvault set <path> <value> | --from-file <f> | --from-stdin  [--content-type T] [--if-match N]
Flag What it does
--from-file F read the value from a file (avoids shell history)
--from-stdin read the value from stdin
--content-type T set the content type
--if-match N optimistic lock: fail (412) unless current version == N
--metadata K=V attach metadata (repeatable)
--allow-empty store an empty value on purpose (refused by default)
bash
pulsvault set prod/product/web/db/password --from-file ./pw.txt
pulsvault set prod/product/web/flags/optional '' --allow-empty   # deliberately empty

A secret's path decides its environment variable name, and a write is the one moment that is free to change — so when a secret's name repeats the folder holding it (db2/db2_url), set names both variables it resolves to and prints the mv that would name it once. A note, not a warning: aws/aws_access_key_id is the right layout for someone who loads the folder. gen and mv say the same.

An empty (or whitespace-only) value is refused, by the CLI and by the server.

A failed command on the left of a pipe hands over zero bytes without failing the pipeline, so cat missing.txt | pulsvault set … --from-stdin would otherwise overwrite a live secret with nothing and still exit 0.

Pass --allow-empty when an empty value is genuinely what you mean.

template#

Render a template file, resolving {{ secret "path" }} via the API.

needs an API key

bash
pulsvault template <file> [--out F] [--watch [--interval SEC]]
Flag What it does
--out F write the rendered output to a file (mode 0600) instead of stdout
--watch keep re-rendering --out when secrets change
--interval SEC poll interval for --watch (default 30)
bash
pulsvault template app.conf.tmpl --out app.conf   # {{ secret "prod/ns/db/password" }}

undelete#

Restore a soft-deleted secret (within 30 days).

needs an API key · writes to the vault · aliases: restore

bash
pulsvault undelete <path>

The window is 30 days from the deletion, matching how long a revoked API key's record is kept — one number for the whole product rather than one per feature.

Past the window the path is gone for good and this reports a failure rather than "not found": the secret did exist, and saying it was never there would send you looking for a typo instead of at your backups.

versions#

List a secret's version history, newest first, with the total.

needs an API key

bash
pulsvault versions <path> [--limit N] [-o json]
Flag What it does
--limit N versions to list (1..100, default 25)

The count is reported separately from the rows, because they are not the same number. The listing stops at --limit, and retention hard-deletes older versions, so neither the row count nor the current version number tells you how many versions a secret holds. That total goes to stderr and is skipped under -q or -o json (where it is the total field), so stdout stays exactly the table a script parses.

Structure#

envs#

List environments in your space.

needs an API key

bash
pulsvault envs [-o json]

namespaces#

List namespaces in a project+environment you can access.

needs an API key · aliases: ns

bash
pulsvault namespaces <project>/<env> [-o json]

projects#

List projects this key can address.

needs an API key

bash
pulsvault projects [-o json]

Validation#

validate#

Run the configured credential probe now (exit codes name the outcome).

needs an API key

bash
pulsvault validate <path> | <project>/<env>/<namespace> --all [--force] [--require-validation]
Flag What it does
--all validate every validated PATH in the namespace (CI gate). Needs a key with list
--force re-probe instead of reporting a result from the last 30s. Still rate-limited
--require-validation with --all, fail (exit 4) when nothing in the namespace is configured
bash
pulsvault validate prod/product/web/db/uri && ./deploy.sh
pulsvault validate prod/product/web --all && ./deploy.sh
pulsvault validate prod/product/web/db --force        # after rotating a credential

Exit codes: 0 the credential works · 1 it genuinely failed · 2 usage error · 3 the check could not be completed (timeout, probe error, rate-limited) · 4 nothing configured for that path · 5 could not reach or authenticate to PulsVault.

3 is the one to treat as retryable: a validator timeout is not a broken credential, and failing a deploy over a network blip is what teaches teams to write || true.

A result from the last 30 seconds is reported rather than re-run, so a matrix build fanning many jobs at one path does not become many sign-ins against your provider. A write to the path always forces a real probe, so rotate-then-validate is never answered from before the rotation.

validation-history#

Show a secret's validation history.

needs an API key

bash
pulsvault validation-history <path> [--limit N] [-o json]

validation-status#

What is validated, and was it working? An environment, a namespace, or one path.

needs an API key

bash
pulsvault validation-status <project>/<env> | <namespace> | <path>  [--status S] [--max-age 1h] [-o json|yaml]
Flag What it does
--status S keep only paths in one health state: verified, degraded, failing, never_tested or disabled. The same five the console's tiles use
--limit N stop after N paths in an environment listing. Without it every page is fetched; when it cuts the answer short the total is printed
--max-age D gate on the LAST recorded result instead of probing: exit 0 only if it was SUCCESS within D (30m, 1h, 24h). No traffic to your provider
bash
pulsvault validation-status acme-web/prod                      # the whole environment
pulsvault validation-status acme-web/prod --status failing     # only what is broken
pulsvault validation-status acme-web/prod/product/web          # one namespace
pulsvault validation-status acme-web/prod/product/web/db       # one path
pulsvault validation-status acme-web/prod/product/web/db --max-age 1h && ./deploy.sh

Triggers nothing. This reads what is already recorded, so it costs nothing and never touches the credential — the command to reach for when you just want to know whether a path is validated at all.

Scope follows the path: two segments is an environment (grouped by namespace), three or more is a namespace or a single path inside it.

An environment or namespace listing needs a key with list, and shows only the namespaces that key may reach. A single path needs only read, the same grant validate needs.

A LISTING (an environment or a namespace) always exits 0 when it can answer, even with paths failing — it reports, it does not gate. A SINGLE PATH with no validation configured exits 4, the same code validate uses for it. validate is the gate.

Config#

config#

Manage profiles: list | use

| project [] | remove

| path.

no credential required

bash
pulsvault config [list|use <profile>|project [<name>|-]|remove <profile>|path]
Flag What it does
list every profile; * marks the active one, and any missing token is named
use <profile> make a profile the active one for later commands
project [<name>] the project this profile assumes, so paths can omit it. Bare prints it; - clears it
remove <profile> (rm) forget a profile's token, host and project
path where the config file lives
bash
pulsvault config list
pulsvault config use ci-deploy
pulsvault config project acme-web        # then `list prod/api` means acme-web/prod/api
pulsvault config project --profile mycorp mycorp-app

A PROFILE is a stored credential — an API key plus the deployment it belongs to. A PROJECT is the first segment of a secret's path. Selecting a profile never changes which project you address, and setting a project never changes which key you use.

The project resolves as --project, then PULSVAULT_PROJECT, then this stored value. A path that already starts with the project is left alone, so fully-qualified paths keep working once it is set.

With no subcommand, pulsvault config lists profiles. Unlike every other command, the config subcommands ignore PULSVAULT_PROFILE: they act on the active profile, or on the one named with --profile.

login#

Verify an API key against the server and store it for a profile.

no credential required

bash
pulsvault login [--profile <name>] [--host URL] [--token pv_live_…] [--set-default]
Flag What it does
--profile P name this credential set (default: prod) — a local label, not an account
--host URL API base incl. /api/v1 — required the first time a custom profile is used
--set-default also make this profile the default (automatic for the first profile)
--skip-verify store the token without checking it against the server (offline setup)
bash
pulsvault login                                   # production, token prompt hidden
pulsvault login --profile mycorp --host https://vault.mycorp.com/api/v1   # your own deployment
echo $PV_KEY | pulsvault login --profile ci

logout#

Remove a profile's stored token (keeps its host).

no credential required

bash
pulsvault logout [--profile <name>] [--all]
Flag What it does
--profile P which profile to clear (default: the active profile)
--all clear the stored token of every profile

status#

Check reachability + auth for the current profile.

needs an API key

bash
pulsvault status [-o json]

whoami#

Show the authenticated service account.

needs an API key

bash
pulsvault whoami [-o json]

Utility#

completion#

Emit a shell-completion script.

no credential required

bash
pulsvault completion bash|zsh|fish
Was this page helpful?