How to build a CLI for your API without writing it from scratch (July 2026)

10 min read

Developers keep asking for a command-line tool. They want to script against your API, wire it into CI, and let coding agents call it without hand-rolling HTTP requests every time. So an engineer volunteers to build one, reaches for Cobra or Click, and three weeks later there's a CLI covering a third of the endpoints that already lags behind the API it wraps. Every new endpoint means another command written by hand, another flag, another release. The hard part of building a CLI for your API was never the argument parsing; it's keeping the tool complete and current as the API evolves. This guide walks through the design decisions a good API CLI has to make, then shows why generating it from your API specification, rather than writing it from scratch, is the approach that actually scales.

TLDR:

  • A CLI turns your API into a scriptable, pipeable tool that developers use in terminals, CI pipelines, and, increasingly, AI agents.
  • Hand-writing a CLI with a framework like Cobra, Click, oclif, or Clap works, but every endpoint, flag, and auth path becomes code you maintain by hand as the API changes.
  • The design work that matters is mapping endpoints to commands, wiring authentication, and formatting output for both humans and agents; the plumbing underneath is mechanical.
  • Generating the CLI from your OpenAPI spec produces a complete, consistent tool in one step and regenerates in seconds when the API changes, eliminating manual drift.
  • Fern generates a CLI, SDKs, and documentation from the same API definition, shipping a single statically linked binary with commands, auth, pagination, and JSON output built in.

Why your API needs a CLI

Well-designed APIs already ship SDKs and documentation, but a CLI covers a different set of jobs. It is the fastest way to explore an API interactively, the natural fit for shell scripts and CI pipelines, and the interface most developers reach for when automating a workflow. The best developer tools prove the point: kubectl, the GitHub CLI (gh), and the Stripe CLI are how many developers experience those platforms day to day.

There's a newer reason too. AI coding agents operate a terminal far more reliably than they navigate a REST client, and a CLI that emits structured JSON gives them a deterministic, composable way to call your API. A tool where one command's output pipes cleanly into the next is exactly what an agent needs, which makes a well-built CLI part of your agentic experience, not just your developer experience.

Two ways to build a CLI for your API

There are two fundamental paths, and choosing between them determines how much of the work below you do by hand.

Hand-writing means picking a CLI framework and coding each command yourself. The mature options are strong: Cobra for Go (it powers kubectl and gh), Click and Typer for Python, oclif for Node/TypeScript, Clap for Rust, and Commander.js for quick JavaScript tools. They handle argument parsing, help text, and subcommands well. What they don't do is know anything about your API, so every endpoint, parameter, auth scheme, and pagination rule is code you write and then maintain forever.

Generating means deriving the CLI from your API definition. Because your OpenAPI spec already describes every endpoint, parameter, and security scheme, a generator has everything it needs to produce a complete tool without you writing per-command code. The sections below walk through the design decisions either way, because a generator still encodes these choices; the difference is whether you implement them once by hand or configure them once and regenerate.

Step 1: Start from your API specification

A CLI is a projection of your API, so the API definition is the right starting point whether you generate the tool or build it by hand. An OpenAPI specification already enumerates your resources, operations, request and response schemas, and security schemes. That is the same source of truth you use for SDKs and reference docs, and reusing it keeps the CLI consistent with them from day one.

If you are hand-writing the CLI, the spec is your checklist of commands to implement. If you are generating it, the spec is the direct input. Either way, treating the spec as the contract means the CLI can never describe an endpoint that doesn't exist or miss one that does, provided you keep regenerating from it.

Step 2: Map endpoints to commands and subcommands

The core design decision is how your API's shape becomes a command tree. The convention that reads well is resource-plus-action: group commands by resource, then expose operations as subcommands. GET /users/{id} becomes users get <id>, POST /users becomes users create, and DELETE /users/{id} becomes users delete <id>. Path and query parameters become flags (--limit, --status), and request body fields become flags or a --data payload.

Consistency is what makes a CLI feel learnable: the same verbs across every resource, predictable flag names, and nesting that mirrors the API's own structure. Hand-writing this means keeping that mapping disciplined across dozens of commands as different people add them. A generator derives the mapping directly from the spec, so every endpoint follows the same rule automatically and new endpoints appear as commands the moment they're added to the definition.

Step 3: Wire up authentication

Almost every API requires auth, and a CLI has to handle it without forcing developers to paste a token into every command. Read the security schemes from your spec (API keys, bearer tokens, OAuth 2.0) and support the standard pattern: a login or configure command that stores credentials, an environment variable override for CI, and a per-command flag as an escape hatch.

The subtlety is doing this consistently and securely: credentials belong in a config file with sane permissions or the OS keychain, never echoed to the terminal or baked into shell history. When the CLI is generated from a spec that declares its security schemes, the auth flow is wired in from that declaration rather than reimplemented per command, which removes a common class of inconsistency between endpoints.

Step 4: Design output for humans and agents

Output format is where modern CLIs earn their keep, because they now serve two very different consumers. Human developers want readable, colored, tabular output and clear error messages. Scripts and AI agents want structured, deterministic JSON they can pipe into jq or into the next command.

