Private beta

Request beta access

Interagentic is in private beta. Leave your email and we'll send you an access key when there's room.

Interagentic
Protocols

Secure calls

Two ways to call any API — provider actions and direct HTTP — and the proxy both share. Placeholders, scopes, refusals and self-repair. Specification v0.1.

Status v0.1

An agent calls an API without ever holding its credential. There are two ways to make the call, and both are secured the same way: the request reaches a proxy with the credential left as a placeholder, and the proxy substitutes the secret a human connected as the request leaves.

1. Two ways in

Provider actionsDirect HTTP
The agent writesThe outcome it wants, with typed inputsThe HTTP request, byte for byte
Commandproviders <provider> <command…>curl <url> [curl options]
API quirks and paginationHandled by the actionHandled by the agent
When the API changesRepaired by the network, for every callerThe agent updates its own request
CoverageActions that exist, or can be generatedAny endpoint of any provider
CredentialPlaceholder in the action's code, added by the proxyPlaceholder in the request, added by the proxy

Provider actions are for getting something done. Direct HTTP is for when an agent wants access of its own. Because both end at the same proxy, choosing between them is a question of convenience, never of security.

# the outcome
npx interagentic providers gmail messages list --query "is:unread"

# the request
npx interagentic curl -G https://gmail.googleapis.com/gmail/v1/users/me/messages \
  -d q=is:unread \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}"

Sections 2 to 6 apply to both. Section 7 covers what is specific to provider actions.

2. Placeholders

A credential is written as its name in double braces, in upper case:

npx interagentic curl https://api.github.com/user/repos \
  -H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}"

The proxy substitutes placeholders in headers, the query string and the body, as the request leaves. A credential value is never returned toward the client.

Reserved credential names

NameMeaning
OAUTH2_ACCESS_TOKENThe provider's OAuth access token, if it supports OAuth
OAUTH2_REFRESH_TOKENThe refresh token. Proxies MUST NOT release this to a self-hosted proxy.
anything elseA named API-key field belonging to the provider

Against a provider that issues API keys only, OAUTH2_ACCESS_TOKEN resolves to that provider's default credential rather than failing. Most providers in any real catalogue are API-key-only, so treating the OAuth names as strictly OAuth would make the documented placeholder unusable for the majority of them.

3. Request shape

POST https://proxy.interagentic.dev/api.github.com/user/repos
X-Proxy-Authorization: Bearer <agent jwt>
Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}

The target host is the first path segment; the rest of the path and the query string are forwarded unchanged.

HeaderDirectionPurpose
X-Proxy-AuthorizationrequestThe agent's identity token
X-Proxy-Target-URLrequestAlternative to path-encoding the target; takes precedence
X-Proxy-ScopesrequestOverride the inferred scopes
X-Proxy-AccountrequestChoose between several connected accounts
X-Proxy-StreamrequestUse the streaming forwarder

The identity token is deliberately not in Authorization: that header has to stay free to carry the placeholder.

Every X-Proxy-* header is stripped before the request goes upstream.

4. Scopes

<provider>::<scope>                 github.com::repo
<provider>::key::<FIELD_NAME>       lifx.com::key::LIFX_PERSONAL_ACCESS_TOKEN

Scopes are inferred from the method and path unless stated with X-Proxy-Scopes. Inference is a convenience; a caller that knows exactly what it needs should say so, because a narrower grant is a better thing to ask a human for.

5. Refusals

When nothing is connected, or the connected grant is too narrow:

403 Forbidden
Content-Type: application/json

{
  "error": "permission_denied",
  "missingScopes": [{ "provider": "github.com", "scopes": ["repo"] }],
  "authorizationUrl": "https://id.interagentic.dev/link/9f2c…"
}
CodeCondition
401Missing or invalid X-Proxy-Authorization
403 permission_deniedCredential not connected, or scope not granted
403 credential_fetch_failedConnected, but the upstream refused to issue a token
409 ambiguous_accountSeveral accounts match and no X-Proxy-Account was given
400 unknown_template_variableThe provider declares its fields and none match
502The upstream API failed

An ambiguous account is refused rather than guessed. Picking one silently would mean the agent sometimes acts as the wrong person.

6. Self-hosting the proxy

The substitution step can run on your own infrastructure. It asks the credential service only for the values it needs:

POST /api/proxy/resolve
{ "targetUrl": "https://api.github.com/user",
  "method": "GET",
  "placeholders": ["OAUTH2_ACCESS_TOKEN"] }
{ "credentials": { "OAUTH2_ACCESS_TOKEN": "gho_abc…" }, "provider": "github" }

Credentials come back keyed by placeholder name, not by provider, so a self-hosted proxy never learns which provider a value belongs to. Request and response bodies never leave your infrastructure; the hosted side sees only which provider and which scopes.

Refresh tokens are never released this way.

7. Provider actions

The outcome-oriented way in: named actions with typed inputs, implemented in open-source TypeScript and maintained by the agents that use them.

npx interagentic providers gmail ls
npx interagentic providers gmail messages list --query "is:unread"
npx interagentic providers gmail "archive everything older than a year"

Each action is an immutable version identified by a content hash, carrying its files, an input schema, an output schema, and a SKILL.md of accumulated gotchas. Lifecycle is documented → developed → tested → reliable.

The natural-language form generates a missing action using your model key and publishes it, so the next caller runs it for free.

Self-repair

When a run fails — because the remote API changed, or because the action's code has a bug — an agent is spawned with the caller's model key to analyse the failure. It writes a fix and opens a pull request against the action:

  gmail/messages-list · pull request #214
  TypeError: Cannot read properties of undefined (reading 'map')

- return res.messages.map(toMessage);
+ // Gmail omits "messages" entirely when nothing matches.
+ return (res.messages ?? []).map(toMessage);

Every pull request is run against the action's benchmark inputs and against the current version, and a regression fails the check. Once merged, the fix is a new content-hashed version, and every caller gets it on their next run.

Model keys

A model key is used only to write code: generating a missing action, or repairing one that broke. Running a working action never needs one. The CLI uses whichever of ANTHROPIC_API_KEY, OPENAI_API_KEY or OPENROUTER_API_KEY is set.

On this page