|
| 1 | +--- |
| 2 | +title: "Turn an OpenAPI Spec Into a CLI People Can Actually Use" |
| 3 | +description: "OpenAPI specs are not only for SDKs and docs. They can generate human-facing Python CLIs with typed flags, auth, table output, and nested request bodies flattened into command options." |
| 4 | +tags: ["openapi", "python", "cli", "fastapi", "developer-tools"] |
| 5 | +canonical_url: "https://github.com/shivaam/openapi-cli-gen" |
| 6 | +--- |
| 7 | + |
| 8 | +# Turn an OpenAPI Spec Into a CLI People Can Actually Use |
| 9 | + |
| 10 | +Most teams already have more API structure than they use. |
| 11 | + |
| 12 | +If you have a FastAPI app, a Swagger spec, or any OpenAPI 3.x document, the spec |
| 13 | +already knows your paths, methods, query parameters, path parameters, request |
| 14 | +bodies, auth schemes, enums, and validation rules. |
| 15 | + |
| 16 | +And yet, a lot of internal API workflows still end up as copied `curl` commands: |
| 17 | + |
| 18 | +```bash |
| 19 | +curl -X POST "$API_URL/users" \ |
| 20 | + -H "Authorization: Bearer $TOKEN" \ |
| 21 | + -H "Content-Type: application/json" \ |
| 22 | + -d '{"name":"Jane","email":"jane@example.com","address":{"city":"NYC","state":"NY"}}' |
| 23 | +``` |
| 24 | + |
| 25 | +That works, but it is not a great interface. |
| 26 | + |
| 27 | +It is easy to mistype. It is hard to discover. It asks humans to write JSON even |
| 28 | +when the schema already knows the fields. It also tends to sprawl across |
| 29 | +runbooks, Slack messages, CI jobs, and one-off scripts. |
| 30 | + |
| 31 | +I built [`openapi-cli-gen`](https://github.com/shivaam/openapi-cli-gen) to |
| 32 | +explore a narrower idea: |
| 33 | + |
| 34 | +> What if an OpenAPI spec could become a human-facing command line app? |
| 35 | +
|
| 36 | +## The Basic Idea |
| 37 | + |
| 38 | +Instead of making users paste JSON: |
| 39 | + |
| 40 | +```bash |
| 41 | +curl -X POST /api/users \ |
| 42 | + -H "Content-Type: application/json" \ |
| 43 | + -d '{"name":"Jane","address":{"city":"NYC","state":"NY"}}' |
| 44 | +``` |
| 45 | + |
| 46 | +generate commands like this: |
| 47 | + |
| 48 | +```bash |
| 49 | +mycli users create \ |
| 50 | + --name Jane \ |
| 51 | + --address.city NYC \ |
| 52 | + --address.state NY |
| 53 | +``` |
| 54 | + |
| 55 | +The OpenAPI spec provides the structure. The generated CLI exposes that |
| 56 | +structure as command groups, options, environment variables, and output formats. |
| 57 | + |
| 58 | +## Why Not Just Use OpenAPI Generator? |
| 59 | + |
| 60 | +OpenAPI Generator is excellent when you need SDKs, server stubs, models, or |
| 61 | +language-specific client libraries. |
| 62 | + |
| 63 | +That is not the same job. |
| 64 | + |
| 65 | +`openapi-cli-gen` is aimed at a smaller workflow: |
| 66 | + |
| 67 | +- give support teams repeatable commands; |
| 68 | +- give ops teams admin tools; |
| 69 | +- give QA teams setup and cleanup commands; |
| 70 | +- give API maintainers a quick internal CLI; |
| 71 | +- give users a scriptable wrapper around a REST API. |
| 72 | + |
| 73 | +The distinction is: |
| 74 | + |
| 75 | +```text |
| 76 | +SDK generator: produce code developers import. |
| 77 | +CLI generator: produce commands humans and scripts run. |
| 78 | +``` |
| 79 | + |
| 80 | +Both can be useful. They just serve different moments. |
| 81 | + |
| 82 | +## FastAPI Is A Natural Fit |
| 83 | + |
| 84 | +FastAPI publishes an OpenAPI spec at `/openapi.json` by default. |
| 85 | + |
| 86 | +That means a running FastAPI app can become a CLI without adding a separate API |
| 87 | +description file: |
| 88 | + |
| 89 | +```bash |
| 90 | +openapi-cli-gen generate \ |
| 91 | + --spec http://localhost:8000/openapi.json \ |
| 92 | + --name internal-admin |
| 93 | +``` |
| 94 | + |
| 95 | +Then: |
| 96 | + |
| 97 | +```bash |
| 98 | +cd internal-admin |
| 99 | +pip install -e . |
| 100 | +internal-admin --help |
| 101 | +``` |
| 102 | + |
| 103 | +For internal tools, this can be enough to turn an API into something support, |
| 104 | +ops, and QA can actually use. |
| 105 | + |
| 106 | +## Nested Request Bodies Are The Pain Point |
| 107 | + |
| 108 | +The easy part of an API CLI is path and query parameters. |
| 109 | + |
| 110 | +The annoying part is request bodies. |
| 111 | + |
| 112 | +FastAPI and Pydantic apps often use nested models: |
| 113 | + |
| 114 | +```python |
| 115 | +from pydantic import BaseModel |
| 116 | + |
| 117 | + |
| 118 | +class Address(BaseModel): |
| 119 | + city: str |
| 120 | + state: str |
| 121 | + |
| 122 | + |
| 123 | +class UserCreate(BaseModel): |
| 124 | + name: str |
| 125 | + email: str |
| 126 | + address: Address |
| 127 | +``` |
| 128 | + |
| 129 | +The generated command can expose nested fields as dot flags: |
| 130 | + |
| 131 | +```bash |
| 132 | +internal-admin users create \ |
| 133 | + --name Jane \ |
| 134 | + --email jane@example.com \ |
| 135 | + --address.city NYC \ |
| 136 | + --address.state NY |
| 137 | +``` |
| 138 | + |
| 139 | +For complex cases, JSON fallback is still available: |
| 140 | + |
| 141 | +```bash |
| 142 | +internal-admin users create \ |
| 143 | + --address '{"city":"NYC","state":"NY"}' |
| 144 | +``` |
| 145 | + |
| 146 | +That combination is important. Simple cases should feel simple. Complex cases |
| 147 | +should still be possible. |
| 148 | + |
| 149 | +## Runtime Mode vs Generated Packages |
| 150 | + |
| 151 | +Sometimes you just want to try a spec: |
| 152 | + |
| 153 | +```bash |
| 154 | +openapi-cli-gen run \ |
| 155 | + --spec https://catfact.ninja/docs \ |
| 156 | + --base-url https://catfact.ninja \ |
| 157 | + facts get-random |
| 158 | +``` |
| 159 | + |
| 160 | +Sometimes you want a real package: |
| 161 | + |
| 162 | +```bash |
| 163 | +openapi-cli-gen generate \ |
| 164 | + --spec https://api.example.com/openapi.json \ |
| 165 | + --name example-cli |
| 166 | +``` |
| 167 | + |
| 168 | +The generated package can be installed locally or shipped to users: |
| 169 | + |
| 170 | +```bash |
| 171 | +cd example-cli |
| 172 | +pip install -e . |
| 173 | +example-cli --help |
| 174 | +``` |
| 175 | + |
| 176 | +## Where This Helps |
| 177 | + |
| 178 | +Generated CLIs seem most useful for APIs with repeated operational workflows: |
| 179 | + |
| 180 | +- internal admin APIs; |
| 181 | +- FastAPI services; |
| 182 | +- self-hosted apps; |
| 183 | +- search and vector databases; |
| 184 | +- import/export tools; |
| 185 | +- media libraries; |
| 186 | +- provider sync/debug APIs; |
| 187 | +- QA and smoke-test endpoints. |
| 188 | + |
| 189 | +The wrapper does not need to replace an SDK. It gives people a terminal surface |
| 190 | +when a terminal surface is the right shape. |
| 191 | + |
| 192 | +## The Tradeoff |
| 193 | + |
| 194 | +Generated CLIs inherit the shape of the OpenAPI spec. |
| 195 | + |
| 196 | +If the spec has clean tags and operation IDs, commands can feel good quickly. If |
| 197 | +the spec has long generated operation IDs, the commands may need wrapper-level |
| 198 | +aliases for the most common workflows. |
| 199 | + |
| 200 | +That is still a useful starting point: |
| 201 | + |
| 202 | +1. generate the full API surface; |
| 203 | +2. verify real commands; |
| 204 | +3. add aliases only where humans actually need them. |
| 205 | + |
| 206 | +## Try It |
| 207 | + |
| 208 | +Install: |
| 209 | + |
| 210 | +```bash |
| 211 | +pipx install openapi-cli-gen |
| 212 | +``` |
| 213 | + |
| 214 | +Inspect a spec: |
| 215 | + |
| 216 | +```bash |
| 217 | +openapi-cli-gen inspect \ |
| 218 | + --spec https://petstore3.swagger.io/api/v3/openapi.json |
| 219 | +``` |
| 220 | + |
| 221 | +Generate a CLI: |
| 222 | + |
| 223 | +```bash |
| 224 | +openapi-cli-gen generate \ |
| 225 | + --spec https://api.example.com/openapi.json \ |
| 226 | + --name mycli |
| 227 | +``` |
| 228 | + |
| 229 | +Project: |
| 230 | + |
| 231 | +<https://github.com/shivaam/openapi-cli-gen> |
| 232 | + |
| 233 | +I am especially interested in real OpenAPI specs that make generated CLIs |
| 234 | +awkward: unusual auth, nested bodies, arrays of objects, multipart uploads, |
| 235 | +unions, or very large schemas. Those are the cases that make the tool better. |
0 commit comments