Skip to content

Reference

Output & scripting

Output formats, exit codes, workspace linking, environment variables, and shell completion.

The CLI is built to be scripted. Output is machine-parseable, exit codes are a stable contract, and the app a command operates on can be resolved from the environment instead of a flag on every invocation.

Data-returning commands generally take --output; mutation commands that only print a confirmation line (apps delete, keys revoke, agents revoke, and similar) do not. Formatted commands accept all three formats:

FormatBest forNotes
tableInteractive usePlain-ASCII, the default for list commands.
jsonScripts and jqPretty-printed. The default for single-object commands.
jsonlLine-oriented list pipelinesOne JSON object per line — pipe into jq -c / grep / awk. The CLI validates the page before writing it, so an invalid --fields projection never leaves a partial stream. Falls back to json for single-object responses.
Terminal window
alter apps list --output json
alter audit list --since 7d --output jsonl | jq -c 'select(.action == "token.retrieved")'

Narrow JSON / JSONL output to specific top-level keys with --fields:

Terminal window
alter apps list --fields id,name
# [
# { "id": "<app-id>", "name": "demo" },
# ...
# ]

The value is comma-separated (spaces around commas are fine; spaces inside a name are rejected as a typo). An unknown field name fails loudly rather than emitting null — so a typo surfaces on the first run. --fields is inert with --output table, since table columns are already a fixed slice.

The CLI returns a structured exit code so scripts can branch on the failure mode without parsing stderr. These are a stable contract across releases.

CodeNameMeaning
0OKCommand succeeded.
1ERRORGeneric runtime failure, including network/SDK errors and backend statuses without a dedicated mapping (for example 400, 422, and 5xx).
2USAGEBad flag, argument, or input format. Always paired with a stderr line naming the offending input.
3AUTHNot signed in, or the PAT was revoked / expired. Fix: re-run alter auth login.
4NOT_FOUNDResource not found (404), or a referenced local file is missing.
5CONFLICTA 409 server-state conflict or a 412 stale reviewed version. Inspect current state and review a new change; do not blindly overwrite it. A 409 that only means “busy” is 75 instead.
6RATE_LIMITA 429 — retry with backoff.
7FORBIDDENThe request was refused with a 403: the PAT lacks the required scope, the organization’s plan does not include the feature, or the route is admin-only and the token’s owner is not an organization admin. Fix: re-mint the PAT with broader scopes, upgrade the plan, or have an admin run the command — the printed message says which. The admin check reads the live membership role on every request, so a demotion turns a previously-working script into a 7 without the token changing.
8CANCELLEDA destructive-action prompt was declined or could not read a matching confirmation. Pass the command’s --yes or --confirm <value> flag in CI. An explicit wrong --confirm value is normally USAGE (2) before the request.
75BUSYA concurrent change held a lock the command needed, so nothing was saved or applied. Fix: retry the same command unchanged, with backoff. After a busy policy apply, do not re-plan or edit the plan file: the reviewed plan is still valid. Returned for policy_plan_busy by policy plan and policy apply.

75 is EX_TEMPFAIL from sysexits.h. Apart from self-update, which forwards npm’s own exit code when the install fails, a status not in this table does not come from the CLI: for example, Node.js exits 9 when NODE_OPTIONS or a Node flag is invalid. Fix the environment instead of retrying.

