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.
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 [ | 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
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
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
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
pulsvault diff <path> [--from N] [--to N] [--reveal]export#
Print a namespace's secrets as env vars.
needs an API key · aliases: export-env
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) |
pulsvault export acme-web/prod/product/web
pulsvault export acme-web/prod/product/web --out .env # 0600, not umaskPrefer 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
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
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 |
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 entryEvery 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
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) |
pulsvault import ./app.env --namespace dev/product/web --dry-runA 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
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 |
--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
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 |
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 setNeeds 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 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
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) |
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
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
pulsvault rollback <path> [--to N]| Flag | What it does |
|---|---|
--to N |
version to restore (default: the one before current) |
pulsvault rollback prod/product/web/db/password # back to previous
pulsvault rollback prod/product/web/db/password --to 3 # back to v3rotate#
Force a new version (fresh data key, same value).
needs an API key · writes to the vault
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
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 |
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 nothingOn 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/
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
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) |
pulsvault set prod/product/web/db/password --from-file ./pw.txt
pulsvault set prod/product/web/flags/optional '' --allow-empty # deliberately emptyA 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
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) |
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
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
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
pulsvault envs [-o json]namespaces#
List namespaces in a project+environment you can access.
needs an API key · aliases: ns
pulsvault namespaces <project>/<env> [-o json]projects#
List projects this key can address.
needs an API key
pulsvault projects [-o json]Validation#
validate#
Run the configured credential probe now (exit codes name the outcome).
needs an API key
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 |
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 credentialExit 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
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
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 |
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.shTriggers 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 [
| path.
no credential required
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 |
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-appA 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
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) |
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 cilogout#
Remove a profile's stored token (keeps its host).
no credential required
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
pulsvault status [-o json]whoami#
Show the authenticated service account.
needs an API key
pulsvault whoami [-o json]Utility#
completion#
Emit a shell-completion script.
no credential required
pulsvault completion bash|zsh|fish