Skip to main content
zespan policy manages policy-as-code files: the YAML policies in your repository that compile into guardrails.

Quick start

You must be signed in

pull, test, plan and apply check your role in the organization that owns the project. That is a question about a person, so they need a person’s session:
An API key cannot run these commands, and the CLI now says so instead of sending the request. ZESPAN_API_KEY / --api-key authenticates a project, not a person, and the server has no member role to check for one — so every such request was already refused with a 403 that read like an org-membership problem. The CLI refuses up front now:
If you have a CI job that passes ZESPAN_API_KEY to zespan policy plan, it was never working — see In CI for what to run instead.
If both a sign-in and an API key are present, the sign-in wins for these commands; the API key is left alone for the data plane and zespan doctor. See the two credentials.

Verbs

validate and init make no network call and need no credential of any kind. That is deliberate: validate is meant to run as a pre-commit or pre-push hook, and a hook that needs the network is a hook people disable. The parser and the full schema are compiled into the binary.

Flags

Configuration precedence is the same as every other zespan subcommand — see CLI overview. As there, the API key and API URL are never read from .zespan.yaml, so a committed config file cannot redirect where your key is sent.

zespan policy init

Writes policies/pii-egress.yaml with a starter policy in dryrun mode. It refuses rather than overwriting an existing file.

zespan policy pull

Generates one policy file per guardrail that already exists, so adopting an existing project does not mean transcribing it by hand. By default it covers guardrails created in the dashboard; --all also re-emits ones already managed in code, which is useful if a file was lost. Each generated file pins its rule to the existing guardrail with a slug:, so the plan straight afterwards reports no changes to make — only pending adoptions. Files you have already edited are skipped unless you pass --force. Generation is not lossless and prints a warning for every case it could not express exactly; see what pull cannot express.

zespan policy validate

Reads every .yaml/.yml file under policies/ and validates all of them, reporting every problem at once with file, line and a message that says what to write instead. Exits 0 when everything is valid, 1 otherwise. As a pre-commit hook:
.git/hooks/pre-commit

zespan policy test

Runs your policies over the project’s own recorded traffic and reports what they would have caught versus what they would have broken, per rule, with the actual conversation behind each false positive. See Policy testing. Promoting a policy to deny requires a test result or --untested.

zespan policy plan

Prints what an apply would change, and changes nothing:
The plan is computed on the server against live state, so what the CLI shows and what an apply would do cannot disagree. Re-running plan against unchanged state prints No changes. — reformatting a file, reordering its keys, or adding comments is not a change, because the comparison runs over a canonical form.

zespan policy apply

apply computes a plan first and sends its hash with the apply, so you cannot apply a plan you never saw. It refuses in the cases below, each naming the flag that resolves it:
appliesTo.environments is a guard, not a router — it never sends a policy somewhere you did not name on the command line, it only refuses to write it where the policy says it does not belong. Either point --env at an environment the policy lists, or add this one to the file. Every offending policy is named, not just the first. Checked before every other refusal below, so a misdirected apply is reported before anything else — see scoping to environments.
Someone else applied, or changed a guardrail in the dashboard, between your plan and your apply. Re-plan and look at the new diff. --force overrides.
Someone took ownership of that policy from the dashboard — often during an incident. Applying over it would undo their change. --force takes ownership back into code.
Run zespan policy test --against issues and look at the false positives. --untested exists because a platform engineer mid-incident has to be able to ship — see the deny gate.
Normal straight after zespan policy pull. Adopting makes those guardrails read-only for whoever built them, so it is never implicit. The row is updated in place and keeps its execution history.
A policy that used to be in your file set is gone. Removing a guardrail removes a control, so it is never implicit. --allow-remove makes it deliberate.
Nothing is written when any of these fires.

Every apply names a person

Because these commands run as you rather than as a project key, an apply is attributed to a real identity. The policy.applied audit entry records the user, their organization, the session the request came from, the device — the machine registered by zespan auth login — plus the IP address and user agent the server observed. The recorded apply itself names the acting user alongside each file’s SHA-256 content hash. An apply made from the dashboard records no device, because there is no CLI machine behind it. That is an honest blank rather than a guess.
A control-plane mutation the server cannot attribute to a real user is refused with 401, not recorded against a placeholder. There is no unattributed apply to find in the audit log, because one can no longer be made.
See Audit log.

In CI

.github/workflows/policies.yml
plan and apply cannot run unattended today. They need a user session, and the only way to obtain one is zespan auth login, which requires a human to approve in a browser. There is no machine credential for the control plane — an API key is refused, by design.If you previously ran zespan policy plan in CI with ZESPAN_API_KEY, it was returning 403 rather than planning anything. Run plan and apply from a developer machine that is signed in, and keep validate — which needs no credential — as the CI gate.
Applying needs policy:apply, which is granted to owner and admin only — editor deliberately does not have it, matching how guardrails are already treated as a high-risk resource. A project you cannot reach is reported by name:

Next steps

Policy as code

The authoring loop and the ownership model.

File reference

Every field and the supported YAML subset.

zespan auth

Sign in — pull, test, plan and apply all need it.

zespan link

Link a project so you never pass --project.

CLI overview

Configuration precedence and install options.

zespan doctor

Diagnose SDK setup problems.