Commands that manage organization resources retry a small set of transient failures themselves before reporting an exit code: at most three attempts per request (two retries). Whether a request is retried depends on its HTTP method, not on what the command is named. The runtime calls made by sdk-passthrough and the runtime checks in verify go through the SDK and are not covered by this policy.

  • GET requests (list, show, status, and most other reads) are retried on HTTP 502, 503, or 504, and on any connection-level failure (reset, refused, DNS). A request that times out is not retried: it already waited the full per-request timeout.

  • PUT, PATCH, and DELETE requests (most updates, deletes, and revoke commands) are retried only on a 503 whose error code is transient_db_conflict, lock_contention, or database_unavailable. The API sends the first two when a lock conflict aborted a database transaction; only the failing transaction was rolled back, so an earlier step of the same request may already have taken effect. It sends database_unavailable when it lost its database connection during the request, for example during a database failover; if the connection was lost while a change was being saved, that change may or may not have been saved. Re-sending is still safe because these requests set state rather than add to it. Any other 5xx, or a dropped connection, is reported unretried. Because an earlier attempt may already have applied the change, a 404 or 409 on the retry can be the result of that attempt (for example, the resource is already deleted) as well as of another client; the error message says so.

  • POST requests are never retried, so a transient failure can never issue a second one-time secret or a duplicate resource. This covers every create and mint (create, keys mint, keys rotate, agents mint-key), and also these commands that send a POST:

    • reads: policy simulate --candidate, identity-providers discover, audit integrity-check, and the preflight check that keys mint runs before minting;
    • updates: apps archive, apps unarchive, grants revoke, and identity-providers webhook enable, disable, and rotate.

    A POST that fails with a 503 database_unavailable may or may not have been applied, because the connection can be lost after the change was saved. Before re-running it, check its result: list or show the resource a create would have made, and after keys mint, keys rotate or agents mint-key look for the new key and revoke any you do not keep.

  • Rate limits (429) are never retried; the command exits 6 and the script owns the back-off.

  • policy plan and policy apply are POSTs, so they are never retried automatically. When another policy change holds a lock they exit 75 with nothing saved or applied, and the script owns the retry of the same command. Re-running plan is safe because the refused attempt saved nothing, and --save writes the file only after the server saved the plan. Re-running apply is safe because apply is idempotent by plan: a plan that already committed returns its committed result.

When the response carries a Retry-After header, the CLI waits that long, capped at 15 seconds, plus up to 20% random jitter — so a single wait is at most 18 seconds. The database 503s (transient_db_conflict, lock_contention, database_unavailable) send Retry-After: 2; the 503 sent while credential verification is temporarily unavailable (error service_unavailable) sends Retry-After: 15, which the cap honours in full. Without the header it waits a random delay of up to 0.5 seconds before the first retry and up to 1 second before the second. The waits for one request never total more than 20 seconds: a retry that would exceed that is skipped and the last response is reported.

Each retry prints one line to stderr, for example alter: GET /apps returned HTTP 503 (transient_db_conflict); retrying in 2.2s (attempt 2 of 3). Stdout is unaffected, so --output json stays parseable. Exit codes are unchanged: a request that still fails after its retries exits with the code in the table above (a 5xx is 1).

Help and version output never report success for a command path that does not exist. A guessed path is a usage error in every form it can be typed, and all three forms produce the same stderr and the same exit 2:

Terminal window
alter connect --help # error: unknown command 'connect'
alter help connect # error: unknown command 'connect'
alter apps nosuchsub --help # error: unknown command 'nosuchsub'
alter help apps nosuchsub # error: unknown command 'nosuchsub'

alter help <path> resolves the whole path, so it documents the command you named rather than the namespace it lives in: alter help apps list prints the same page as alter apps list --help.

An invocation that names no command to run is incomplete: bare alter, and a bare namespace such as alter apps or alter policy rules. Each writes usage to stderr and exits 2. Exit 0 in this CLI means a command ran and succeeded, so an invocation that ran nothing does not report it. Some other CLIs exit 0 here; if a script relied on that, probe with alter --help or alter apps --help, which are successful discovery and exit 0 with the help text on stdout.

Operands beyond a command’s declared arity are a usage error too, rather than being silently discarded.

-V and --version report the CLI’s own version and are accepted only before a command name. Typed after one they are a usage error, so a version flag can never pre-empt the command it was typed after:

Terminal window
alter --version # 0.7.3
alter apps list --version # error: unknown option '--version'

Asking for help validates what you supplied, and does not require what you omitted. Every token you did type is checked exactly as it would be without the help flag: an unknown command, an unknown option, a misspelled subcommand, conflicting options, and surplus operands all produce the same exit code and byte-identical stderr with and without --help, including the error: prefix and the “Did you mean” suggestion.

