OpenAPI

Turn an OpenAPI Spec Into a CLI Without Generating Code

OpenAPI can be more than SDK input or Swagger UI data. Restish loads API descriptions at runtime and turns them into shell-native commands with profiles, auth, output, pagination, and MCP-ready extension points.

By Daniel Taylor 9 min read
openapiclirestdevtools

OpenAPI is usually treated as an input to something else. Generate an SDK. Render Swagger UI. Publish reference docs. Feed a contract test. All of those are good jobs for a spec.

But there is another useful job hiding in the same document: teach the terminal how to work with the API.

That does not have to mean generating a new repository, choosing a language runtime, publishing a package, and keeping a client binary in sync with every operation change. A CLI can load an OpenAPI description at runtime, cache what it needs, and turn repeated API work into commands without making users rebuild a client.

That is the core bet behind Restish. It can still make a one-off HTTP request, but once an API publishes OpenAPI, Restish can turn that API into a small shell-native command group.

Try it as you read. Runnable examples below use the browser preview against the public api.rest.sh API. Local setup commands are shown as fenced shell snippets because they write Restish config on your machine.

Start With Plain HTTP

A good API CLI still needs the universal escape hatch: send a request to a URL. Restish does not require setup before it is useful.

Browser preview

Edit the command and run it from your browser against the live docs API.

Ready

That path matters because API work often starts messy. You paste a URL from a bug report, check a response header, inspect an error body, or try one endpoint before you know whether the API deserves local setup.

That is useful, but it is not the whole job. The moment you call the same API repeatedly, the URL, auth, parameters, output shape, and pagination rules start becoming a little client you keep reconstructing by hand.

OpenAPI already knows a lot of that.

Register The API Once

When an API publishes an OpenAPI document, connect it to Restish:

restish api connect example api.rest.sh
restish example --help

Restish discovers the spec, stores the API registration, and caches the description. The API name becomes a command group. After that, generated operations are available under restish example ....

The browser preview used in these docs has a built-in example mapping, so you can try the generated-command shape without writing local config:

Browser preview

Edit the command and run it from your browser against the live docs API.

Ready

That command is not a wrapper around a handwritten curl string. It comes from the API description. Restish knows the operation, parameters, path, response shape, and profile context, then runs the request through the same pipeline used by ordinary URL requests.

What The Spec Becomes

OpenAPI has many fields that look like documentation metadata until a CLI starts using them as interface design.

operationId becomes a command name:

paths:
  /items/{item-id}:
    get:
      operationId: getItem
      summary: Get one item

Restish turns that into:

restish myapi get-item alpha

Path parameters become positional arguments. Optional query, header, and cookie parameters become flags. Summaries and descriptions become help text. Schemas describe request bodies and parameter types. Servers influence where operations are sent. Security requirements tell Restish which credentials an operation can use.

Here is the anatomy of a generated command:

restish inventory update-item --dry-run item-123 'name: Travel mug, enabled: true'
Binary restish Restish itself The universal entry point.
API inventory Chosen during api connect Local operator vocabulary, not an OpenAPI field.
Operation update-item operationId: updateItem The operation ID becomes the command name.
Option --dry-run Optional query parameter dry_run Optional parameters become flags.
Argument item-123 Required path parameter item-id Required path parameters become positional arguments.
Body 'name: Travel mug, enabled: true' JSON request body schema Body input can come from shorthand, stdin, or a file.

The corresponding OpenAPI operation might look like this:

paths:
  /items/{item-id}:
    patch:
      operationId: updateItem
      summary: Update one item
      parameters:
        - name: item-id
          in: path
          required: true
          schema:
            type: string
        - name: dry_run
          in: query
          schema:
            type: boolean
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                enabled:
                  type: boolean

That means spec quality becomes CLI quality. Stable operation IDs, clear parameter names, accurate schemas, and honest auth metadata are not only good for docs. They make the generated command easier to discover, complete, and run.

When a spec needs CLI-specific polish, Restish supports targeted extensions:

x-cli-name: list-items
x-cli-aliases: [items]
x-cli-description: List items with optional filtering.

Those extensions should stay small. OpenAPI remains the source of truth for the API. Restish-specific hints are for command vocabulary, hiding confusing operations, auth setup hints, and other terminal UX details that the base spec cannot express cleanly.

Why Runtime Helps

Generated SDKs make sense inside applications. A service written in Go, TypeScript, Python, or Java usually wants typed calls, versioned dependencies, and application-local error handling. That is a different job.

A terminal workflow has different constraints:

  • users want the newest operation without waiting for a released client
  • commands need to compose with files, pipes, jq, grep, and CI scripts
  • auth and environment selection belong in local profiles
  • output has to be helpful for humans and boring for scripts
  • debugging often starts before anyone knows which language a real integration will use

Runtime generation fits that shape. You connect an API once, then sync the spec when it changes:

restish api sync example
restish example --help

