Skip to content

apis/

API entries — each .yml file in apis/ is a list of API definitions loaded on world init. Each entry compiles into a typed gateway.<name>.* namespace inside execute_javascript / execute_python.

Trust tiers are normative. In a marketplace/untrusted realm, file://, process-environment fallback, ${VAR}/raw-header credential interpolation, query/path/body credentials, and directly networked credential-bearing MCP are rejected at load. Marketplace network access must use a host-vetted typed credential slot and structured auth profile through the gateway; if the provider cannot meet the deployment’s marketplace credential policy, that integration is unavailable in the marketplace tier. The token-env, custom-header interpolation, and credential-bearing MCP forms documented below are compatibility features for local or explicitly first-party/org-reviewed installations. Their trust tier is adoption-visible and they never become marketplace-safe merely because they run in Docker.

The typed credential slot is a key entry in a conventional realm and a declared credential the owner binds in a captured realm. See keys and declared credentials for which applies where.

apis/petstore.yml
- url: https://petstore3.swagger.io/api/v3/openapi.json
name: petstore
type: openapi
auth: api-key
token-env: PETSTORE_API_KEY
Field Required Notes
url yes Spec source. HTTP(S), file://, or a bare relative path resolved against the apis.yml file’s parent directory (used for vendored specs — see below).
name recommended Gateway namespace — gateway.<name>.*. Falls back to a slugified spec title if omitted. Always set this in published realms so the prompt examples work regardless of the spec’s info.title.
type no openapi (default) or graphql.
auth no none (default), bearer, api-key, oauth2. See Auth below.
token-env with bearer / api-key Env-var or credential-store key holding the token. Deprecated in favour of a key entry for the same variable, or of a declared credential in a captured realm.
headers no Custom HTTP headers; values support ${VAR} interpolation from credential store / env.
oauth2 with auth: oauth2 OAuth2 config — see OAuth2 below.
tags no Allowlist of OpenAPI tag names. Filters huge specs to a coarse subset.
operation-ids no Exact operationId allowlist. Composes with tags (tags pre-filter, operation-ids picks exact ops). Match is case-insensitive and treats -// as _, so repos/get, repos-get, repos_get all match.
capability-tags no Capability DECLARATION — what the API is FOR, in the host’s vocabulary (web-search, web-fetch). Lets host code pick a tool by capability instead of by provider name. Unrelated to tags; see Capability tags below.

capability-tags says what an API IS FOR, in a vocabulary the host defines — web-search (discovers information on the open web from a query), web-fetch (reads a URL it is given, and cannot discover one). Most tools are chosen by an LLM reading their descriptions, but some consumers have to choose without one in the loop: the host’s person research needs a search tool before any model is asked anything. A declaration is how such a consumer finds yours.

- url: https://api.example.com/search/openapi.json
name: example-search
capability-tags: [web-search]

Not tags. tags filters WHICH OPERATIONS of the spec become tools, using the spec’s own OpenAPI tag names. tags: [web-search] therefore filters the spec down to operations the provider happened to tag web-search — usually none, leaving the entry with no tools at all. The two fields do unrelated jobs and may both be set.

The host owns the vocabulary; realms supply the providers. A tag the host does not recognise is carried but matches nothing, so inventing one declares nothing. Only web-search and web-fetch are defined today.

Declare on every provider you ship, not just one. Selection is all-or-nothing per capability: once anything in the world declares web-search, only declarations count, and an untagged provider in the same realm stops being selected. Where nothing at all declares the capability the host falls back to matching tool names and logs that it did — so a realm that predates this field keeps working, but a realm that tags half its providers loses the other half.

The tag sits on the ENTRY, so it covers every operation that entry exposes. Where only part of a spec carries the capability, narrow the entry with tags / operation-ids — or split it into two entries, each with its own declaration.

url accepts a bare relative path. The loader resolves it against the file’s own parent directory, so realms can ship a hand-curated spec next to their apis.yml:

realm-hubspot/
└── apis/
├── apis.yml # url: hubspot-crm.json
└── hubspot-crm.json # the spec, vendored in-realm

Use this when the upstream provider has stopped publishing OpenAPI specs (or never did), or when you want to pin a specific subset of operations and types without depending on a moving public URL. HTTP(S) and file:// URLs work too — the relative-path mode is just the most ergonomic for vendored specs.

A realm’s producers call the realm’s own APIs. A producer’s operation: names an operation by its bare id, and another installed realm may ship an operation of the same id — two realms each vendoring Wikipedia’s page summary both expose pageSummary. A producer declared by a realm resolves to the operation that realm’s own apis/ (or handlers) supply, wherever the other realm’s sits, so each realm’s fetches go out through its own entry, with its own headers and credentials. Give the API entry a name of the realm’s own (bible_wikipedia_summary): API names are unique in a world, operation ids need not be. Only a producer whose realm supplies no such operation — or a world-authored producer, which belongs to no realm — can land on another realm’s copy; the host warns about that once, and a conversation bound to both realms refuses that fetch, naming both candidates.

Four auth modes plus the implicit headers-only path:

