Skip to content

Command-line reference

Every command pistra accepts is here, with the flags each one takes. The page is generated from the commands themselves. The descriptions are the text -h prints and the tables are the flags as declared, so a renamed flag or a reworded explanation changes this page in the same commit or not at all.

One binary does all of it. The subcommands are here rather than in a second tool because the image ships distroless. There is no shell to curl from and nowhere to install anything beside it, so kubectl exec into a running pod reaches the admin API, the backup verbs and the offline recovery path with the binary that is already there.

Commands that call a running gateway take a door: -socket for the local Unix socket, which is authenticated by the caller’s peer credentials, or -url with the token pistra login cached. Neither is guessed at when both are given. See Sign in from the command line for the sign-in flow and Reach the admin API without your identity provider for the socket.

Command pistra runs the gateway. Two modes:

  • flag mode: -upstream/-dialect/-alias describe a single provider, no virtual keys, the original passthrough spine;
  • config mode: -config points at a YAML file with providers, model routing, virtual keys, CEL access rules and the admin listener.

SIGHUP reloads the config file; a bad file logs and keeps the previous snapshot serving.

flag value default
-config string node file path; its deployment names the deployment document. When set, the flags below are ignored
-listen string :8484 address to serve on
-metrics-listen string :9464 address to serve /metrics on; none serves no metrics
-preset string provider preset from the compiled catalog (e.g. groq, mistral); overrides -upstream and -dialect
-upstream string https://api.openai.com provider base URL
-dialect string openai upstream API dialect: openai or anthropic
-max-body integer 32 << 20 max request body bytes
-openapi boolean print the control-plane OpenAPI 3.1 spec and exit
-config-schema boolean print the JSON Schema for a configuration source document and exit
-test-rules string run the rule suite at this path against -config and exit; non-zero when a case fails
-alias string model alias as client=upstream (repeatable)

Run this command:

pistra check -config PATH [-timeout 5s] [-offline]

Loads the configuration the way the gateway would, with the same parse, the same variable and secret expansion, every model resolved, every recognizer’s examples run and every rule compiled. It then asks each thing it names to answer: a cheap request at every provider with its credential, discovery and the key set at every issuer, a request at every fronted MCP server, the card of every fronted agent, and the native libraries the guardrails would load. One line per answer, and a non-zero exit when any is a failure.

It starts nothing and writes nothing: no listener, no raft log, no key. A check that passes says the file is sound and its dependencies answer from this machine; the gateway may still find a port taken.

The node half is read as written. What the file leaves out is reported as what the gateway will do about it, whether that is no admin door, no public_url or plain HTTP, rather than refused, since each of those is a deployment somebody may mean.

Run this command:

Terminal window
pistra check -config pistra.yaml
pistra check -config pistra.yaml -offline # in a pipeline, with placeholder variables
flag value default
-config string node file path (required); its deployment names the deployment document, and both are checked
-timeout duration how long each network probe may take
-offline boolean compile only: touch no provider, issuer, server, agent or library

Run this command:

pistra admin [-socket PATH | -url URL] [-d BODY] [METHOD] PATH

Calls the admin API. METHOD defaults to GET.

Use the following example:

-socket the local Unix socket, which needs no token: the caller is
named by its peer credentials and decided over by
admin.policy_file like any other caller.
-url over the network, presenting the token `pistra login`
cached. Renewed automatically while a refresh token lasts.

Run this command:

Terminal window
pistra admin /admin/v1/config
pistra admin -url https://gw.example /admin/v1/suspensions
pistra admin PUT /admin/v1/suspensions/provider:openai \
-d '{"reason":"INC-4471","duration":"4h"}'
pistra admin DELETE /admin/v1/suspensions/provider:openai

Exits non-zero when the API answers 400 or above.

flag value default
-socket string admin.local_socket path (default $PISTRA_ADMIN_SOCKET)
-url string gateway URL, using the token from pistra login (default $PISTRA_ADMIN_URL)
-d string request body; @- reads stdin

Run this command:

pistra query [-socket PATH | -url URL] [-o table|json] [-p VALUE]... SQL
pistra query [-socket PATH | -url URL] [-o table|json] -tables

Runs one SQL statement over the admin API’s list views: keys, profiles, budgets, backends, models, mcp_servers, a2a_agents, suspensions, secrets, sources, cluster_members, audit_heads, reconcile and reconcile_verdicts. Each table is the matching list endpoint’s response, row for row, and exists for a caller who may call that endpoint; the statement joins and sums them in one read of one node. SQL may be - to read the statement from stdin.

