Skip to content

Statable CLI

statable is the Stats API as a command. One static binary, no runtime, no configuration file it insists on. Every command prints and exits, so it composes with jq, awk, a cron entry or a CI step without anything having to be told it is not a terminal.

This page gets you to your first number. Commands is the full reference, Output and exit codes is what a script reads, and Configuration is how to point it somewhere else.

Install

brew install key-arg/tap/statable

The man pages and the bash, zsh and fish completions come with it, so man statable-top works straight away.

scoop bucket add statable https://github.com/key-arg/scoop-bucket
scoop install statable

For Windows, on x64 and Arm64. Every release updates the bucket itself, so it carries the same archives as the releases page.

go install github.com/key-arg/statable-cli/cmd/statable@latest
mise use -g "ubi:key-arg/statable-cli[exe=statable]"

The exe is required: ubi looks for a file named after the project, and the binary is statable.

docker run --rm -e STATABLE_API_KEY docker.io/statable/statable query \
  --metric visitors,pageviews --range 7d

The image is linux/amd64 and linux/arm64: the same signed binary on distroless, with no shell. A container has no keyring, so the key comes from STATABLE_API_KEY and the CLI never prompts.

An Arch package is built by the release pipeline and will be published as statable-bin, but it does not exist yet. AUR registration is closed while they deal with a wave of automated signups, so there is no account to publish it from. Arch and Omarchy users want go install or the archive until then.

There is no curl … | sh installer, on purpose. Archives, checksums and keyless signatures are on the releases page; cosign verify-blob against checksums.txt tells you which workflow and which commit produced what you downloaded.

Sign in

statable auth login

On a terminal this asks for a key and stores it in the system keyring. Anywhere else, a pipe, CI, an agent, it prints what to do next and exits rather than blocking on input nobody can type. For CI, set the key in the environment instead:

export STATABLE_API_KEY=stbl_...

The key is resolved from --key, then STATABLE_API_KEY, then the keyring, then a 0600 file. statable auth status always reports which of those the active key came from. If no keyring is available the command refuses and names --insecure-storage; it never quietly writes the key in the clear.

--key puts the secret in ps output and in your shell history, so the environment variable is the better habit.

On Windows the keyring is the Credential Manager and does protect the key. The --insecure-storage file does not: Windows maps a file mode onto the read-only attribute and nothing else, so a file written with 0600 is readable by every account on the machine. The command says so when it writes one.

No account or key yet? statable auth register --email you@example.com sends a code, and running it again with --code and --accept-terms opens the account and stores its first key. --accept-terms records that the account owner agreed to the Terms of Service, so pass it only after they have. On a terminal one run does both, and asks for the code and for that agreement. The same flow over HTTP is Register without a browser.

Read something

statable sites                       # what this key can read
statable sites use example.com       # remember a default

statable now                         # visitors active right now
statable stats --range 30d --compare previous_period
statable series --by day --range 30d
statable top pages
statable top countries --range month

top takes a short name, pages, sources, countries, browsers, goals, or a full dimension such as visit:utm_campaign. Run statable top with no argument to see the short names.

The rest of the surface:

statable props                       # custom property keys, and the event each belongs to
statable goals                       # the conversion goals defined on the site
statable snippet                     # the install tag, ready to paste
statable funnels                     # saved funnel definitions
statable funnel 45 --range 7d        # run one, step by step
statable subscription                # the plan state of the key's owner

statable goals lists the definitions; statable top goals reports how they converted. statable snippet prints the tag alone, so statable snippet | pbcopy puts something installable on the clipboard, and the script URL and site id are in the machine formats beside it.

Both are served from behind the API's write guard even though reading them needs only the read scope. On a deployment with the write surface switched off they answer 404 write_disabled, which reads as a missing site if you do not know that; the command says what is actually wrong instead.

Change something

The CLI covers the writing half of the API too: sites, goals, funnels, keys and all five settings groups can be created, changed and deleted.

statable sites create https://example.com
statable goals create --name Signup --event Signup
statable funnels create --name Checkout --step page:/pricing --step event:Signup
statable keys create ci --scope read
statable settings set countries --block RU

Four rules apply to everything that writes.

  • Deletes, revokes and rotations ask first. Without a terminal they need --yes, rather than blocking on a question nobody can answer.
  • goals edit and funnels edit replace. The goal or funnel becomes exactly what the flags say, and anything left out is cleared. sites edit is the exception: it sends only the flags you give.
  • List settings replace the whole list. Blocked IPs, countries, hostnames and tracking are sent in full, so emptying one is spelled --clear. The public dashboard takes --on or --off.
  • A minted secret is printed once. statable keys create ci --json | jq -r .token captures it, because the server never returns it again.

Anything the commands do not cover

Everything else goes through query, which is POST /query with flags:

statable query --metric visitors,pageviews --range 7d \
               --dimension time:day --filter country=US

Filters use a short syntax: = is, != is not, ~ contains, !~ does not contain. Repeating --filter combines with AND; commas inside one flag are OR.

Past that there is statable api call, which reaches any endpoint at all, including the ones that write. See Configuration.

Agent plugin

The repository is also an Agent Plugin, so a client that reads the standard picks up two skills built on the commands above:

SkillWhat it does
Analytics from the terminalTurns a question into the right command and pipes the JSON on to whatever reads it next
Traffic check in CIPuts a threshold in a pipeline step, so a deploy fails when visitors collapse or bounce rate climbs

The plugin carries no server of its own. Both skills drive the binary, so install it and sign in first.

To use it in Cursor, clone key-arg/statable-cli into ~/.cursor/plugins/local/statable-cli and run Developer: Reload Window. The skills then show up under Customize.

For an assistant that reads your analytics directly rather than through this binary, see AI assistants and MCP.

Where the numbers come from

Every command is the API underneath, so the Query reference and Endpoints reference describe exactly what each one returns. statable query is POST /query; statable top pages is that same call with one dimension and a default set of metrics.