# 1. No auth
- url: https://api.example.com/openapi.yaml
auth: none
# 2. Bearer token (most REST APIs)
- url: https://api.github.com/openapi.json
name: gh
auth: bearer
token-env: GITHUB_PERSONAL_ACCESS_TOKEN
# 3. API key (sent as the header/query the spec declares)
- url: https://petstore3.swagger.io/api/v3/openapi.json
auth: api-key
token-env: PETSTORE_API_KEY
# 4. Custom headers only — for APIs that need multiple auth headers
# (e.g. RapidAPI). No `auth:` field needed.
- url: https://weatherapi-com.p.rapidapi.com
type: openapi
headers:
X-RapidAPI-Key: "${X_RAPIDAPI_KEY}"
X-RapidAPI-Host: weatherapi-com.p.rapidapi.com
# 5. OAuth2 — see next section

Credential references resolve through a host binding keyed by world, context, principal, connection, and grant revision, with context access checked on every call. In the local/first-party compatibility tier, token-env and ${VAR} may then resolve from that world’s credential store (set via set NAME = ... in chat or via the admin UI), followed by the process environment. Process fallback is unavailable in shared multi-world or untrusted marketplace deployments. Missing credentials mean the entry is skipped at world load with a logged warning; the API never appears in the gateway. A captured realm’s API operations read only the owner’s wallet, through the credential the owner bound, and never fall back to the process environment.

For providers that use the OAuth2 authorization-code flow (HubSpot, Slack, Salesforce, GitHub, Google, etc.). The pack ships only the provider facts (URLs, scopes, identity introspection). Per-deployment client app credentials live in the host’s org vault, keyed by provider name — never in the pack repo and never in any user’s workspace. (The host admin file oauth-apps.yml is retired; credentials are no longer read from disk.)

realm-hubspot/apis/apis.yml
- url: hubspot-crm.json
name: hubspot
type: openapi
auth: oauth2
oauth2:
auth-url: https://app.hubspot.com/oauth/authorize
token-url: https://api.hubapi.com/oauth/v1/token
scopes: >-
crm.objects.contacts.read crm.objects.contacts.write
crm.objects.companies.read crm.objects.deals.read
identity:
url: https://api.hubapi.com/oauth/v1/access-tokens/{token}
method: GET
auth: path-token
account-id-field: hub_id
display-name-field: hub_domain

oauth2: block fields

Field Required Notes
auth-url yes Provider’s authorize endpoint.
token-url yes Provider’s token endpoint.
scopes usually Space-separated scope list.
client-id / client-secret NO in published packs Power-user fallback only — accepts ${VAR} interpolation. Production setups put these in the host’s org vault (or an individual user’s wallet, which takes precedence) so the pack stays public and credential-free.
identity optional Introspection block — see below. Without it the connect flow still completes, but the UI shows a generic label instead of the real account.

identity: block — provider-agnostic introspection

The identity: block tells the host how to call the provider’s /userinfo or /whoami endpoint and pull an accountId + display label out of the JSON response. This is what lets the UI show “Connected as alice@acme.com” rather than just “Connected”.

Field Required Notes
url yes Endpoint URL. May contain the literal {token} placeholder, substituted with the URL-encoded access token (used by HubSpot’s path-token style).
method no GET (default) or POST.
auth no How to send the token: bearer (default — Authorization: Bearer <token>), path-token (interpolated into {token}, no header), header:<NAME> (custom header), query:<NAME> (URL query parameter).
account-id-field yes Top-level JSON field name to read as the stable account identifier.
display-name-field no Top-level JSON field name for the human-readable label. Falls back to account-id-field if absent or blank in the response.

Examples:

# Google (bearer token, /userinfo)
identity:
url: https://www.googleapis.com/oauth2/v3/userinfo
account-id-field: email
display-name-field: name
# GitHub
identity:
url: https://api.github.com/user
account-id-field: login
display-name-field: name
# Slack
identity:
url: https://slack.com/api/auth.test
method: POST
account-id-field: user_id
display-name-field: user

Where the OAuth client credentials live (host-managed, never on disk)

client-id and client-secret for the provider’s Public App belong in the host’s org vault, keyed by provider name. The host manages them through its admin UI or REST; the exact surface is host-specific, but the key is always the provider name:

Terminal window
PUT /api/v1/admin/org-vault/provider/hubspot
{"client-id": "12345-abcdef-…", "client-secret": "…"}

The provider name (hubspot, slack, …) matches the name: field of the matching apis.yml entry.

Two properties a pack author can rely on:

  • Nothing is read from the filesystem. The old admin/oauth-apps.yml and its per-workspace override are retired, so a pack must never instruct an operator to write credentials to a file.
  • A user can override the deployment’s app with their own, by putting it in their personal wallet. Packs should not assume the org’s client app is the one in use — which matters for docs that tell a developer how to test against their own provider project.

Lookup order for client_id / client_secret:

  1. The user’s own wallet (per-user override — encrypted and self-service, replacing the retired per-workspace file)
  2. The deployment’s org vault (the default everyone gets)
  3. ${VAR} from the pack’s oauth2.client-id / client-secret (escape hatch for power users)

If none resolve, the provider’s status reports not-configured and Authorize returns an actionable error message instead of silently failing.

End-user UX

End users never paste tokens, IDs, or secrets. Settings → Connected Services → click Authorize → consent on the provider’s page → done. ConnectedAccounts holds the real account label; gateway.<name>.* is live in chat.

Token refresh is automatic — the host’s OAuth2Service rotates expired access tokens using the stored refresh token and writes back any new refresh token the provider issues (HubSpot rotates them on every refresh).