Run any OpenAPI spec as an Agent optimized executable
npm install specli
Turn any OpenAPI spec into a CLI.
Compile this weather OpenAPI spec to an executable:
``sh`
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:
`sh`
opencode run 'Using ./out/weather what is the current weather in new york city'
`bash`
npm install -g specli
Or use directly with npx/bunx:
`bash`
npx specli exec ./openapi.json __schema
bunx specli exec ./openapi.json __schema
Run commands dynamically from any OpenAPI spec URL or file path. Works with both Node.js and Bun.
`bash`
specli exec
Examples:
`bashInspect available commands
specli exec ./openapi.json __schema
$3
Bundle an OpenAPI spec into a standalone executable. Requires Bun.
`bash
specli compile [options]
`Options:
| Option | Description |
|--------|-------------|
|
--name | Binary name (default: derived from spec title) |
| --outfile | Output path (default: ./out/) |
| --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 | Bake in a default server URL |
| --server-var | Bake in server variables (repeatable) |
| --auth | Bake in default auth scheme |Examples:
`bash
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-linuxBake in defaults
specli compile https://api.example.com/openapi.json \
--name myapi \
--server https://api.example.com \
--auth BearerAuth
`The compiled binary works standalone:
`bash
./dist/myapi users list
./dist/myapi users get abc123 --json
`CLI Shape
specli generates commands of the form:
`
[...positionals] [options]
`- resource: Derived from
tags[0], operationId prefix, or first path segment
- action: Inferred from HTTP method or operationId suffix
- Name collisions are disambiguated automaticallyUse
__schema to see the command mapping for any spec.Global Options
| Option | Description |
|--------|-------------|
|
--server | Override server/base URL |
| --server-var | Server URL template variable (repeatable) |
| --profile | Profile name |
| --auth | Select auth scheme by key |
| --bearer-token | Set Authorization: Bearer |
| --oauth-token | Alias for --bearer-token |
| --username | Basic auth username |
| --password | Basic auth password |
| --api-key | API key value |
| --json | Machine-readable output |Per-Operation Options
Every operation command includes:
| Option | Description |
|--------|-------------|
|
--header | Extra headers (repeatable, Name: Value or Name=Value) |
| --accept | Override Accept header |
| --timeout | 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 | Inline request body |
| --file | Read request body from file |
| --content-type | Override Content-Type |Parameters
$3
Path parameters become positional arguments in order:
`
/users/{id}/keys/{key_id} →
`$3
These become kebab-case flags:
`
limit → --limit
X-Request-Id → --x-request-id
`Required parameters are enforced by the CLI.
$3
Array parameters are repeatable:
`bash
All produce ?tag=a&tag=b
specli ... --tag a --tag b
specli ... --tag a,b
specli ... --tag '["a","b"]'
`Request Bodies
$3
For JSON request bodies, specli generates convenience flags matching schema properties:
`bash
specli exec ./openapi.json contacts create --name "Ada" --email "ada@example.com"
`Produces:
`json
{"name":"Ada","email":"ada@example.com"}
`$3
Use dot notation for nested properties:
`bash
mycli contacts create --name "Ada" --address.city "NYC" --address.zip "10001"
`Produces:
`json
{"name":"Ada","address":{"city":"NYC","zip":"10001"}}
`Servers
Server URL resolution order:
1.
--server flag
2. Profile server setting
3. First servers[0].url in the specFor templated URLs (e.g.
https://{region}.api.example.com):`bash
specli ... --server-var region=us-east-1
`Authentication
$3
- 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$3
1.
--auth flag (explicit)
2. Profile authScheme setting
3. If operation requires exactly one scheme, use it
4. If spec defines exactly one scheme, use it$3
`bash
Bearer/OAuth2/OIDC
specli ... --bearer-token Basic auth
specli ... --username --password API key
specli ... --api-key
`Profiles
Store configuration for automation:
`bash
List profiles
specli profile listCreate/update profile
specli profile set --name dev --server https://api.example.com --auth bearerAuth --defaultSwitch default profile
specli profile use --name devDelete profile
specli profile rm --name devManage tokens
specli auth token --name dev --set "..."
specli auth token --name dev --get
specli auth token --name dev --delete
`Config location:
~/.config/specli/profiles.jsonOutput Modes
$3
- Success: Pretty JSON for JSON responses, raw text otherwise
- HTTP errors:
HTTP + response body, exit code 1
- CLI errors: error: , exit code 1$3
`json
// Success
{"status":200,"body":{...}}// HTTP error
{"status":404,"body":{...}}
// CLI error
{"error":"..."}
`$3
Prints equivalent curl command without sending the request.
$3
Prints method, URL, headers, and body without sending.
Programmatic API
Use specli as a library to execute OpenAPI operations programmatically:
`typescript
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);
}
`$3
| Option | Description |
|--------|-------------|
|
spec | OpenAPI spec URL or file path (required) |
| server | Override server/base URL |
| serverVars | Server URL template variables (Record) |
| 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) |$3
| 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 |$3
The
exec() method returns a CommandResult which is a discriminated union:`typescript
// Success
{
type: "success";
request: PreparedRequest;
response: {
status: number;
ok: boolean;
headers: Record;
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:
`typescript
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:
`typescript
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 actions
- help - Get details about a specific action
- exec - Execute an API callLimitations
- 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-token with pre-acquired tokens)Development
`bash
bun install
bun run build
bun test
bun run lint
bun run typecheck
``