Three things behave differently on the help path, by design:

  1. Omitted input is not required. alter apps show --help exits 0 even with <app-id> omitted, and alter providers show --help exits 0 without --provider, because those are the values the caller is asking about. Without the help flag both exit 2.
  2. A help flag in a value position is an ordinary token. Nothing is special-cased, so a preceding option claims it: alter keys list --app --help runs the command with --app set to the literal string --help.
  3. Validation inside a command’s action does not run. A value check on a flag is part of executing the command, so alter apps list --output bogus exits 2 while alter apps list --output bogus --help prints help and exits 0.

To explore safely, start at alter --help and descend through confirmed names, or use alter help <path> for any depth.

managed-secrets delete is the exception for an explicit confirmation: the CLI can avoid the read needed to discover the secret slug, so it forwards --confirm and a mismatch returns generic server error code 1.

branding set without --replace, against an app that has no branding yet, exits 2 (usage) rather than 4: the backend’s 404 carries the machine code branding_not_configured, which is a fixable input problem — re-run with --replace — not an absent resource. A script branching on 4 to mean “create it instead” will never fire.

apps create --stub, against a deployment without stub-provider mode enabled, exits 2 (usage) rather than the generic 1 a 422 normally maps to, for the same reason: the rejection is a fixable input problem — re-run without --stub — not a transient failure. Retrying the identical command can never succeed, so a script branching on 1 to mean “retry” must not fire here.

sdk-passthrough request is the one command that maps a backend failure to 5 rather than its usual blanket 1: when the grant’s application is a stub application and the deployment does not run stub-provider mode, the call is refused permanently. The distinct code exists so a script can tell “this application can never work against this deployment” from “that one call failed” — the first must stop, the second may retry.

The pre-minted-token probe in auth login, the remote probe in auth status, and pats whoami use generic code 1 when the backend rejects a resolved PAT. Regular resource commands that use the shared authenticated-command path map a backend 401 to 3.

alter verify also uses code 1 for its verification-input failures: a malformed --key, a malformed --grant used with --runtime, or a missing or malformed --agent required by an agent-runtime design.

alter link predates the dedicated cancellation code: declining its overwrite prompt currently returns 1, and --force is the non-interactive form. All destructive resource prompts use 8.

Example — probe whether an app exists without erroring on the not-found case:

Terminal window
alter apps show "$APP_ID" --output json > /dev/null 2>&1
status=$?
if [ "$status" -eq 0 ]; then
echo "app exists"
elif [ "$status" -eq 4 ]; then
echo "app not found"
elif [ "$status" -eq 7 ]; then
echo "PAT lacks dashboard_apps:read — re-mint with broader scopes"
else
echo "unexpected error" && exit 1
fi

Capture $? into a variable immediately — every command clobbers it.

VariableUsed byEffect
ALTER_PATAll commandsThe PAT to authenticate with. Highest-precedence credential source.
ALTER_APP_IDApp-scoped commandsDefault app ID when --app is omitted (UUID only — the env var and workspace pin never take a name).
ALTER_API_KEYsdk-passthroughRuntime API key for the one-off request escape hatch (not a PAT).
XDG_CONFIG_HOMEAuthentication, Fish completionMoves the plaintext fallback to $XDG_CONFIG_HOME/alter/auth.toml and the Fish install target to $XDG_CONFIG_HOME/fish/completions/alter.fish.
XDG_CACHE_HOMEUpdate notifierMoves the 24-hour update-check cache from ~/.cache/alter/update-check.json.
XDG_DATA_HOMEBash completionChanges the Bash install target to $XDG_DATA_HOME/bash-completion/completions/alter.
SHELLCompletionAuto-detects bash, zsh, or fish when --shell is omitted.
ALTER_NO_UPDATE_NOTIFIER, NO_UPDATE_NOTIFIER, CIUpdate notifierAny non-empty value other than 0 or false disables the update notice.
ALTER_NO_TELEMETRY, DO_NOT_TRACKUsage telemetry1 or true disables telemetry when the CLI build has telemetry configured.