The pattern that satisfies both is human-readable output by default with a --json (or --output json) flag for machine consumption, or JSON-first output with formatting layered on top. Either way, exit codes should be meaningful (non-zero on failure) and errors should be structured, not just printed prose. Designing for agents specifically means predictable schemas and no interactive prompts in the machine path, so one call composes cleanly into the next.

Step 5: Handle pagination, retries, and errors

The details that separate a toy CLI from a production one are the same ones that make SDKs hard to hand-write. Paginated endpoints should offer a way to auto-follow pages rather than making developers manage cursors manually. Transient failures should retry with exponential backoff instead of failing on the first 429 or 503. Rate-limit responses, timeouts, and network errors should surface as clear, actionable messages with the right exit code.

Written by hand, each of these is real work repeated per command. This is precisely the logic a generator can implement once and apply uniformly, because the spec already declares which endpoints paginate and how.

Step 6: Distribute and version the CLI

A CLI is only useful if developers can install it easily. The distribution channels that cover most environments are Homebrew for macOS and Linux, npm for Node ecosystems, Scoop for Windows, a one-line curl installer, and prebuilt binaries on GitHub Releases. Shipping a single statically linked binary with no runtime dependencies is the smoothest option, since developers don't need a specific language runtime installed to use it.

Versioning matters as much as packaging. The CLI's version should track your API, follow semantic versioning, and signal breaking changes clearly. Signed release binaries and shell completions round out a professional distribution story. Maintaining all of this by hand across five channels is a release process in itself; generating and publishing it on every spec change folds it into your existing pipeline.

Step 7: Keep the CLI in sync as your API evolves

This is the step that quietly defeats hand-written CLIs. An API is never finished, and every added endpoint or renamed field is a change the CLI has to reflect. Do it manually and the tool drifts: commands go missing, flags fall out of date, and developers stop trusting it. The same API drift that plagues hand-maintained docs and SDKs hits CLIs just as hard.

Regenerating from the spec is what removes the drift. When the CLI is derived from the same definition as your SDKs and docs, a spec change regenerates all three together in seconds, and the CLI stays a faithful projection of the current API rather than a snapshot of what it looked like when someone last had time to update it.

Building a CLI from your spec with Fern

Fern's CLI generator takes an OpenAPI spec (or a GraphQL introspection schema) and produces a fully functional command-line tool as a single statically linked Rust binary with no runtime dependencies. Endpoints map to commands and subcommands, parameters become flags, and authentication is wired in from the spec's security schemes, with pagination, retries, and output formatting handled automatically. Output is structured JSON for agents and scripts, with colored help, tabular formatting, and shell completions for human developers. Fern publishes the binary to Homebrew, npm, Scoop, and GitHub Releases on every release, and because the CLI comes from the same API definition as Fern's SDKs and documentation, a spec change opens pull requests with updated CLI source so all three stay in sync.

Final thoughts on building a CLI for your API

The decision that shapes everything is whether you hand-write the CLI or generate it from your API definition. Hand-writing gives you full control and turns every endpoint, flag, and release into code you maintain by hand as the API changes. Generating from the spec encodes the same design decisions — command structure, auth, output, pagination — once, then keeps the tool complete and current as the API evolves. For any API that ships more than a handful of endpoints and expects to keep growing, generation is the approach that keeps the CLI trustworthy without a standing maintenance cost. To generate a CLI, SDKs, and docs from one spec, book a demo.

FAQ

How do you build a CLI for a REST API?

Start from your API specification, then map each endpoint to a command and subcommand (for example GET /users/{id} becomes users get <id>), turn parameters into flags, and wire authentication in from the spec's security schemes. Add pagination, retries, and both human-readable and JSON output, then distribute the tool through channels like Homebrew, npm, and GitHub Releases. You can implement each of these by hand with a framework like Cobra or Click, or generate the whole tool from your OpenAPI spec.

What is the best framework to build a CLI?

It depends on your language: Cobra is the standard for Go and powers kubectl and gh, Click and Typer are popular for Python, oclif for Node/TypeScript, and Clap for Rust. These frameworks handle argument parsing and help text well, but they don't know your API, so you still write and maintain a command for every endpoint. Generating the CLI from your API spec avoids that per-command work entirely.

Should I hand-write a CLI or generate it from OpenAPI?

Hand-write it if you have a small, stable API or need behavior that doesn't map cleanly to your spec. Generate it if your API has many endpoints or changes often, because a generator produces a complete, consistent tool in one step and regenerates in seconds when the spec changes, eliminating the drift that makes hand-written CLIs go stale.

How do you make a CLI usable by AI agents?

Emit structured JSON so agents can parse and pipe output deterministically, use meaningful exit codes, return structured errors, and avoid interactive prompts in the machine path. A CLI that is JSON-first by default, with human formatting available as an option, gives agents a composable way to call your API where one command's output feeds cleanly into the next.

How do you keep a CLI in sync with your API?

Derive the CLI from the same API definition you use for SDKs and documentation, and regenerate it whenever the spec changes. Generating from a single source of truth means added endpoints, renamed fields, and new auth schemes flow into the CLI automatically, rather than requiring someone to hand-edit commands and inevitably fall behind.