CLI
outis ships with the Go SDK. It’s for scripts, CI steps and runbooks: anywhere a shell is easier than code.
go install github.com/outis-auth/outis-go/cmd/outis@latest
It reads the API key from OUTIS_API_KEY, and the API from OUTIS_BASE_URL if you’re not on https://api.outis.tech. Flags take a single dash.
Commands
Section titled “Commands”outis request -action deploy.production -requester keith -param repo=acme/api -param env=production -idempotency-key deploy-4f2a9c
outis get req-4f2a9c1b8d7e6f50
outis wait req-4f2a9c1b8d7e6f50 -timeout 5m
outis gate -action db.restore -requester keith -param db=payments -timeout 5m ./restore.sh payments
outis verify-webhook -header "Outis-Signature: t=...,v1=..." < body.json
outis hash -action deploy.production -param repo=acme/api
outis init worker -runtime go
| Command | What it does |
|---|---|
request |
Creates a request and prints it. -param k=v repeats. Also takes -summary, -quorum, -idempotency-key and -callback-url. |
get <id> |
Reads a request as it stands. |
wait <id> -timeout D |
Waits for a decision, at most 30 minutes. Exits 0 only if it was authorized. |
gate ... -timeout D command [args] |
Creates a request, waits, and runs the command only if it was authorized. See below. |
verify-webhook |
Checks a delivery on stdin against the secret in OUTIS_WEBHOOK_SECRET (-secret-env names another variable) and prints the event. Pass each header with -header "Name: value". |
hash -action A [-param k=v] |
Prints the operation hash of an action and its params. |
init worker -runtime R |
Writes a starter worker into the current directory (or -dir): go (the default) or temporal-go. Refuses to overwrite a file. Ask it for node or python and it prints that SDK’s own command. |
-json on request, get and wait prints the API’s own request object. outis <command> -h lists a command’s flags.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 |
Done. For wait and gate, authorized. |
1 |
Something went wrong talking to Outis. |
2 |
Bad usage. |
3 |
Not authorized: denied, expired or aborted. |
4 |
The timeout ran out with the request still pending. |
Once gate runs its command, it exits with the command’s own code. It exits 126 if the command can’t be run and 127 if it isn’t found.
outis gate asks for a request, waits up to -timeout, and runs the command only if the request was authorized. Everything after the first argument that isn’t a flag belongs to the command, so its own flags pass straight through. The command gets OUTIS_REQUEST_ID and OUTIS_OPERATION_HASH in its environment, and gate forwards SIGTERM to it.
outis gate -action db.restore -requester "$USER" -param db=payments -param snapshot=snap_0412 -timeout 10m ./restore.sh payments snap_0412
It’s a convenience, not a security boundary
Section titled “It’s a convenience, not a security boundary”Gate only enforces anything when the command can’t be run any other way. That means the privileged credentials exist only where gate runs:
- a CI job whose secrets are scoped to the step that calls gate
- a restricted runner nobody can shell into
- a Kubernetes job whose service account only that job gets
- a privileged execution service that only runs commands through gate
An operator who can run ./restore.sh directly has bypassed nothing. On a laptop, gate is a polite reminder. To make it hold, move the credentials into one of those four places, and have the command itself check the request before it does anything:
outis wait "$OUTIS_REQUEST_ID" -timeout 5s > /dev/null || exit 3
A decided request answers wait at once, and it exits 0 only if it was authorized.
In code, that check is assertAuthorized with OUTIS_REQUEST_ID, which also confirms the operation matches. See SDKs.
The timeout follows the same rule as wait on the SDKs’ guard: it’s required, and it’s capped at 30 minutes. If approval could take longer, gate is the wrong tool. Use durable execution.