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.
Contents
Section titled “Contents”pistra checkpistra adminpistra querypistra loginpistra logoutpistra clusterpistra auditpistra snapshotpistra modelspistra guardrailspistra packpistra version
pistra
Section titled “pistra”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) |
pistra check
Section titled “pistra check”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:
pistra check -config pistra.yamlpistra 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 |
pistra admin
Section titled “pistra admin”Run this command:
pistra admin [-socket PATH | -url URL] [-d BODY] [METHOD] PATHCalls 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:
pistra admin /admin/v1/configpistra admin -url https://gw.example /admin/v1/suspensionspistra admin PUT /admin/v1/suspensions/provider:openai \ -d '{"reason":"INC-4471","duration":"4h"}'pistra admin DELETE /admin/v1/suspensions/provider:openaiExits 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 |
pistra query
Section titled “pistra query”Run this command:
pistra query [-socket PATH | -url URL] [-o table|json] [-p VALUE]... SQLpistra query [-socket PATH | -url URL] [-o table|json] -tablesRuns 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:
pistra query -socket /run/pistra/admin.sock -tablespistra 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) |
pistra login
Section titled “pistra login”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:
pistra login https://gw.examplepistra 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 |
pistra logout
Section titled “pistra logout”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 |
pistra cluster
Section titled “pistra cluster”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.
pistra cluster recover
Section titled “pistra cluster recover”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:
pistra cluster recover -config /etc/pistra/config.yamlpistra cluster recover -config /etc/pistra/config.yaml -confirmpistra 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 |
pistra cluster prune
Section titled “pistra cluster prune”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 |
pistra audit
Section titled “pistra audit”Run this command:
pistra audit verify [flags] FILE...Offline checks on an exported audit trail.
pistra audit verify
Section titled “pistra audit verify”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:
pistra audit verify gateway.logkubectl 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 |
pistra snapshot
Section titled “pistra snapshot”Run this command:
pistra snapshot save [-socket PATH | -url URL] [-stale] FILEpistra snapshot restore [-socket PATH | -url URL] FILEBack up the cluster’s replicated state to an archive, or restore it.
pistra snapshot save
Section titled “pistra snapshot save”Run this command:
pistra snapshot save [-socket PATH | -url URL] [-stale] FILEForces 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:
pistra snapshot save -socket /run/pistra/admin.sock backup.snappistra 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 |
pistra snapshot restore
Section titled “pistra snapshot restore”Run this command:
pistra snapshot restore [-socket PATH | -url URL] FILEReplaces 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:
pistra snapshot save -socket /run/pistra/admin.sock before-restore.snappistra 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) |
pistra models
Section titled “pistra models”Run this command:
pistra models pin [-revision REV] [-model PATH] [-endpoint URL] [-token TOKEN] OWNER/NAMEResolve a HuggingFace repository to a commit and print the models entry to paste.
pistra models pin
Section titled “pistra models pin”Run this command:
pistra models pin [-revision REV] [-model PATH] [-endpoint URL] [-token TOKEN] OWNER/NAMEResolves 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:
pistra models pin Xenova/bert-base-multilingual-cased-ner-hrlpistra 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) |
pistra guardrails
Section titled “pistra guardrails”Run this command:
pistra guardrails eval -config FILE [flags] DATA...Score the config’s guardrail ensemble against labelled text.
pistra guardrails eval
Section titled “pistra guardrails eval”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:
pistra guardrails eval -config eval/ner.yaml -types PERSON data/anercorp-test.conllpistra 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 |
-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) |
pistra pack
Section titled “pistra pack”Run this command:
pistra pack listpistra pack show PACKpistra 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.
pistra pack list
Section titled “pistra pack list”Run this command:
pistra pack listPrints 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.
pistra pack show
Section titled “pistra pack show”Run this command:
pistra pack show PACKPrints 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.
pistra pack add
Section titled “pistra pack add”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:
pistra pack add -deployment deployment.yaml sa-pdpl > merged.yamlpistra pack add -deployment deployment.yaml -write sa-pdplA 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:
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 |
pistra version
Section titled “pistra version”Run this command:
pistra versionPrints 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.