There is still caching. Restish should not fetch and parse a large spec for every shell completion or every request. But the cached artifact is local operational state, not a generated client project that must be reviewed, published, installed, and eventually forgotten.

This also keeps API examples honest. Documentation can say:

restish example get-image jpeg > dragonfly.jpg

That example is both readable to a person and connected to the live API shape after sync. If the API publishes a better command name, updated schema, or new auth metadata, the CLI can pick it up from the spec instead of waiting for a new handwritten wrapper.

Output Is A Contract

Turning OpenAPI into commands only helps if the result behaves like a good CLI. The generated command should not dump surprising diagnostics into a script, add color to piped output, or corrupt a downloaded file because the tool tried to be helpful.

Restish v2 treats output as a product contract:

  • stdout carries the selected response data
  • stderr carries diagnostics, warnings, progress, and verbose traces
  • terminal output can be readable by default
  • redirected unfiltered responses preserve body bytes
  • explicit filters and formats produce structured output for the next program

For example, ask for one field from every paginated item:

Browser preview

Edit the command and run it from your browser against the live docs API.

Ready

The same rule applies to generated OpenAPI commands and plain URL requests. The API-aware layer should add names, help, auth, and request shaping. It should not break the shell contract underneath.

Auth Belongs With Profiles

OpenAPI command generation becomes much more useful when credentials are not pasted into every command.

Restish keeps repeated auth in profiles. A profile can hold API keys, bearer tokens, OAuth configuration, mTLS settings, external tool auth, base URLs, headers, query defaults, and OpenAPI credential bindings.

The practical difference is that this:

restish github list-issues --state open

can carry the right base URL, token source, TLS config, and operation-specific credential choice without turning every docs example into a wall of headers.

OpenAPI security matters here. Some operations are public. Some allow several auth alternatives. Some require a credential that should only be used for a subset of operations. Restish v2 treats operation security as request execution metadata, not as one global header pasted onto every generated call.

That is where a runtime CLI can feel more like a local tool than a copied HTTP example. Profiles remember the environment. The spec explains the operation. The command stays small enough to type.

The Same Source Can Feed Agents

OpenAPI-to-CLI is also a useful bridge to agent tools.

For humans, Restish turns OpenAPI operations into terminal commands. For MCP clients, the restish-mcp plugin can expose registered operations as tools:

restish mcp serve example

That does not mean every endpoint should be exposed to every model, user, or profile. Restish keeps MCP conservative by default: read-oriented tools are shown first, write tools require explicit opt-in, and operations can be allowlisted or hidden.

The important part is that both interfaces use the same source of truth. The OpenAPI document describes the API. Restish profiles carry local auth and environment choices. The request pipeline owns TLS, retries, timeouts, normalization, filtering, and output behavior.

Humans use the CLI. Agents use MCP. The API should not need a separate pile of handwritten glue for each one.

There is also a lighter-weight path when an agent is calling Restish through an ordinary command instead of MCP: render the response as TOON. TOON is a token-dense text encoding for JSON-shaped data, and Restish now supports it with -o toon:

Browser preview

Edit the command and run it from your browser against the live docs API.

Ready

That is not a replacement for filtering. The biggest savings still come from projecting the response down to the records and fields the agent needs, then using TOON when the remaining shape is a uniform list. Restish treats TOON as output-only, so JSON remains the format to use for request bodies and for workflows where the consumer expects standard JSON. The output formats reference covers the tradeoffs.

CLI-Friendly Specs

If you publish an API and want it to work well in tools like Restish, start with the ordinary OpenAPI basics:

  • publish the spec at a predictable URL such as /openapi.json
  • use stable, human-readable operationId values
  • write summaries that make sense as command help
  • give parameters clear names and accurate schemas
  • describe request and response bodies honestly
  • model auth per operation, including public operations
  • provide examples for tricky request shapes
  • expose pagination links or enough metadata for clients to proceed safely

Then test the terminal shape:

restish api connect myapi https://api.example.test
restish myapi --help
restish doctor api myapi

If the generated commands are awkward, the fix is often useful beyond Restish. Better operation IDs improve docs anchors. Better schemas improve SDKs. Better security metadata improves Swagger UI, contract tests, and agent tools.

That is the quiet advantage of OpenAPI as a shared interface contract. A spec that can teach a terminal usually teaches other tools better too.

Try It Locally

Install Restish:

brew install restish
restish --version

Or with Go:

go install github.com/rest-sh/restish/v2/cmd/restish@latest
restish --help

Connect the public example API:

restish api connect example api.rest.sh
restish example --help
restish example list-images

Useful next stops:

OpenAPI is not only for generated SDKs and browser docs. If the spec already knows the API, the terminal should be able to learn from it too.

Try Restish

Make any REST-ish API feel shell-native.

Run the browser tour for live examples, or install Restish locally to turn OpenAPI-described REST APIs into command-line workflows.

Run the tour Connect an API
$ brew install restish