Turn any OpenAPI spec into a CLI.
Demo
Compile this weather OpenAPI spec to an executable:
npx specli compile https://raw.githubusercontent.com/open-meteo/open-meteo/refs/heads/main/openapi.yml --name weather
Ask an agent what the current weather is:
opencode run 'Using ./out/weather what is the current weather in new york city'Install
Or use directly with npx/bunx:
npx specli exec ./openapi.json __schema bunx specli exec ./openapi.json __schema
Commands
exec
Run commands dynamically from any OpenAPI spec URL or file path. Works with both Node.js and Bun.
specli exec <spec> <resource> <action> [args...] [options]
Examples:
# Inspect available commands specli exec ./openapi.json __schema # Machine-readable schema output specli exec ./openapi.json __schema --json # Minimal schema (best for large specs) specli exec ./openapi.json __schema --json --min # Run an operation specli exec ./openapi.json users list # Run with path parameters specli exec ./openapi.json users get abc123 # Preview the curl command without executing specli exec ./openapi.json users list --curl # Dry run (show request details without executing) specli exec ./openapi.json users list --dry-run
compile
Bundle an OpenAPI spec into a standalone executable. Requires Bun.
specli compile <spec> [options]
Options:
| Option | Description |
|---|---|
--name <name> |
Binary name (default: derived from spec title) |
--outfile <path> |
Output path (default: ./out/<name>) |
--target <target> |
Cross-compile target (e.g. bun-linux-x64) |
--minify |
Enable minification |
--bytecode |
Enable bytecode compilation |
--no-dotenv |
Disable .env autoload |
--no-bunfig |
Disable bunfig.toml autoload |
--server <url> |
Bake in a default server URL |
--server-var <k=v> |
Bake in server variables (repeatable) |
--auth <scheme> |
Bake in default auth scheme |
Examples:
# Compile with auto-derived name specli compile ./openapi.yaml # Creates: ./out/my-api # Compile with explicit name specli compile ./openapi.yaml --name myapi # Creates: ./out/myapi # Cross-compile for Linux specli compile ./openapi.json --target bun-linux-x64 --outfile ./out/myapi-linux # Bake in defaults specli compile https://api.example.com/openapi.json \ --name myapi \ --server https://api.example.com \ --auth BearerAuth
The compiled binary works standalone:
./dist/myapi users list ./dist/myapi users get abc123 --json
CLI Shape
specli generates commands of the form:
<resource> <action> [...positionals] [options]
- resource: Derived from
tags[0],operationIdprefix, or first path segment - action: Inferred from HTTP method or
operationIdsuffix - Name collisions are disambiguated automatically
Use __schema to see the command mapping for any spec.
Global Options
| Option | Description |
|---|---|
--server <url> |
Override server/base URL |
--server-var <name=value> |
Server URL template variable (repeatable) |
--profile <name> |
Profile name |
--auth <scheme> |
Select auth scheme by key |
--bearer-token <token> |
Set Authorization: Bearer <token> |
--oauth-token <token> |
Alias for --bearer-token |
--username <user> |
Basic auth username |
--password <pass> |
Basic auth password |
--api-key <key> |
API key value |
--json |
Machine-readable output |
Per-Operation Options
Every operation command includes:
| Option | Description |
|---|---|
--header <header> |
Extra headers (repeatable, Name: Value or Name=Value) |
--accept <type> |
Override Accept header |
--timeout <ms> |
Request timeout in milliseconds |
--dry-run |
Print request details without sending |
--curl |
Print equivalent curl command without sending |
For operations with request bodies:
| Option | Description |
|---|---|
--data <data> |
Inline request body |
--file <path> |
Read request body from file |
--content-type <type> |
Override Content-Type |
Parameters
Path Parameters
Path parameters become positional arguments in order:
/users/{id}/keys/{key_id} → <id> <key-id>
Query/Header/Cookie Parameters
These become kebab-case flags:
limit → --limit
X-Request-Id → --x-request-id
Required parameters are enforced by the CLI.
Arrays
Array parameters are repeatable:
# All produce ?tag=a&tag=b specli ... --tag a --tag b specli ... --tag a,b specli ... --tag '["a","b"]'
Request Bodies
Body Field Flags
For JSON request bodies, specli generates convenience flags matching schema properties:
specli exec ./openapi.json contacts create --name "Ada" --email "ada@example.com"
Produces:
{"name":"Ada","email":"ada@example.com"}Nested Objects
Use dot notation for nested properties:
mycli contacts create --name "Ada" --address.city "NYC" --address.zip "10001"
Produces:
{"name":"Ada","address":{"city":"NYC","zip":"10001"}}Servers
Server URL resolution order:
--server <url>flag- Profile
serversetting - First
servers[0].urlin the spec
For templated URLs (e.g. https://{region}.api.example.com):
specli ... --server-var region=us-east-1
Authentication
Supported Schemes
- HTTP Bearer (
type: http,scheme: bearer) - HTTP Basic (
type: http,scheme: basic) - API Key (
type: apiKey,in: header|query|cookie) - OAuth2 (
type: oauth2) - treated as bearer token - OpenID Connect (
type: openIdConnect) - treated as bearer token
Scheme Selection
--auth <scheme>flag (explicit)- Profile
authSchemesetting - If operation requires exactly one scheme, use it
- If spec defines exactly one scheme, use it
Providing Credentials
# Bearer/OAuth2/OIDC specli ... --bearer-token <token> # Basic auth specli ... --username <user> --password <pass> # API key specli ... --api-key <key>
Profiles
Store configuration for automation:
# List profiles specli profile list # Create/update profile specli profile set --name dev --server https://api.example.com --auth bearerAuth --default # Switch default profile specli profile use --name dev # Delete profile specli profile rm --name dev # Manage tokens specli auth token --name dev --set "..." specli auth token --name dev --get specli auth token --name dev --delete
Config location: ~/.config/specli/profiles.json
Output Modes
Default (Human Readable)
- Success: Pretty JSON for JSON responses, raw text otherwise
- HTTP errors:
HTTP <status>+ response body, exit code 1 - CLI errors:
error: <message>, exit code 1
--json (Machine Readable)
// Success {"status":200,"body":{...}} // HTTP error {"status":404,"body":{...}} // CLI error {"error":"..."}
--curl
Prints equivalent curl command without sending the request.
--dry-run
Prints method, URL, headers, and body without sending.
Programmatic API
Use specli as a library to execute OpenAPI operations programmatically:
import { specli } from "specli"; const api = await specli({ spec: "https://api.example.com/openapi.json", bearerToken: process.env.API_TOKEN, }); // List available resources and actions const resources = api.list(); // Get help for a specific action const help = api.help("users", "get"); // Execute an API call const result = await api.exec("users", "get", ["123"], { include: "profile" }); if (result.type === "success" && result.response.ok) { console.log(result.response.body); }
Options
| Option | Description |
|---|---|
spec |
OpenAPI spec URL or file path (required) |
server |
Override server/base URL |
serverVars |
Server URL template variables (Record<string, string>) |
bearerToken |
Bearer token for authentication |
apiKey |
API key for authentication |
basicAuth |
Basic auth credentials ({ username, password }) |
authScheme |
Auth scheme to use (if multiple are available) |
Methods
| Method | Description |
|---|---|
list() |
Returns all resources and their actions |
help(resource, action) |
Get detailed info about an action |
exec(resource, action, args?, flags?) |
Execute an API call |
CommandResult
The exec() method returns a CommandResult which is a discriminated union:
// Success { type: "success"; request: PreparedRequest; response: { status: number; ok: boolean; headers: Record<string, string>; body: unknown; rawBody: string; }; timing: { startedAt: string; durationMs: number }; } // Error { type: "error"; message: string; response?: ResponseData; // If HTTP error }
Type guards are available for convenience:
import { isSuccess, isError } from "specli"; if (isSuccess(result)) { console.log(result.response.body); }
AI SDK Integration
specli exports an AI SDK tool for use with LLM agents:
import { specli } from "specli/ai/tools"; import { generateText } from "ai"; const result = await generateText({ model: yourModel, tools: { api: await specli({ spec: "https://api.example.com/openapi.json", bearerToken: process.env.API_TOKEN, }), }, prompt: "List all users", });
The specli() function is async and fetches the OpenAPI spec upfront, so the returned tool is ready to use immediately without any additional network requests.
The tool supports three commands:
list- Show available resources and actionshelp- Get details about a specific actionexec- Execute an API call
Limitations
- OpenAPI 3.x only (Swagger 2.0 not supported)
- Array serialization uses repeated keys only (
?tag=a&tag=b) - OpenAPI
style/explode/deepObject not implemented - Body field flags only support JSON with scalar/nested object properties
- Multipart and binary uploads not implemented
- OAuth2 token acquisition not implemented (use
--bearer-tokenwith pre-acquired tokens)
Development
bun install
bun run build
bun test
bun run lint
bun run typecheck