Nested values are JSON text; read into them with ->>. NULL prints as an empty cell in a table; -o json keeps the distinction.

Run this command:

Terminal window
pistra query -socket /run/pistra/admin.sock -tables
pistra query -url https://gw.example \
'SELECT k.name, p.budget, b.status FROM keys k JOIN profiles p ON p.name = k.profile JOIN budgets b ON b.budget = p.budget'
pistra query -url https://gw.example -p openai \
'SELECT day, class, metered, reported, ratio FROM reconcile_verdicts WHERE provider = ? AND breach = 1'
flag value default
-socket string admin.local_socket path (default $PISTRA_ADMIN_SOCKET)
-url string gateway URL, using the token from pistra login (default $PISTRA_ADMIN_URL)
-o string table output: table or json
-tables boolean describe the tables this caller may query instead of running a statement
-p string a value for the next ? in the statement (repeatable)

Run this command:

pistra login [-issuer NAME] [-device] [URL]

Signs in at the gateway’s identity provider and caches the token, so pistra admin -url URL can call the API afterwards. URL defaults to $PISTRA_ADMIN_URL.

The flow is PKCE with a loopback redirect where there is a browser to open, and the device grant where there is not, over SSH for example, which is where somebody otherwise reaches for a long-lived token.

Run this command:

Terminal window
pistra login https://gw.example
pistra login -device https://gw.example
flag value default
-issuer string which issuer to sign in at, when the gateway trusts more than one
-device boolean use the device flow even where a browser could be opened

Run this command:

pistra logout [-all] [-list] [URL]

Forgets this machine’s cached token for a gateway. The token stays valid at the identity provider until it expires; revoking it there is a separate act.

flag value default
-all boolean forget every cached sign-in
-list boolean print the cached sign-ins and change nothing

Run this command:

pistra cluster recover [flags]
pistra cluster prune [flags]

recover is an offline operation on this node’s raft data directory; run it with the gateway stopped. prune declares a running cluster’s membership over its forward listener, with the cluster secret.

Run this command:

pistra cluster recover [-config FILE | -data-dir DIR -node-id ID] [-keep IDS] [-confirm]

Recovers a cluster that has lost quorum for good: rewrites this node’s raft state to believe in a smaller membership, so the survivors can elect a leader again. Keys, budget ledgers, certificates, suspensions and config sources on this disk are kept.

Run it on a STOPPED node. Run it once, on the survivor with the highest last index, and bring the rest back with a blank data directory and cluster.join. It can only drop members, never add one.

Without -confirm it prints what it found and exits, writing nothing.

Run this command:

Terminal window
pistra cluster recover -config /etc/pistra/config.yaml
pistra cluster recover -config /etc/pistra/config.yaml -confirm
pistra cluster recover -data-dir /var/lib/pistra/raft -node-id gw-a \
-keep gw-a,gw-b -confirm
flag value default
-config string node file to read cluster.node_id and cluster.data_dir from
-data-dir string raft data directory (overrides -config)
-node-id string this node’s cluster.node_id (overrides -config)
-keep string comma-separated node ids to keep (default: this node alone)
-confirm boolean write the recovered configuration; without it, nothing is written

Run this command:

pistra cluster prune -addr HOST:PORT[,...] -keep IDS [-attempts N]

Tells a running cluster that its members are exactly -keep, and removes every other one. The request goes to the first address that answers; a follower relays it to the leader, which hands leadership over first if it is among the removed. Keeping every current member is a no-op.

The cluster secret is read from PISTRA_CLUSTER_SECRET. It authenticates the request and derives the certificate the forward listener requires.

Use the following example:

PISTRA_CLUSTER_SECRET=... pistra cluster prune \
-addr pistra-0.pistra-headless:7001,pistra-1.pistra-headless:7001 \
-keep pistra-0,pistra-1 -attempts 30
flag value default
-addr string comma-separated forward addresses of members to try, in order
-keep string comma-separated node ids that remain; every other member is removed
-attempts integer 1 passes over the addresses, five seconds apart, before giving up

Run this command:

pistra audit verify [flags] FILE...

Offline checks on an exported audit trail.

Run this command:

pistra audit verify [-heads FILE] [-key KID=BASE64]... FILE...

