Skip to content

OpenAPI and Plugins

The http/openapi skill is foxctl’s generic HTTP client for calling any OpenAPI 3.x REST API without code generation. It handles authentication, pagination, retries, and large response management automatically.

For APIs that need custom auth or pagination, foxctl supports out-of-process plugins that communicate via Protocol v1 JSON envelopes.

This page covers the OpenAPI skill path, spec management, built-in auth strategies, pagination, the plugin architecture, and error handling.

Instead of generating skill stubs for each API endpoint, the http/openapi skill reads an OpenAPI specification at runtime and invokes any operation by operationId. This single skill can call any OpenAPI 3.x API.

  • Zero-codegen API access — call any operation defined in an OpenAPI 3.x spec
  • Spec-as-memory — load specs from files, CAS artifacts, or named memories
  • Built-in auth — Bearer, API Key, Basic Auth, OAuth2 Client Credentials
  • Automatic pagination — Link headers, cursor-based, offset/limit
  • Smart retries — exponential backoff with jitter for 429/5xx errors
  • CAS integration — large responses stored as artifacts with inline summaries
  • Dry-run support — preview requests without making API calls
  • Plugin extensibility — custom auth and pagination via subprocess plugins

The skill accepts these input fields:

FieldRequiredDescription
specYesSpec location: file path, sha256:<hex>, or memory:<name>
operationIdYesTarget operation ID from the OpenAPI doc
paramsNoParameters organized by location: path, query, header, body
authNoAuth configuration: scheme selection and credential overrides
pagingNoPagination configuration: strategy, limits, field mappings
retryNoRetry configuration: backoff timing and max attempts
dry_runNoIf true, return request plan without executing

Example input:

{
"spec": "memory:github",
"operationId": "listReposForUser",
"params": {
"path": { "username": "octocat" },
"query": { "per_page": 100, "sort": "updated" }
},
"paging": { "strategy": "auto", "max_pages": 10 }
}

Successful responses follow the Protocol v1 envelope:

  • Small responses (< 32KB): body inline in data.body
  • Large responses (≥ 32KB): stored in CAS with data.artifact pointer and data.summary containing a preview
  • Error responses: inline error details with data.hint for remediation

Store OpenAPI specifications as named memories for easy reference:

Terminal window
# From local file
foxctl openapi import ./github-api.yaml --as=github
# From URL
foxctl openapi import https://api.github.com/openapi.json --as=github
# With strict validation
foxctl openapi import ./spec.yaml --as=myapi --strict

Importing validates the spec, stores it in CAS, and creates a named memory entry (memory:github → CAS digest).

Terminal window
foxctl memory list --type=openapi_spec
Terminal window
# Basic validation
foxctl openapi validate memory:github
# Strict validation (all refs resolved, no warnings)
foxctl openapi validate memory:github --strict

List all operations available in a spec:

Terminal window
foxctl openapi describe memory:github

Filter by tag:

Terminal window
foxctl openapi describe memory:github --tag=issues

Specs are cached in memory for 24 hours (configurable). Cache is invalidated on re-import or TTL expiry. To force a refresh, re-import the spec:

Terminal window
foxctl openapi import ./github-api.yaml --as=github
SchemeHTTP HeaderSetup
BearerAuthorization: Bearer <token>FOXCTL_BEARER_TOKEN env var
API KeyConfigurable header or query paramFOXCTL_API_KEY env var
Basic AuthAuthorization: Basic base64(user:pass)FOXCTL_BASIC_AUTH env var
OAuth2 Client CredentialsToken exchange → BearerFOXCTL_OAUTH2_CLIENT_ID + FOXCTL_OAUTH2_CLIENT_SECRET

Auth is auto-detected from the OpenAPI spec’s securitySchemes definition. You can override explicitly:

{
"auth": {
"scheme": "bearer",
"credentials": { "token": "ghp_..." }
}
}

Credentials are loaded from, in order of preference:

  1. Files under /run/secrets/<name> (container deployments)
  2. Environment variables (FOXCTL_* or scheme-specific names)
  3. Direct auth.credentials (testing only — not recommended for production)
  • All secrets are redacted as "***" in logs, error messages, and dry-run output
  • Never hardcode credentials in scripts or config files
  • Prefer environment variables or secret mounts over inline credentials

The skill auto-detects pagination strategy when paging.strategy is "auto" (default). It tries strategies in order:

StrategyHow it worksUsed by
Link headersRFC 5988 Link: <url>; rel="next"GitHub, GitLab
Cursor-basedBody fields like next_page_token, cursorStripe, Google, Slack
Offset/limitClassic page/per_page or offset/limit paramsTraditional REST APIs
Total-count heuristicStops when items < page sizeFallback
{
"paging": {
"strategy": "cursor",
"cursor_field": "pagination.next_token",
"max_items": 1000,
"max_pages": 10
}
}

