Turn any OpenAPI spec into a CLI.
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 weatherAsk an agent what the current weather is:
opencode run 'Using ./out/weather what is the current weather in new york city'npm install -g specliOr use directly with npx/bunx:
npx specli exec ./openapi.json __schema
bunx specli exec ./openapi.json __schemaRun 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-runBundle 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 BearerAuthThe compiled binary works standalone:
./dist/myapi users list
./dist/myapi users get abc123 --jsonspecli 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.
| 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 |
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 |
Path parameters become positional arguments in order:
/users/{id}/keys/{key_id} → <id> <key-id>
These become kebab-case flags:
limit → --limit
X-Request-Id → --x-request-id
Required parameters are enforced by the CLI.
Array parameters are repeatable:
# All produce ?tag=a&tag=b
specli ... --tag a --tag b
specli ... --tag a,b
specli ... --tag '["a","b"]'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"}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"}}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- 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
--auth <scheme>flag (explicit)- Profile
authSchemesetting - If operation requires exactly one scheme, use it
- If spec defines exactly one scheme, use it
# Bearer/OAuth2/OIDC
specli ... --bearer-token <token>
# Basic auth
specli ... --username <user> --password <pass>
# API key
specli ... --api-key <key>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 --deleteConfig location: ~/.config/specli/profiles.json
- 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
// Success
{"status":200,"body":{...}}
// HTTP error
{"status":404,"body":{...}}
// CLI error
{"error":"..."}Prints equivalent curl command without sending the request.
Prints method, URL, headers, and body without sending.
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);
}| 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) |
| 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 |
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);
}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
- 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)
bun install
bun run build
bun test
bun run lint
bun run typecheck