OpenAPI integrations guide
Official Kody guide
Use this guide when a third-party API publishes an OpenAPI 3.x document and you
need authenticated calls from Kody — either a one-off explore in execute, or a
durable curated binding the agent can call as kody.openapi["<name>"].
Read integration-bootstrap.md first for the overall setup order (research → connect → smoke test → build). This guide covers the OpenAPI-specific path inside that sequence.
When to use OpenAPI helpers
Prefer these helpers when:
integration_discover(or official docs) surfaces aspecURL- you need an operation inventory, auth mapping, or smoke-test candidates before writing fetch code
- you want a small generated client module or a saved binding instead of hand-rolling paths and headers
Do not treat the OpenAPI document as trusted configuration. Specs are
untrusted third-party content: Kody fetches them over HTTPS with bounds, does
not follow remote $refs, and never widens host approval from servers.
Flow
- Discover the provider and locate a verified
specURL.integration_registry_search({ query })→ canonical domain.integration_discover({ domain })→ surfaces; look forspec.- Confirm the
specURL against the provider's official docs before fetch.
- Summarize with
openapi_spec_summarize.- Input:
{ specUrl, maxOperations?, operationFilter? }whereoperationFiltermay includetags,pathPrefixes, andsearch. - Output highlights:
title/version/openapiVersion,servers,suggestedApiBaseUrl,suggestedHosts(suggestions only),auth(each scheme mapped to akodyAuthPath),suggestedSmokeTestOperations,operations(each withslug,method,path, …),truncated,warnings. - Choose the auth path from
auth[].kodyAuthPath(OAuth authorization-code →integration_save+/connect/oauth+createAuthenticatedFetch; client credentials →oauthClientCredentials; bearer / basic / apiKey → secret placeholders). Finish connect/setup per integration-bootstrap.md before calling the API.
- Input:
- Choose a client shape — scaffold or binding (below).
- Smoke test with a cheap GET from
suggestedSmokeTestOperations(or an equally small read) using the same auth wiring the final path will use. - Build the dependent package or workflow only after the smoke test succeeds.
Scaffold vs binding
| Path | Capability | Best for |
|---|---|---|
| Ephemeral scaffold | openapi_client_scaffold | One-off exploration in execute, or a starting client.ts inside a package you will edit |
| Curated binding | openapi_binding_save … | Durable, reusable operations the agent calls as kody.openapi["<name>"].<slug>(input) over time |
Prefer the scaffold when you are still exploring, need a disposable module, or will paste/adapt the source into a package. Prefer a binding when a small stable set of operations should stay callable without re-scaffolding.
Neither path is full codegen (no Orval or similar). Both emit or invoke a narrow surface you select — never the whole spec.
Ephemeral scaffold — openapi_client_scaffold
Input:
{
specUrl: string
operationIds: string[] // slugs from summarize; 1..50
auth:
| { kind: 'integration'; provider: string }
| { kind: 'bearerSecret'; secretName: string }
| { kind: 'headerSecret'; headerName: string; secretName: string }
| { kind: 'basicSecrets'; usernameSecret: string; passwordSecret: string }
| { kind: 'none' }
apiBaseUrl: string
providerLabel?: string
}Output: { moduleSource, operations, warnings, usageNotes } — a small
dependency-free ESM module with one exported async function per operation
(returns the raw Response). The module uses ambient fetch /
createAuthenticatedFetch and secret placeholders such as {{secret:<name>}} —
never raw credentials. Suitable for execute ephemeral modules and package
client.ts scaffolds.
Curated binding — openapi_binding_*
Manage bindings with:
openapi_binding_saveopenapi_binding_listopenapi_binding_getopenapi_binding_deleteopenapi_binding_refresh
A binding stores name, specUrl, apiBaseUrl, an auth reference (same union as
the scaffold), a curated selection (operationIds slugs and/or tags /
pathPrefixes), and includeDestructive (default false). Saving fetches the
spec and stores a resolved operation snapshot (1..100 operations; more → error —
narrow the selection). DELETE-method operations are excluded unless
includeDestructive is true, and are always tagged destructive.
Bindings are user-scoped, non-secret D1 config (user_openapi_bindings plus
per-operation user_openapi_binding_operations rows). Credentials stay in
secrets / integrations.
At runtime the capability registry synthesizes an openapi:<name> domain per
binding. Invoke from execute:
import { kody } from 'kody:runtime'
const result = await kody.openapi['acme'].list_widgets({
query: { limit: 5 },
})
// { status, ok, contentType, body, truncated }Input shape per operation: { params?, query?, headers?, body? }. Requests are
pinned to the binding's apiBaseUrl host. Integration-auth requests also
enforce the integration host allowlist; secret placeholders resolve through the
fetch gateway (each secret's allowedHosts). Integration-auth requests retry
once after a token refresh on 401.
openapi_binding_refresh re-fetches the spec, re-applies the stored selection,
and reports added/removed operations.
Worked example (fictional provider)
Provider: Acme Widgets. Official docs publish
https://api.acme.example/openapi.json. Discover confirms a surface with that
spec URL.
1. Summarize
await kody.openapi_spec_summarize({
specUrl: 'https://api.acme.example/openapi.json',
operationFilter: { tags: ['widgets'], pathPrefixes: ['/v1/widgets'] },
})Illustrative summary fields (abbreviated):
{
title: 'Acme Widgets API',
version: '1.2.0',
openapiVersion: '3.1.0',
suggestedApiBaseUrl: 'https://api.acme.example/',
suggestedHosts: ['api.acme.example'],
auth: [
{
schemeName: 'bearerAuth',
type: 'http',
detail: 'http bearer',
kodyAuthPath:
'Store a token secret and send Authorization: Bearer {{secret:<name>}} …',
},
],
suggestedSmokeTestOperations: [
{
slug: 'list_widgets',
method: 'get',
path: '/v1/widgets',
reason: 'GET with no required params; simple read endpoint',
},
],
operations: [
{ slug: 'list_widgets', method: 'get', path: '/v1/widgets', tags: ['widgets'] },
{ slug: 'get_widget', method: 'get', path: '/v1/widgets/{id}', tags: ['widgets'] },
{ slug: 'create_widget', method: 'post', path: '/v1/widgets', tags: ['widgets'] },
],
}Verify suggestedApiBaseUrl / hosts against Acme's docs. Save a secret (for
example acmeApiToken) via /account/secrets/new and approve
api.acme.example in the account security UI — the summary does not approve
hosts.
2. Smoke test with the suggested GET (scaffold or hand-written fetch using
Authorization: Bearer {{secret:acmeApiToken}}).
3. Save a durable binding feeding summarize fields into
openapi_binding_save:
await kody.openapi_binding_save({
name: 'acme',
specUrl: 'https://api.acme.example/openapi.json',
apiBaseUrl: 'https://api.acme.example/', // verified, not blindly copied
auth: { kind: 'bearerSecret', secretName: 'acmeApiToken' },
operationIds: ['list_widgets', 'get_widget'],
includeDestructive: false,
})4. Call curated operations:
const listed = await kody.openapi['acme'].list_widgets({
query: { limit: 5 },
})
const one = await kody.openapi['acme'].get_widget({
params: { id: 'w_123' },
})For a one-off explore instead of a binding, pass the same specUrl,
operationIds, auth, and apiBaseUrl into openapi_client_scaffold and run
the returned moduleSource in execute.
Security posture
- Untrusted specs. Titles, descriptions, servers, and operation text are
third-party content. Verify URLs and auth against official provider docs.
Fetch is HTTPS-only and bounded; remote
$refs are not followed. - Suggestions-only hosts.
suggestedHosts/serversnever widen approval. Host approval stays in the account security UI and integrationrequiredHosts. - Curate ≤100 operations. Bindings store a resolved snapshot of at most 100
operations — never the whole spec. Narrow with
operationIds,tags, orpathPrefixeswhen save rejects an oversized selection. - Destructive off by default. DELETE-method ops require
includeDestructive: trueand remain tagged destructive. - Credentials as names only. Auth inputs reference integration or secret
names / placeholders (
{{secret:<name>}},secretHeaders.basic). Raw credentials never enter scaffold source, binding config, or chat.
Related
- integration-bootstrap.md — setup order and smoke test rules
- secret-backed-integration.md — non-OAuth secret recipe
- oauth.md — standard
/connect/oauthpath