# The sar CLI > Everything the console does, from a shell — and the one thing it does that the console cannot. Source: https://docs.stg.perdurance.dev/cli/ --- `sar` drives the same public API the console does. Nothing in it reaches around the API into the database, which is what makes it usable against a deployment you do not operate. ## Connecting Three global options, each with an environment variable behind it, so a shell can be configured once and every command inherits it. | Option | Variable | Default | |---|---|---| | `--url` | `ROUTER_URL` | `http://127.0.0.1:8787` | | `--tenancy` | `ROUTER_TENANCY` | The one the session key opens | | — | `ROUTER_API_KEY` | — | | — | `ROUTER_SESSION_KEY` | — | | — | `ROUTER_REGISTRATION_TOKEN` | — | ```bash export ROUTER_URL=https://api.perdurance.dev export ROUTER_TENANCY=acme export ROUTER_API_KEY='sar_ab12cd34_…' ``` The `CTX_*` variables belong to the service and `ROUTER_*` to this client. The two lists never mix, so it is always clear which side of the connection a setting is about. ## Which credential a command wants This is the part that surprises people. Commands do not all take the same kind of credential. | Wants | Commands | |---|---| | A registration token | `register` | | Nothing | `login` | | A session | `password`, `whoami`, `logout` | | An API key | `submit` | | Either a session or an API key | everything else | `submit` is the one place a session is refused by kind rather than by role: sending a request is something a machine credential does, and holding a browser session should not be enough to spend your provider budget. ## Getting started from a shell ```bash # once per organisation, with a registration token from the deployment. # `register` creates the tenancy, so it has to be told what to call it. sar register --tenancy acme --email you@example.com # thereafter sar login --email you@example.com sar namespaces create --slug prod --name Production sar backends create --namespace prod --name openai --kind openai_compat sar rules create --namespace prod --priority 100 --pattern '*' --backend sar keys create --name my-service ``` `backends create` prompts for the credential, or reads it from standard input with `--credential -`. There is no way to pass one inline, deliberately: an argument sits in the shell's history and is visible in `ps` to everyone on the machine. ```bash pass show openai/prod | sar backends create --namespace prod \ --name openai --kind openai_compat --credential - ``` ## Sending a request ```bash sar submit --namespace prod --dialect openai --file body.json ``` The body travels **exactly as written** — `sar` does not reformat it. That matters more than it looks: the router hashes the bytes it was given, so a re-sent file deduplicates only if it is byte-identical. See [Idempotency](/idempotency). `--file -` reads from standard input: ```bash jq -n '{model:"gpt-4.1",messages:[{role:"user",content:"Say hello."}]}' \ | sar submit --namespace prod --file - ``` `--dialect` is `openai` (the default) or `anthropic`, and it decides which vendor route the body is sent to rather than being sniffed from the body. Which shape the answer takes is read off what came back rather than worked out from the body, so a request that asked for a stream and was refused before reaching the provider still prints as one document. ## Working with stored requests ```bash # fire-and-forget: prints the id and returns sar requests submit --namespace prod --file body.json # read one back, whatever it has reached so far sar requests get 01J8F2ZK9QX3M4NBVWT7 --namespace prod ``` `sar submit` holds the connection and gives you the provider's answer. `sar requests submit` walks away with an id. Same body, same deduplication. The id is positional; the namespace is not, because a request id is only meaningful inside one. ### Replaying a stream ```bash # replay from the first chunk, then follow it live if it is still running sar requests get $ID --namespace prod --stream # pick up after the last chunk you already have sar requests get $ID --namespace prod --stream --from 25 ``` `--from` takes the sequence number of the last chunk in hand and is the CLI's `Last-Event-ID`. It requires `--stream`, since there is nothing to resume in a whole document. ## Everything else | Command | Does | |---|---| | `register` | Create a tenancy and its owner from a registration token | | `login` | Sign in, opening a session or taking an API key | | `password` | Replace the password this account signs in with | | `whoami` | Show what the credential in hand stands for | | `logout` | End the session this key opened | | `keys` | Mint, list and end API keys | | `users` | Invite people into the tenancy and see what they hold | | `ownership` | Move ownership of the tenancy | | `namespaces` | List and create namespaces | | `grants` | Read and write what a member may do in a namespace | | `backends` | Configure the providers a namespace sends to | | `rules` | Decide which backend a model name reaches | | `submit` | Send a body and take the provider's answer on the connection | | `requests` | `submit` without holding a connection, and `get` one back | | `usage` | Report what a namespace has spent | | `storage` | Report what a namespace is holding | `sar --help` has the arguments for each. When something is refused and you are not sure why, `sar whoami` says which tenancy the credential opens and what it holds. Most `403`s are answered by it.