Most developer-facing APIs ship with documentation, an OpenAPI spec, and a curl example. A meaningful percentage of them never ship a CLI, on the assumption that anyone serious will write their own client. This is a mistake. A good CLI is one of the highest-leverage things you can build for your developers, because it answers the question that documentation cannot: what does the happy path actually look like.
Reading docs is study. Running a CLI is doing. The cognitive distance between the two is enormous. A user who can paste one line into their terminal and see a working result is a different user from one who has to compose an HTTP request, set headers, encode a JSON body, and parse a response. The first user is on the path to becoming a paying customer. The second is on the path to closing the tab.
The four jobs of a CLI
A CLI for a SaaS API has roughly four jobs. The first is onboarding: getting an authenticated request to the API as quickly as possible after signup. The second is scripting glue: making it easy to compose your API into shell pipelines, cron jobs, CI/CD scripts, and Makefiles. The third is operations: providing direct, no-UI access to administrative actions for power users (creating tokens, rotating keys, exporting data). The fourth is discoverability: serving as a working example of how the API is meant to be used.
The first and last are the ones most teams underweight. They think of a CLI as a power-user tool, when in fact a CLI is one of the best onboarding surfaces you have. brew install yourtool && yourtool init is a much better second step after signup than "now write your first curl request."
Authentication: the friction tax
The single biggest CLI design decision is how it authenticates. There are essentially three patterns, in order of friction.
The lowest-friction pattern is the browser-based OAuth flow: yourtool login opens a browser, the user signs in to your dashboard, and a token is exchanged back to a local config file. This is what the GitHub, Vercel, and Stripe CLIs do. It works because users are already logged in to the dashboard, so there is no password entry. The cost is that you have to implement a local HTTP listener and handle the redirect.
The medium-friction pattern is the environment variable or config file: the user generates an API key in your dashboard, saves it to ~/.config/yourtool/config.toml or exports YOURTOOL_API_KEY, and the CLI picks it up. This is what most database CLIs and the AWS CLI do. It is slightly more friction but trivially scriptable.
The highest-friction pattern is to require --api-key=... on every command. Avoid this. It litters shell history with secrets and is annoying to script around.
The right design is to support all three: OAuth login as the first-run default, config file for persistent setups, and environment variable for CI. Read in that priority order, document the precedence clearly, and provide a yourtool config show that prints the resolved configuration without printing the secret itself.
Output design: the underrated detail
Output design is where most CLIs fail in subtle ways. The two main consumers of CLI output are humans (reading the terminal) and other programs (parsing it in pipelines). These two audiences want different things, and the CLI has to serve both.
The right pattern is structured output by default in a TTY, JSON or NDJSON when piped or when --json is set. The kubectl convention of -o for output format is a good baseline. Detect isatty(stdout) at startup and switch defaults accordingly. Print prettified output with column alignment and color when humans are reading; print machine-parseable JSON when something downstream is reading.
The error case is even more important. Failures must always exit non-zero (the AWS CLI famously does this wrong sometimes), must print errors to stderr not stdout, and must use a stable error format that scripts can grep for. Stack traces are not error messages; if your CLI ever prints a Python traceback to a user, you have failed.
Help text: the conversation
The CLI's help text is documentation that lives next to the user. yourtool --help should answer "what can I do," yourtool subcommand --help should answer "how do I do this specific thing," and both should include examples. The most useful CLIs treat --help as a small piece of writing, not as a generated parameter list. Stripe CLI does this beautifully; the stripe trigger help text reads like a tutorial paragraph.
A small but high-impact detail: every help message should include at least one realistic example. Not yourtool foo --bar <BAR> but yourtool foo --bar customer_42 --output json. Real values, real-looking commands. Users copy from help text more often than from documentation.
Distribution: pick the right channels
You will be tempted to ship only as a binary download. Resist this. The platforms developers actually use for installation, in rough priority order: brew (Mac), scoop/winget (Windows), npm/pip/cargo (language-local), curl | sh (everywhere). Single-binary downloads are an acceptable last resort but are not how most users install things.
Brew is the highest-leverage. It has automation tooling for SaaS-shipped CLIs (the GitHub Releases pattern with a Homebrew tap), and Mac is the dominant developer laptop. Set up a tap on day one. The setup is a few hours of work and dramatically expands your install surface.
What not to put in a CLI
Just as important as what to include: a CLI should not try to be a UI. Do not make it interactive (no spinners that block scripting, no menus, no read -p-style prompts unless explicitly in --interactive mode). Do not make it a wrapper around your dashboard's quirks; if a workflow is awkward in the API, fix the API first. Do not include features that only work for one customer.
The compounding effect
A CLI is one of those investments that looks small but compounds. Every blog post that mentions your product can include a one-line install command. Every tutorial can be copy-paste runnable. Every bug report from a sophisticated user comes pre-formatted because they used the CLI to reproduce. The best evangelists for your product end up shipping integrations, scripts, and tutorials that all start yourtool init. That is what a good CLI buys you.
We ship four developer APIs at DocuMint (PDF generation), CronPing (cron monitoring), FlagBit (feature flags), and WebhookVault (webhook debugging). All of them are accessed by curl today; CLI tooling is the next item on the list, and the pattern above is what we are building toward.