Published builds do not emit telemetry unless telemetry has been explicitly configured. When enabled, events use a random anonymous installation ID stored at ~/.config/alter/telemetry-id; no user PII is attached. Delivery is best-effort and bounded, so it cannot change a command’s result.

If you work primarily on one app, pin it to the current directory tree so app-scoped commands don’t need --app:

Terminal window
cd ~/code/my-product
alter link <app-id>
# alter: pinned app_id=<app-id> in .../.alter/config.yaml
# alter: appended `.alter/` to .gitignore so the pin isn't committed.
# From anywhere in this tree, --app is now optional:
alter keys list
alter agents create --name worker --type service \
--scopes '{"google":["openid"]}'
alter policy show-app
# Inspect or clear the pin
alter link --status
alter unlink

The app ID is resolved in this order (highest precedence first):

  1. --app <id> on the command line
  2. ALTER_APP_ID environment variable
  3. The nearest .alter/config.yaml, found by walking up from the current directory
  4. Otherwise: no app selected (exit code 2)

Discovery checks each parent directory through $HOME itself, then stops before walking above it. Therefore $HOME/.alter/config.yaml intentionally acts as a user-wide default; configs above the home directory are never read. The search tests for .alter/config.yaml, not merely an .alter/ directory.

alter link <app-id> validates the UUID and verifies that the app exists before writing. If the current directory already pins a different app, it prompts before overwriting; use --force for scripts. In an enclosing git repository it appends .alter/ to the repository root’s .gitignore. alter unlink removes only ./.alter/config.yaml in the current directory; it does not remove a pin discovered from a parent.

Generate static completion scripts for bash, zsh, or fish:

Terminal window
# Write the script to the conventional location and print the line to add to the rc file
alter completion install
# Or print to stdout to install it yourself
alter completion print --shell zsh >> ~/.zsh/completions/_alter

install writes the script and prints the line to activate it; it never edits .bashrc / .zshrc automatically. The shell is auto-detected from $SHELL unless --shell is passed. Unsupported or undetectable shells return usage code 2; installation on Windows also returns 2 and directs the operator to completion print.

ShellDefault install path
Bash$XDG_DATA_HOME/bash-completion/completions/alter, or ~/.local/share/bash-completion/completions/alter
Zsh~/.zsh/completions/_alter
Fish$XDG_CONFIG_HOME/fish/completions/alter.fish, or ~/.config/fish/completions/alter.fish

The generated scripts do not query Alter. They statically complete this current top-level subset: auth, apps, keys, agents, providers, policy, audit, pats, link, unlink, completion, sdk-passthrough, self-update, and help, plus a static subset of their verbs. They do not complete flags, resource IDs, or the remaining namespaces.

Terminal window
# Upgrade to the latest published version
alter self-update
# Pin a specific version, or preview the command without running it
alter self-update --to 0.3.0
alter self-update --to next
alter self-update --dry-run

--to accepts strict SemVer (including prerelease/build metadata) or the npm tags latest and next; other values fail locally with usage code 2. Legacy self-update --version is rewritten to --to with a deprecation warning, in every form it was typed: --version <value>, --version=<value>, and a bare --version. The bare form has no version to pin, so it reports option '--to <version>' argument missing and exits 2. Run alter --version for the installed CLI version, before any command name.

self-update runs npm install -g @alter-ai/cli@<version> and smoke-tests the resolved binary. If the active binary is not under npm’s global prefix, the command refuses with usage code 2; update Homebrew, apt, or other package-manager installations through that manager. A failed npm install forwards npm’s own exit code.

After a successful interactive command, the CLI may print a newer-version notice to stderr. It checks npm at most once per 24 hours, caches under the XDG cache directory, skips piped stdout, --version, and self-update, and silently ignores registry/cache failures. Use one of the notifier opt-out variables in the environment table when deterministic interactive stderr is required.

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.