Disable pagination (fetch one page only):

{ "paging": { "max_pages": 1 } }

All paginated responses include pagination metadata:

{
"pagination": {
"has_more": false,
"total_pages": 3,
"total_items": 247,
"strategy_used": "link"
}
}
SettingDefault
Retry codes429, 502, 503, 504
StrategyExponential backoff with jitter
Max attempts5 (including initial request)
Backoff schedule250ms → 500ms → 1s → 2s → 4s (±20% jitter)

The Retry-After header is always respected when the server provides it.

CodeHTTPMeaningFix
EOPENAPISpec errorValidate spec, check operationId
EARG400, 422Invalid argumentsCheck params, verify types
EAUTH401Auth failedVerify credentials, check expiry
EPAGINATIONPagination failureSpecify strategy manually
ERATELIMIT429Rate limit exceededWait for reset, reduce rate
ERUNTIME5xxServer errorRetry later, check API status
ENOTFOUND404Resource not foundVerify spec/memory name
ETIMEOUTRequest timeoutIncrease timeout, check network

Error responses include data.hint with actionable remediation. 4xx errors are kept inline (not artifactized). 5xx errors after retries include error.code="ERUNTIME" with any available request-id.

{
"retry": {
"base_ms": 500,
"factor": 2.0,
"max_attempts": 3,
"max_ms": 5000,
"retry_codes": [429, 503]
}
}

Preview and validate requests without making actual API calls:

{
"spec": "memory:github",
"operationId": "listReposForUser",
"params": { "path": { "username": "octocat" } },
"dry_run": true
}

Returns a request plan showing method, URL, headers (with secrets redacted), body, pagination config, and retry config. Useful for debugging and validation.

For APIs that need custom auth or pagination behavior, http/openapi supports out-of-process plugins. Plugins run as separate binaries and communicate via JSON envelopes on stdin/stdout.

TypeCommandPurpose
Authplugin/authSign requests, inject headers/body
Paginationplugin/paginationDetermine next page from response

Plugins are found via:

  1. FOXCTL_PLUGIN_PATH environment variable (colon-separated paths)
  2. openapi.plugin_path config setting
  3. Default: ~/.foxctl/plugins

Plugin naming convention: foxctl-plugin-<name> (e.g., foxctl-plugin-aws-sigv4).

Reference plugins by name in the auth config:

{
"auth": { "scheme": "plugin:aws-sigv4" }
}

Or via spec hints:

x-foxctl:
auth: plugin:aws-sigv4
auth_config:
service: s3
region: us-east-1

Plugins communicate through a simple stdin/stdout protocol:

  1. Parent spawns plugin process
  2. Parent writes request envelope to stdin, closes stdin
  3. Plugin reads request, processes, writes response to stdout
  4. Plugin exits with code 0 (success) or non-zero (error)
  5. Parent reads response envelope, enforces timeout/limits

The parent process enforces hard limits:

LimitDefaultMax
Wall time500ms5s
CPU time200ms2s
Memory64MB256MB
Input size128KB1MB
Output size32KB128KB

Plugins must not make network calls (unless explicitly documented), write to filesystem (except temp directories), or spawn child processes.

Input (parent → plugin): Contains the pending HTTP request (method, URL, headers, body), security scheme definition, credentials, and spec hints.

Output (plugin → parent): Returns modified headers and optional body transform.

Input (parent → plugin): Contains the last HTTP response, paging state, items fetched so far, and spec hints.

Output (plugin → parent): Returns continue (boolean), and either next_url, next_query, or next_cursor to construct the next request.

Pagination stops when the plugin returns continue: false, or when max_pages or max_items limits are reached.

Terminal window
# Import spec (once)
foxctl openapi import https://api.github.com/openapi.json --as=github
# Set auth
export FOXCTL_BEARER_TOKEN="ghp_..."
# Call API
foxctl run http/openapi \
--input '{
"spec": "memory:github",
"operationId": "listReposForUser",
"params": {"path": {"username": "octocat"}, "query": {"per_page": 100}},
"paging": {"max_pages": 5}
}'
Terminal window
foxctl run http/openapi \
--input '{
"spec": "memory:github",
"operationId": "createIssue",
"params": {
"path": {"owner": "user", "repo": "my-repo"},
"body": {"title": "Bug report", "body": "Description", "labels": ["bug"]}
}
}'
Terminal window
foxctl run http/openapi \
--input '{
"spec": "memory:github",
"operationId": "deleteRepo",
"params": {"path": {"owner": "user", "repo": "old-repo"}},
"dry_run": true
}'