Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

OpenAI Agents API

Attach Anlyon to the OpenAI Responses API or Agents API as a remote MCP tool server, and have a named reviewer decide gated actions

OpenAI’s Responses API and Agents API can connect to a remote MCP server as a tool source. Anlyon’s MCP server works as that server: the model sees your workspace’s actions as tools, calls one, and Anlyon executes it against production systems.

The statements about OpenAI’s APIs on this page come from OpenAI’s documentation as checked on 2026-09-30. Check OpenAI’s MCP tool guide for the current behaviour.

Your agent declares what it wants to do. Anlyon executes it against production systems. The agent names an action and passes input, and Anlyon resolves the credentials at dispatch and makes the request. That is true of actions routed through Anlyon’s hosted executor. It is not true of a tool your own code calls directly, which is not routed through Anlyon and is not governed by it.

When OpenAI’s approval switch is enough

OpenAI’s Agents API and Responses API can pause an MCP tool call for approval. The pause is answered by your own code. Anlyon routes the call to a named reviewer, executes the reviewed request with credentials bound to that action, and records the outcome. Use OpenAI’s switch when one developer is the reviewer. Use Anlyon when a second person, a record, or a second agent runtime is involved. Anlyon’s half holds for actions routed through its hosted executor.

Choose the bearer

The Responses API sends the value you put in the mcp tool’s authorization field as a bearer on every request, and does not store it. It does not run an OAuth flow for you: you supply a token that is already valid.

Endpoint Accepts Use it from the Responses API?
Self-hosted anlyon-mcp --http An Anlyon API key as Authorization: Bearer anlyon_live_... Yes. This is the setup this page documents.
Hosted https://api.anlyon.com/mcp OAuth access tokens issued through “Login with Anlyon” only Not in practice: an API key is refused, and an OAuth access token expires after 15 minutes. Use the hosted endpoint from clients that run the OAuth flow themselves, such as Claude Code, Codex and Cursor.

Run the self-hosted transport somewhere OpenAI can reach over HTTPS:

npx -y @anlyonhq/mcp-server --http   # listens on PORT (default 3001), serves POST /mcp

It holds no credential of its own. Each request’s bearer is the Anlyon API key the tools act with, so the key’s scopes are exactly what the model can do.

Create two keys in the dashboard:

  • Agent key (ANLYON_AGENT_KEY): actions:read, actions:invoke, approvals:read. This is the MCP bearer. Do not give it approvals:decide.
  • Operator key (ANLYON_OPERATOR_KEY): approvals:decide and actions:read, held by the person or service that reviews. A key can never decide an approval it requested itself. Add actions:resolve if this key will also record the outcome of an unknown invocation.

Call a gated action from the Responses API

Set require_approval: "never" on the OpenAI side. The approval lives in Anlyon, which parks the call for your reviewer. Asking OpenAI to pause as well would put a second approval, one Anlyon does not record, in your own process in front of the one that counts.

This example asks the model to change a support ticket’s priority through an action named update-ticket-priority that requires approval, then to wait for the decision:

// Node 22+. Needs OPENAI_API_KEY, OPENAI_MODEL, ANLYON_MCP_URL and ANLYON_AGENT_KEY.
const response = await fetch('https://api.openai.com/v1/responses', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: process.env.OPENAI_MODEL,
    tools: [{
      type: 'mcp',
      server_label: 'anlyon',
      server_url: process.env.ANLYON_MCP_URL,
      authorization: process.env.ANLYON_AGENT_KEY,
      // The approval happens in Anlyon, not here.
      require_approval: 'never',
      allowed_tools: ['action_update-ticket-priority', 'wait_for_approval'],
    }],
    input:
      'Set ticket T-1042 to priority high with action_update-ticket-priority. ' +
      'If it is parked for approval, call wait_for_approval with the approvalId and report the decision.',
  }),
});

const result = await response.json();
if (!response.ok) throw new Error(JSON.stringify(result));
for (const item of result.output ?? []) {
  if (item.type === 'mcp_call') {
    console.log(JSON.stringify({ tool: item.name, output: item.output, error: item.error }));
  }
}

What happens:

  1. The model calls action_update-ticket-priority. Anlyon records the invocation, creates an approval, and returns Awaiting human approval with its apr_... id. Nothing has been sent to the destination.
  2. The model calls wait_for_approval, which holds the call open (60 seconds by default, 120 at most) until a reviewer decides. If it comes back still pending, the model calls it again.
  3. Your reviewer approves in the Anlyon inbox, or with the operator key:
curl -sS -X POST "${ANLYON_BASE_URL:-https://api.anlyon.com}/api/v2/approvals/$APPROVAL_ID/approve" \
  -H "Authorization: Bearer $ANLYON_OPERATOR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"note": "Checked the ticket; priority change is correct."}'
  1. Anlyon executes the approved request snapshot using the credentials bound to that action. The snapshot freezes what the approver reviewed. It does not preserve a credential that has since been revoked, and destination bindings are re-checked at dispatch.
  2. wait_for_approval returns the approval record. The model reads the decision (approved, denied or expired) and who made it. payload.invocationId names the invocation it gated:
{
  "id": "apr_...",
  "kind": "action",
  "status": "approved",
  "payload": { "invocationId": "inv_...", "actionName": "update-ticket-priority", "method": "PATCH" },
  "decidedBy": { "type": "api_key", "id": "...", "displayName": null },
  "requiredApprovals": 1,
  "approvedCount": 1
}

A denied or expired approval comes back with that status, and the gated request is never sent.

Read the outcome

The approval says who decided. The invocation says what happened when Anlyon made the request. Read it with any key that has actions:read:

curl -sS "${ANLYON_BASE_URL:-https://api.anlyon.com}/api/v2/actions/invocations/$INVOCATION_ID" \
  -H "Authorization: Bearer $ANLYON_OPERATOR_KEY"

data.status is succeeded, failed or unknown, with responseStatus and the response body. The same record is in the dashboard’s invocation log.

unknown

Anlyon does not retry an action invocation. Where a request may have reached the destination and the outcome cannot be confirmed, the invocation is recorded as unknown and you reconcile before retrying: check the destination, then record what happened with POST /api/v2/actions/invocations/{id}/resolve. That call needs the actions:resolve scope. No key receives it unless its creator names it, and the key that made the invocation cannot resolve it. Idempotency keys are the supported way to replay an invocation safely.

Credentials

The model never sees a credential: an action references {{secret:NAME}} and Anlyon resolves it at dispatch. The secret is bound to a destination and a placement, checked again at fire time. This covers credentials in the Anlyon vault, not a key your own process already holds. The MCP bearer itself is an Anlyon API key. OpenAI receives it on each request and does not store it.

More than one approver

To require more than one decision, set an approval policy on the action. N counts distinct credentials for API-key deciders and distinct users for dashboard and OAuth deciders: two keys held by one person are two deciders.

The Agents API

The Agents API takes the same MCP server as a tool with transport: { type: "http", server_url, authorization } and connection_origin: "service", so OpenAI makes the HTTP connection. Put the agent key in authorization as Bearer anlyon_live_.... The hand-off is identical: the call parks in Anlyon, a reviewer decides, and Anlyon executes and records the outcome. The Agents API was in beta when this was checked on 2026-09-30. Check OpenAI’s MCP connection reference for the current field names.

How this page is checked

A script runs the three code blocks on this page in CI, against the self-hosted transport and a local API fixture.

Was this page helpful?