English | 简体中文
apic calls any HTTP API from the command line. It loads the API's
definition (OpenAPI 3.x / Swagger 2.0, or a few lines of simple YAML) and
builds commands from it, so you do not have to write a CLI for each service.
It also handles base URLs, environments and authentication.
It is designed for people and AI agents alike: stable JSON output on stdout, documented exit codes, write protection for production environments and secrets masked everywhere.
- Register a site from an OpenAPI / Swagger URL or file, or from a simple YAML definition
- Call operations by name (
apic pet list-users --page 2) or by raw path (apic pet -X POST /api/users -f name=tom) - Command names come from
operationId, or from the path when there is none (POST /back/cfg/get→back-cfg-get) - Separate environments (base URLs) and authentications; switch with
apic use - Auth types:
none,bearer,basic,apikey(header or query) andlogin(log in, cache the token per environment, re-login on 401) - Secrets live in
.envand are referenced as${env:NAME}; they are masked in-v,--dry-run, error messages andconfig show --dry-runprints the equivalentcurlcommand- Write protection:
protect: trueenvironments need--yesfor writes;allow_methodsrestricts an authentication to read-only methods - Shortcut commands:
apic link pet --name petctlcreates apetctlscript (.cmdon Windows) - Shell completion (bash, zsh, PowerShell) for sites, operations, parameters,
enum values and
--env/--auth, including shortcut commands --pick data.items.*.idextracts values from a JSON response;-o yamland-o tablereformat it, keeping numbers and field order intactapic skill petgenerates aSKILL.mdthat teaches an AI agent to use the siteexecauthentication: get the token from an external command (vault, SSO CLI…)- Overlays: rename, hide or fix operations and parameters in
overlays/<site>.yamlwithout touching the upstream spec - File uploads:
-F file=@./a.xlsxsendsmultipart/form-data
go install github.com/inhere/apic/cmd/apic@latestOr build from source:
make install # builds into dist/ and copies to $GOPATH/bin# register a site from an OpenAPI / Swagger document
apic site add pet --spec https://pet.example.com/swagger/doc.json
# the document has no host? give the base URL yourself
apic site add pet --spec ./swagger.json --base-url http://127.0.0.1:8080
apic ops pet # list operations
apic desc pet create-user # parameters, request body, response fields
apic pet list-users --page 2 # call by operation
apic pet create-user -f name=tom -f age:=3 # JSON body from fields
apic pet create-user -d @user.json # JSON body from a file (- for stdin)
apic pet -X GET /api/users -p page=2 # call by raw path
apic pet list-users --dry-run # print the curl command only-f key=value sends a string, -f key:=<json> sends a typed JSON value and
-f a.b=v builds nested objects. A parameter whose name clashes with a global
option is passed with -p name=value (or -p query.name=value).
The config dir is ~/.config/apic (override with APIC_CONFIG_DIR):
~/.config/apic/
config.yaml # your settings and sites (apic never rewrites it)
.env # secrets: KEY=VALUE; exported OS env vars win
sites/<name>.yaml # one site per file, written by `apic site add`
defs/<name>.yaml # simple definitions
state.yaml # current env/auth per site and generated shortcuts
cache/ # cached definitions and login tokens (0600)
A site with two environments and three authentications:
sites:
pet:
source: {type: openapi, url: https://pet.example.com/swagger/doc.json, ttl: 24h}
envs:
dev: {base_url: http://127.0.0.1:8080}
prod: {base_url: https://pet.example.com, protect: true}
default_env: dev
auths:
admin: {type: bearer, token: "${env:PET_ADMIN_TOKEN}"}
ro: {type: bearer, token: "${env:PET_RO_TOKEN}", allow_methods: [GET, HEAD]}
me:
type: login
login:
method: POST
path: /api/login
body: {username: "${env:PET_USER}", password: "${env:PET_PASS}"}
token: {from: body, path: data.token}
expires: {from: body, path: data.expires_in}
apply: {header: Authorization, value: "Bearer {token}"}
default_auth: meapic use pet --env prod --auth ro # switch the current selection
apic pet list-users --env dev --auth me # override for one request
apic auth status pet # login state (token never printed)An environment can declare the authentication it uses with envs.<env>.auth:
envs:
build: {base_url: http://127.0.0.1:18090, auth: build}
factory: {base_url: http://127.0.0.1:18080, auth: factory, protect: true}The auth for a call is chosen by --auth, then envs.<env>.auth, then the
auth saved by apic use, then default_auth. Give every environment of a
multi-environment site its own auth: an environment without one uses the
saved auth, so a saved choice can never send another environment's credential
to an environment that declares its own. apic use prints the source as
auth: factory (env) and a note when a saved --auth is shadowed by the
environment. Adding auth to an environment switches it to the token cache of
that auth name; a cache left under the old name is no longer used and can be
removed with apic auth logout <site> --env <env> --auth <old-name>.
Other behaviours of authentication:
- Dry run with unset variables:
--dry-runtolerates${env:NAME}that is not set in credential fields (token,password,value,exec.command,exec.env,login.body); it exits 0 and prints one stderr linewarning: dry-run: NAME is not set; a real call would failper variable. Real calls andapic auth statusstill fail. The placeholder is shown as***(header values), URL-encoded in a query string, or as<exec-token>/<login-token>; the warning is the source of truth. Other fields (such asusernameorallow_methods) with unset variables still fail. A dry run never runsexeccommands and never writes or removes a token cache. - 401 drops the token: when the retried request is still 401, the cached
token of a
login/execauth is deleted, soapic auth statusshows logged out and the next call logs in again.
For services with only a few endpoints (Jenkins, small internal tools), write a short YAML file instead of a Swagger document:
# defs/jenkins.yaml
operations:
job-info:
get: /job/{name}/api/json # {name} becomes a required parameter
summary: show job info
job-build:
post: /job/{name}/buildWithParameters
query: [BRANCH, ENV] # optional string query parameters
job-log:
get: /job/{name}/{build}/consoleText
output: raw
job-list:
get: /api/json
params:
- {name: view, in: query, enum: [all, failed, running]} # allowed valuesenum values are compared as strings: numbers and booleans are converted by
type (1.0 becomes "1", 0x10 becomes "16"), so quote a value to keep it
as written. The list must be non-empty with distinct, non-empty values.
apic site add jenkins --def ~/.config/apic/defs/jenkins.yaml --base-url https://ci.example.com
apic jenkins job-build --name demo --BRANCH mainA ready-to-copy read-only GitLab site (definition, site file with a
PRIVATE-TOKEN auth, sample commands) is in examples/gitlab.
auths:
sso:
type: exec
exec:
command: [/usr/local/bin/corp-sso, token, --audience, pet] # argv, no shell
env: {SSO_SECRET: "${env:PET_SSO_SECRET}"} # passed to the command
ttl: 30m # cache the token; without ttl/expires it runs on every request
# format: json, token: {path: data.token}, expires: {path: expires_in}
apply: {header: Authorization, value: "Bearer {token}"}The command runs without a shell, with an empty stdin, in the config directory
(relative paths are resolved from there) and is killed after timeout (30s).
Its argv never appears in any output. On a 401 the command runs again once.
Security: with
exec,config.yamlandsites/*.yamlcan make apic run commands, so treat them like scripts and do not import untrusted site files. On Windows the 0600 permission of the token cache is not enforced, and arguments with& | < > ^ % ! "are refused when the command is a.bat/.cmd.
When a generated command is awkward or a spec is wrong, write
<config dir>/overlays/<site>.yaml instead of editing the spec. It survives
site refresh and applies to calls, ops, desc, completion and apic skill.
operations:
"POST /back/cfg/get": # METHOD /path, as in the PATH column of `apic ops`
name: cfg-get
summary: get the print config
"POST /back/cfg/delete":
hide: true
users-list: # or the generated operation name
output: raw
body: multipart # json | form | multipart | xml | raw
params:
status: {required: true, enum: [active, banned]}
debug: {hide: true} # no option; -p debug=... is refused
tenant: {in: header, required: true} # new parameter: in is query or headerapic overlay init pet # writes overlays/pet.yaml with one empty entry per operation
apic overlay init pet --stdout
apic config check # reports overlay errors and keys that match nothing- A key that matches no operation only warns. A structural error (bad value,
name clash, hiding a path or required parameter, a new
Authorization/Cookie/Host/Content-*header…) makes every command of that site exit 1, exceptsite refresh/show/rmandoverlay init;config checkthen exits 1 too. descmarks changed fields with(overlay)and hidden parameters with(hidden);desc -o jsonadds anoverlayarray.hideis not access control: a hidden operation can still be called by its raw path, and a hidden parameter can still be sent with-Hor a raw path call. Useprotectandallow_methodsfor dangerous endpoints.overlayis a reserved name: a site calledoverlayis skipped with a warning.
apic pet import-users -F file=@./users.xlsx -F type=user
apic pet upload -F 'file=@./a.bin;type=application/zip;filename=report.zip'
apic pet upload -F 'file=@"./a;b.bin";filename=ab.bin' # quote paths containing ; or "
apic pet -X POST /upload -F file=@./a.txt # raw path call- Operations whose spec says
multipart/form-dataare sent as multipart; for others fix the body type with an overlay (body: multipart). - Only
-F name=@pathreads a file.-F name=valueand-f name=valueare text (-f name=@xsends the literal@x).-F name=@-and curl's-F name=<fileare not supported; a value starting with<is sent as text. typemust be a plaintype/subtype(no parameters); otherwise it is guessed from a builtin extension table, falling back toapplication/octet-stream. Names and file names cannot contain",\or control characters — pass;filename=when the path's last segment has them.-Fcannot be combined with-d,-for aContent-Typeheader. On a multipart operation,-dalone needs-H 'Content-Type: ...'.- Only regular files are read, after the write-protection check, and the whole
body is limited to 64 MiB (use curl for larger uploads).
--dry-runprints a curl command that sends the same parts;-vlists the parts without content.
Security:
-F name=@pathand-d @filesend local files.-d @fileis read before the write-protection check and has no size limit. On Windows a UNC path (\\host\share\...) authenticates to that host when it is opened.
apic link pet --name petctl # petctl list-users == apic --api pet list-users
apic link pet --name pet-prod --env prod --auth ro
apic link ls
apic link rm petctlScripts go to defaults.link_dir, ~/.local/bin by default (%USERPROFILE%\bin
on Windows). On Windows a .cmd file is also generated; cmd.exe re-parses
%*, so pass JSON bodies with -d @file there.
eval "$(apic --gen-completion bash)" # or zsh; PowerShell: apic --gen-completion pwsh | Invoke-Expression
eval "$(apic link ls --completion bash)" # shortcut commands; re-run after link / link rmCompletion only reads cached definitions: it never sends requests, logs in or
runs exec commands. Known limit: after an option value
(apic pet --env dev list-users --<TAB>) only site-level options are offered.
apic pet list-users --pick data.items.*.name # ["tom","amy"]
apic pet get-user --id 7 --pick data.name # tom (strings print bare)
apic pet list-users --pick data.items -o table
apic pet list-users -o yamlA path is dot separated: keys, array indexes (items.0) and * (every element;
missing fields become null, an empty array gives []). The last segment may
be a projection a,b, which builds an object from those keys in the given
order (missing keys are null): --pick data.items.*.id,name -o table prints
two columns. A projection needs an object; put .* before it for arrays.
--pick, -o yaml
and -o table apply to 2xx responses only; other responses print unchanged.
apic skill pet # writes <config dir>/skills/apic-pet/SKILL.md
skillc install -S ~/.config/apic/skills apic-petThe skill lists how to call the site, the exit codes, environment and
authentication names (never base URLs or credentials) and the operations
(grouped by tag when there are more than --max-ops). Review it before sharing.
stdout carries only the response body; everything else goes to stderr, so
apic ... | jq works. -i prints the status line and headers, -v the
request line and headers (secrets masked).
| Code | Meaning |
|---|---|
| 0 | 2xx / 3xx (redirects are not followed) |
| 1 | local error: config, definition, parameter validation |
| 2 | usage error: unknown option, command or operation |
| 3 | network error or timeout |
| 4 | HTTP 4xx |
| 5 | HTTP 5xx |
| 6 | refused by write protection |
| 7 | the request succeeded (2xx) but --pick / -o yaml|table failed — do not retry |
make test # go vet + unit tests
make lint # gofmt (+ staticcheck when installed)
make build # dist/apicDesign and plans: design, phase 1 plan.