Reads an exported audit trail, either the gateway’s stderr or a collector’s dump of the pistra/audit scope written back as lines, and checks every chain in it: each record hashes to its entry, each entry links to the one before it, each is signed by the key the chain started with, and no sequence number between the first and last seen is missing. Other lines on the stream are ignored. FILE may be - for stdin.

What it cannot see is a trail cut at the end. Give it -heads with what the cluster or an operator witnessed and it will report a trail that stops short of a witnessed head.

Exit status is 0 when every chain verified, 1 otherwise.

Run this command:

Terminal window
pistra audit verify gateway.log
kubectl logs pistra-0 | pistra audit verify -
pistra audit verify -heads heads.json -key 3f2a9c1b8d7e6f50=BASE64 export.jsonl
flag value default
-heads string JSON file of witnessed chain heads ([{node,kid,seq,hash}]) the trail must reach
-key string public key for a chain whose start record is not in the input, as KID=BASE64 (repeatable)
-q boolean print findings only

Run this command:

pistra snapshot save [-socket PATH | -url URL] [-stale] FILE
pistra snapshot restore [-socket PATH | -url URL] FILE

Back up the cluster’s replicated state to an archive, or restore it.

Run this command:

pistra snapshot save [-socket PATH | -url URL] [-stale] FILE

Forces a raft snapshot on the node and writes it to FILE (- for stdout) as a gzipped tar of meta.json, state.bin and SHA256SUMS: every key, budget, configuration source with its history, sealed secret, keyring, certificate and witnessed audit head. Only the leader answers unless -stale is given; a follower’s state may trail by a commit round.

Secrets stay sealed in the archive. Only a cluster holding the same cluster secret can open them again.

Run this command:

Terminal window
pistra snapshot save -socket /run/pistra/admin.sock backup.snap
pistra snapshot save -url https://gw.example - | aws s3 cp - s3://backups/pistra.snap
flag value default
-socket string admin.local_socket path (default $PISTRA_ADMIN_SOCKET)
-url string gateway URL, using the token from pistra login (default $PISTRA_ADMIN_URL)
-stale boolean take the snapshot from this node even if it is not the leader

Run this command:

pistra snapshot restore [-socket PATH | -url URL] FILE

Replaces the cluster’s replicated state with the archive in FILE (- for stdin). Membership is kept: the archive restores into whichever cluster receives it, which is how a wiped or replaced cluster gets its state back. Any node accepts it and forwards to the leader. Every node rebuilds from the restored state at once, and the previous state is gone. Take a snapshot first if there is any chance you want it back.

Run this command:

Terminal window
pistra snapshot save -socket /run/pistra/admin.sock before-restore.snap
pistra snapshot restore -socket /run/pistra/admin.sock backup.snap
flag value default
-socket string admin.local_socket path (default $PISTRA_ADMIN_SOCKET)
-url string gateway URL, using the token from pistra login (default $PISTRA_ADMIN_URL)

Run this command:

pistra models pin [-revision REV] [-model PATH] [-endpoint URL] [-token TOKEN] OWNER/NAME

Resolve a HuggingFace repository to a commit and print the models entry to paste.

Run this command:

pistra models pin [-revision REV] [-model PATH] [-endpoint URL] [-token TOKEN] OWNER/NAME

Resolves the revision to its commit and prints the models entry a configuration takes. A gateway accepts a commit and nothing else, so that the artifact behind a detector cannot change under a configuration that did not; this is where a branch becomes one.

Run this command:

Terminal window
pistra models pin Xenova/bert-base-multilingual-cased-ner-hrl
pistra models pin -model onnx/model_quantized.onnx Xenova/bert-base-multilingual-cased-ner-hrl
flag value default
-revision string main branch, tag or commit to resolve
-model string path of the ONNX graph within the repository
-endpoint string the Hub, or a mirror of it
-token string bearer token for a gated repository (default $HF_TOKEN)
-name string the name detectors will reference (default: the repository name)

Run this command:

pistra guardrails eval -config FILE [flags] DATA...

Score the config’s guardrail ensemble against labelled text.

Run this command:

pistra guardrails eval -config FILE [flags] DATA...

Compiles the config’s guardrails exactly as the gateway does, with models fetched and verified, native libraries loaded and thresholds applied, and runs every case of each DATA file through the ensemble as one chat turn, scoring what it found against what the file marks.

A .jsonl file holds one case per line:

Use the following example:

