OpenAPI / Swagger
If your app serves an OpenAPI document, this is the source to use. It produces the cleanest tool names and the most accurate parameter schemas. For frameworks without a dedicated appctl sync --… target (e.g. Nest, Spring, Next with a spec), this is the usual path—see Choosing a sync source.
Fetching the document
Section titled “Fetching the document”- File path — pass a local
.jsonor.yamlpath; no network. - http(s) URL —
appctluses a normal GET with a clearUser-Agent(appctl/<version>), follows redirects, and sendsAccept: application/json, application/yaml, …. - Authenticated spec URL — pass the same header you use for tool calls:
appctl sync --openapi https://api.example.com/v3/api-docs --auth-header 'Authorization: Bearer env:STAGING_API_KEY'
The header is used both to download the spec and, unless overridden in config, inschemametadata for HTTP tools. Supported forms:Header-Name: literalHeader-Name: env:VAR(value is read from the environment at sync time)Authorization: Bearer env:VAR(expands toBearer <value>)
- Root base URL — if you pass a site root (path
/or empty) and the first GET returns 404,appctlalso tries:/openapi.json,/v3/api-docs,/v2/api-docs,/api-docs,/swagger.json,/api/openapi.json(common Spring / gateway layouts).
Prerequisites
Section titled “Prerequisites”- A running HTTP server that serves an OpenAPI 2 or 3 document. The document can be reachable at a URL (for example
/openapi.json) or saved as a file on disk. appctlinstalled. See Installation.
The demo in this repo
Section titled “The demo in this repo”examples/demos/openapi-fastapi/ is a FastAPI app with one endpoint. FastAPI generates the OpenAPI document automatically at /openapi.json.
1. Start the server
Section titled “1. Start the server”cd examples/demos/openapi-fastapipython3 -m venv .venvsource .venv/bin/activatepip install -r requirements.txtuvicorn main:app --host 127.0.0.1 --port 80002. Confirm the spec is being served
Section titled “2. Confirm the spec is being served”curl -s http://127.0.0.1:8000/openapi.json | head -c 80Real output:
{"openapi":"3.1.0","info":{"title":"appctl demo API","version":"1.0.0"},"paths"3. Sync appctl
Section titled “3. Sync appctl”In another terminal:
appctl sync --openapi http://127.0.0.1:8000/openapi.json \ --base-url http://127.0.0.1:8000 --forceReal output with appctl 0.2.0:
Synced Openapi: 1 resources, 1 tools written to .appctl4. Inspect what was generated
Section titled “4. Inspect what was generated”cat .appctl/schema.json | python3 -c \ "import json,sys; s=json.load(sys.stdin); \ [print(r['name'], [a['name'] for a in r['actions']]) for r in s['resources']]"Output:
widgets ['create_widget_widgets_post']5. Call it through chat
Section titled “5. Call it through chat”appctl chat "create a widget named Demo"This step requires a language model configured. See Installation.
Verify the tools are reachable
Section titled “Verify the tools are reachable”appctl doctorExpected:
tool method path HTTP verdictcreate_widget_widgets_post POST /widgets 200 reachablePass --write to mark reachable routes as provenance=verified in the schema.
What appctl does in this mode
Section titled “What appctl does in this mode”- Fetches the OpenAPI document, or reads it from disk.
- For each path and operation, creates one tool with JSON Schema parameters.
- Marks tools as
provenance=declared. This is the highest trust level because the server itself published the contract. - Reads
securitySchemesfrom the spec and picks the matchingAuthStrategy.
OpenAPI and protected (authenticated) APIs
Section titled “OpenAPI and protected (authenticated) APIs”appctl sync turns operations into tools with the parameters your spec advertises. It does not “log in as admin” to your app. You wire credentials into HTTP requests the executor makes.
Header-based auth (typical: Bearer, API key)
Section titled “Header-based auth (typical: Bearer, API key)”[target] auth_headerin.appctl/config.toml— a singleAuthorization(or other) header value sent on every HTTP tool call. Prefer storing secrets in the keychain or env and referencing them rather than pasting in chat.appctl sync ... --auth-header "Bearer ..."— bakes a header into the synced schema’smetadata.auth_header; the executor prefers that when building headers. Avoid committing long-lived tokens in the repo; useauth_headerin config for production.- Security schemes in the spec (when the sync can map them) set
schema.auth(bearer, API key, etc.) and read secrets from the env or keychain.
If your backend accepts Authorization: Bearer <token>, set [target] auth_header (or a mapped schema.auth bearer) so the LLM does not need the secret. It only chooses tools and body/query arguments the spec still exposes as parameters.
Query-based tokens (e.g. ?access_token=)
Section titled “Query-based tokens (e.g. ?access_token=)”If your OpenAPI file lists access_token (or similar) as a query parameter, it becomes a normal tool parameter. The model may ask you for a value, or you pass it in a prompt.
To supply a value without typing it in chat, use [target.default_query] in config.toml: a map of query names to values merged into every relevant HTTP call. Tool arguments from the model override these defaults for the same key. Values can be a literal or env:VAR to read from the environment, for example:
[target.default_query]access_token = "env:MY_API_ACCESS_TOKEN"Use appctl doctor to check reachability after you set the base URL and auth.
What the model does vs what appctl adds (so the agent can use auth)
Section titled “What the model does vs what appctl adds (so the agent can use auth)”The language model only chooses tool names and JSON arguments that match the generated tool schema. It does not “log in” to your app by itself.
| Layer | Who supplies it |
|---|---|
HTTP headers (Authorization, API keys, etc.) | appctl — from [target] auth_header, schema.metadata.auth_header (from sync), or schema.auth + env/keychain. Injected on every HTTP tool request by the appctl process before the call is sent. The model is not given your raw bearer string in the system prompt; it should call the tool and let appctl attach headers. |
| Query parameters in the spec | Model if it passes them in the tool call; appctl fills gaps from [target.default_query] (e.g. env:VAR) so optional tokens need not be typed in chat. If you configure default_query for a name, the model can omit that key and the runtime still sends it. |
| What to tell users | Configure target (and default_query for query-style APIs) before chat; then ask the model in natural language to list/call endpoints. If it still asks for a token, ensure default_query or header auth is set, or the tool will return 401/403 in the result. |
In short: auth for the target API is not “in the model’s head” — it is in appctl config and the OS env/keychain. The model’s job is to use tools; the executor’s job is to add configured credentials to each HTTP request.
Known limits
Section titled “Known limits”- Incomplete specs produce incomplete tools. Lint your spec with Spectral before syncing.
- If the spec is served behind login, save it to a file and pass the file path.
- Webhook and callback operations are ignored.