{"text": "...", "spans": [{"start": 0, "end": 12, "type": "PERSON"}], "tags": ["clitic"]}
{"text": "...", "labels": ["INJECTION"]} an empty list is a clean text
{"text": "...", "kind": "tool_result", "system": "You are ...", "labels": ["INJECTION"]}

Any other file is read as CoNLL: a token and its tag per line, blank lines between sentences. Offsets are bytes into the text. A case’s kind is the segment it is presented as (default -kind); its system is the instruction sent as the system turn before it, and findings on that turn are not the case’s.

Spans are scored exactly and with overlap allowed (the ~ columns); labels as sets. A tag on a case repeats the totals for that slice.

Run this command:

Terminal window
pistra guardrails eval -config eval/ner.yaml -types PERSON data/anercorp-test.conll
pistra guardrails eval -config eval/injection.yaml -sweep data/deepset-test.jsonl
flag value default
-config string deployment document whose guardrails are measured, or a node file naming one (required)
-detector string score one detector’s findings, by name (default: every detector’s)
-kind string user present a case that names no kind as this segment kind: user, system, assistant, tool_result or output
-types string comma-separated entity types or labels to score (default: the ones the data carries)
-sweep boolean recompile with thresholds lowered and report the average precision plus precision, recall and F1 at every 0.05 and at the quantiles of the scores seen
-errors integer 10 how many wrong cases to print
-parallel integer 1 cases inspected at once
-limit integer 0 score only the first N cases of each file
-json boolean print the report as JSON
-invariance boolean read the cases as one meaning per row in several languages, ids --, and report how the verdict changes with the language
-reference string en with -invariance: the language the others are read against
-verdicts boolean with -json: every case’s gold and findings, the whole matrix rather than the -errors sample
-rename string rename a type in the data, as FROM=TO (repeatable; default PER/PERS=PERSON, LOC/GPE=LOCATION, ORG=ORGANIZATION, MIS=MISC)

Run this command:

pistra pack list
pistra pack show PACK
pistra pack add [-deployment FILE] [-write] PACK...

List the control packs this binary ships, read one, or copy one or more into a deployment document. PACK is a built-in id or a pack directory.

Run this command:

pistra pack list

Prints one line per built-in pack: its id, its version, how many rules and controls it carries, and its title. The id is what pack show and pack add take; pack show prints one pack whole, README included.

Run this command:

pistra pack show PACK

Prints the pack: its README, the controls it cites with their citations, the controls it attaches to other packs’ rules, the controls it maps to a mechanism rather than a rule, and the deployment fragment pack add would copy, comments included. That is everything a reviewer needs and everything there is; a pack has no hidden half. PACK is a built-in id (see pack list) or the path of a pack directory, which is checked before it is shown.

Run this command:

pistra pack add [-deployment FILE] [-write] PACK...

Copies each pack’s detectors, guardrail rules and access rules into the deployment document, at their own addresses, leaving every other line of the document as it was, comments included. The result is compiled the way the gateway compiles it before anything is printed or written, so a pack whose rule names a detector the document calls something else is refused with the gateway’s reason. Variables the document takes from the environment have to be set, as they do for pistra check.

An object the document already has is compared with the pack’s by meaning. Equal, it is kept; different, the add is refused and names it, and nothing is written. So adding a pack twice changes nothing, and adding a newer edition of a pack lists what the edition changed.

A pack that builds on another attaches its control identifiers to rules that pack carries, and add unions them onto the rule’s controls. Name the base pack first on the command line, or add it earlier; a pack whose base is absent is refused and names it.

What was added and what was already there is reported on stderr, one line per object. Flags come before the packs, as in every command of this binary. Without -write the merged document goes to stdout:

Run this command:

Terminal window
pistra pack add -deployment deployment.yaml sa-pdpl > merged.yaml
pistra pack add -deployment deployment.yaml -write sa-pdpl

A pack directory of your own is added the same way, and is held to the same checks as a built-in first:

Run this command:

Terminal window
pistra pack add -deployment deployment.yaml -write ./packs/acme-controls
flag value default
-deployment string deployment.yaml the deployment document to copy the pack into; a file that does not exist is an empty document
-write boolean rewrite the document in place. Without it the merged document is printed and the file is untouched

Run this command:

pistra version

Prints one line: the build, its commit and date, the Go toolchain and the platform, which is what to paste into a bug report. A build made outside the release pipeline reads what the toolchain stamped, so it answers with something true rather than “unknown”, and says -dirty when it was made from a modified tree.