Skip to content

Get started

For AI agents

Everything an agent, or the person wiring one up, needs on one page. Ask before acting, follow the decision, retry safely, keep the receipt, and report what happened. Copy-paste setups for Claude Code, the OpenAI Agents SDK, MCP clients and x402.

This page is written to be read by a model as much as by a person. Every page of these docs is also plain Markdown at the same address with .md on the end (this one is https://immiscible.fly.dev/docs/ai-agents.md), the core (getting started, the concepts, the answers and the decision API) is one file at https://immiscible.fly.dev/llms-full.txt, every other section is a file of its own under /llms-full/, and the index, which lists them all, is https://immiscible.fly.dev/llms.txt.

#Set up Immiscible for this project

If a person has asked you to “Set up Immiscible for this project”, do this, in their project folder. They allow the sign-in once in their browser; you do the rest without a terminal prompt.

Shell
npx immiscible login --json        # line 1 has verification_uri_complete: show it to the person and wait for them
npx immiscible init --yes --json   # the agent, its rule, .env, the Claude Code hook (fails closed) and a live test call
npx immiscible doctor --json       # exit 0 when nothing failed
  • init adds IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY to .env (never replacing a value without --force) and, in a Claude Code project, installs the PreToolUse hook in .claude/settings.json, so every Bash, Write, Edit, WebFetch and MCP call asks first. Pass --no-hook to skip it.
  • Not Claude Code? Connect the MCP server instead: npx immiscible mcp --client <cursor|vscode|windsurf|codex|gemini> --json prints the entry for that client (see install in your assistant).
  • Exit codes: 3 not signed in (run login), 4 a flag is needed (the error names it), 10 the rule waits for another owner. Never paste the agent key into chat, and never commit .env.
  • Then tell your person what changed: the agent’s name, its rule, and the files you touched. To try it first with no account at all, npx immiscible try runs a local demo.

#The contract in six lines

  1. Before a payment, a release of personal data, an email, an account change or a tool call that reaches another system, describe it to Immiscible: POST /v1/actions/authorize, or the MCP tools request_payment, request_personal_data and authorize_action.
  2. allow: go ahead with exactly that, and keep the signed receipt.
  3. approval_required: do not act; tell your person, and poll until a person decides.
  4. deny: do not act, and do not try another route; tell your person the reasons.
  5. Retry with the same idempotency key, never a new one.
  6. Afterwards, settle the action with what actually happened.

#Integrate in under five minutes

  1. Get an agent key. In the console, Agents, Add an agent, then Collect the agent’s key on its setup page. It looks like ask_..., is shown once, and can only ask: it cannot approve, widen a rule or lift a freeze.
  2. Give it a rule. Agents, Agent limits, Add a rule. With no rule (a mandate) for an action type, the answer is deny with no_mandate; there is no default allowance. See mandates.
  3. Ask. Set two variables and send one request:
Shell
export IMMISCIBLE_URL=https://immiscible.fly.dev
export IMMISCIBLE_AGENT_KEY=ask_...

curl -X POST "$IMMISCIBLE_URL/v1/actions/authorize" \
  -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: invoice-2026-10-northwind" \
  -d '{
    "type": "payment",
    "summary": "Pay the October invoice from Northwind Supplies",
    "payment": { "amount": 42000, "currency": "GBP", "merchant": { "name": "Northwind", "domain": "northwind.example" } },
    "provenance": [{ "source": "user", "detail": "monthly supplier run" }]
  }'
  1. Follow the decision (below), then settle:
Shell
curl -X POST "$IMMISCIBLE_URL/v1/actions/act_7Qm2c1f0/settle" \
  -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" -H "content-type: application/json" \
  -d '{ "status": "completed", "amount": 42000 }'

Amounts are always whole minor units: 42000 is £420.00. The quickstart walks the same steps with the console open beside you.

#Decision semantics

decisionHTTPWhat the agent does
allow200Proceed with exactly what was asked. receipt is a signed, single-use token; expiresAt says until when
approval_required200Do not act. Tell the person you act for; approval.url is the link a person decides at. Poll GET /v1/actions/:id (or check_action_status) every five seconds for a minute, then every thirty. It becomes allow or deny
deny200Do not act and do not try another way. Tell the person the reasons

A refusal is an answer, not an error, so it is HTTP 200. reasons are sentences for people; risk.signals[].id are stable codes for code (over_transaction, approve_above, rule_of_two, lookalike_domain and the rest are listed in decisions). When Immiscible cannot reach a decision, the answer is deny, never allow.

To tell your person why, ask for the explanation: GET /v1/actions/:id/explain, or the MCP tool explain_decision. It is read only, safe to call at any time, and answers in plain English: the rule the action was judged under, each reason and signal, and what to do next.

#Error handling

HTTP errors mean Immiscible could not evaluate the request at all. Every one carries a stable type, a message, and, on every 4xx, a one-sentence fix and a docs link:

JSON
{
  "error": {
    "type": "agent_key_required",
    "message": "this endpoint needs an agent key; issue one for the agent in the console under Agents",
    "fix": "Send an agent key (ask_...), not a workspace or gateway key: in the console open Agents, Add an agent, then Collect the agent's key on its setup page.",
    "docs": "https://immiscible.fly.dev/docs/api/authentication#agent-keys"
  }
}
You getDo this
400 invalid_requestcorrect each field in error.errors (field is a dotted path into the body) and send again
401 invalid_api_keythe key is wrong, revoked or missing; do not retry until a person gives you a new one
403 agent_stopped, or a deny with agent_frozenstop everything; a person has stopped this agent and only a person can restart it
409 idempotency_conflictyou reused a key for a different request; use a new key for a new request
429 rate_limitedwait retry-after seconds, then retry with the same idempotency key
5xx or no answerretry with the same idempotency key and back off; if it never answers, do not act. Never treat an error as allow

The full list is in errors and headers. Branch on error.type; the message is for people and may change.

#Idempotency

Send an idempotency key with every action request, in the body as idempotencyKey or as the Idempotency-Key header, 1 to 128 printable characters of your choosing, without spaces.

  • The same key with the same body returns the same decision, and never asks a person twice. That is how to retry after a timeout.
  • The same key with a different body is 409 idempotency_conflict.
  • A new key is a new request. Do not use a fresh key to get a different answer: a burst of attempts trips the velocity signal, which asks a person.

The SDKs make the key for you; the OpenAI Agents SDK integration uses the model’s tool call id, so a retried tool call is the same action.

#Receipts

An allow carries receipt: a compact JWS signed with Ed25519, single use, naming the agent, the action, the mandate, the amount and whether a person approved it ("hum": true).

  • Hand it to whoever needs proof: a merchant, a card issuer, an auditor.
  • Anyone can check it with POST /v1/verify and { "receipt": "eyJ..." }, no account needed, or offline against the public key set at https://immiscible.fly.dev/.well-known/immiscible-keys.json.
  • A receipt proves one action was allowed. It does not prove the action happened; settling records that.

More in receipts.

#Copy-paste setups

# 1. Immiscible's MCP server, so Claude can ask before paying or sharing data
claude mcp add --transport http immiscible https://immiscible.fly.dev/mcp \
  --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY"

# 2. The PreToolUse hook, so every Bash, Write, Edit, WebFetch and MCP call is
#    checked before it runs, whether or not the model remembers to ask
mkdir -p ~/.immiscible
curl -fsSL https://immiscible.fly.dev/downloads/claude-code-hook.mjs -o ~/.immiscible/claude-code-hook.mjs
# then add the hook to ~/.claude/settings.json (see "The Claude Code hook")
  • Claude Code: the hook and its settings are in the Claude Code hook; its model traffic can go through the gateway too (ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic).
  • OpenAI Agents SDK: Python, the guardrail alternative and every option are in the OpenAI Agents SDK guide. Install with npm install @immiscible/sdk or pip install immiscible.
  • MCP clients: Claude, ChatGPT and other connector-capable clients can instead add https://immiscible.fly.dev/mcp as a custom connector and sign in with OAuth; see Claude and ChatGPT as connectors.
  • Any other MCP client (Cursor, VS Code, Windsurf, Codex, Gemini CLI): npx immiscible mcp --client <client> prints its entry; see add the MCP server. An AI coding agent can install Immiscible itself with npx immiscible login --json then npx immiscible init --yes --json, a person allowing the sign-in once.
  • x402: Immiscible sits between the 402 and your signature and never signs; see x402 payments.

#Install in your assistant

One line for each, from the packages in efr7-7/immiscible-sdks. Each one connects to the MCP server at https://immiscible.fly.dev/mcp and reads the agent key from IMMISCIBLE_AGENT_KEY (which npx immiscible init writes to .env) or signs in with OAuth.

WhereInstallWhat you get
Claude Code (plugin)/plugin marketplace add efr7-7/immiscible-sdks, then /plugin install immiscible@immisciblethe fail-closed hook on every tool call, the MCP server, the ask-before-acting skill and the immiscible-analyst subagent, which reads approvals, decisions and spend and never acts
Claude Code (no plugin)npx immiscible initthe hook in .claude/settings.json, the agent, its rule and .env
Claude Desktopopen immiscible.mcpb (built from packages/immiscible-desktop) and paste the agent keythe eleven tools, through a local bridge with no dependencies
Claude and ChatGPT on the webadd https://immiscible.fly.dev/mcp as a custom connector and sign inthe eleven tools, acting as the agent you pick
ChatGPT and Codex (plugin)packages/immiscible-openai: a plugin with the MCP server (OAuth) and the ask-before-acting skillthe same
Cursornpx immiscible mcp --client cursor prints .cursor/mcp.json and a one-click cursor:// install linkthe eleven tools
VS Codecode --add-mcp '{"name":"immiscible","type":"http","url":"https://immiscible.fly.dev/mcp"}'the eleven tools, with OAuth sign-in
Gemini CLIgit clone https://github.com/efr7-7/immiscible-sdks && gemini extensions install ./immiscible-sdks/packages/immiscible-gemini, or gemini mcp add --transport http -H "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" immiscible https://immiscible.fly.dev/mcpthe eleven tools and the same instructions as context
Codex CLIcodex mcp add immiscible --url https://immiscible.fly.dev/mcp --bearer-token-env-var IMMISCIBLE_AGENT_KEYthe eleven tools
Windsurf (Devin Desktop)npx immiscible mcp --client windsurf prints the entrythe eleven tools

Every entry is in add the MCP server. The plugins, the Desktop bundle and the Gemini extension ship with the 0.1.1 release of the public repository; none is listed in a directory yet.

#Ask a person above an amount

”How do I stop my Claude Code agent paying more than £500 without approval?”

  1. Write a payment rule with an approval line. Agents, Agent limits, Add a rule, kind payment, with Ask me above £500. In the API that is a payment mandate with approveAbove: 50000, sent by a signed-in owner or admin to POST /api/w/:wid/mandates; perTransaction stays the ceiling nobody can talk past (above it is deny, not a question):
JSON
{
  "agentId": "agt_4f2c91a7",
  "kind": "payment",
  "title": "Claude Code purchases",
  "currency": "GBP",
  "perTransaction": 200000,
  "perPeriod": 500000,
  "period": "month",
  "approveAbove": 50000,
  "newMerchant": "approve"
}
  1. Make every payment pass the gate where the amount is visible. The strongest first:
    • the card rail: bind a Stripe Issuing card (or another issuer through signed webhooks) to the agent, and the issuer asks Immiscible before money moves; no receipt, no payment;
    • the MCP proxy in front of a payment tool, with a tool map that says where the amount is ("type": "payment", "amountPath": "amount");
    • the MCP server’s request_payment tool or POST /v1/actions/authorize, which the agent calls itself, so pair it with one of the two above.
  2. Close the side doors with the Claude Code hook. It sends Claude Code’s own tools (Bash, Write, Edit, WebFetch) as tool.call actions; a tool.call rule that lists its domains refuses a shell command that posts to anywhere else, such as a payment API the rule does not name.

The agent’s tier still applies on top: a new agent is an intern and a person signs off every payment, which is stricter than £500. For it to pay up to £500 alone it must reach senior (pays alone up to £1,000), on evidence. See autonomy tiers.

#One budget across OpenAI and Anthropic

”What’s the best way to govern agent spend across OpenAI and Anthropic?”

  1. Connect the providers. Under Settings, Connections, paste your OpenAI and Anthropic keys. Traffic is served on your own contracts; Immiscible never resells inference.
  2. Point every client at the gateway. One base URL per protocol: OpenAI-shaped at https://immiscible.fly.dev/v1, Anthropic-shaped at https://immiscible.fly.dev/anthropic, with an Immiscible key instead of the provider’s:
Shell
export ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic   # Claude Code, the Anthropic SDK
export OPENAI_BASE_URL=https://immiscible.fly.dev/v1             # the OpenAI SDK and compatible tools
  1. Set budgets. By team, person or workspace, for a calendar month, checked against the projected cost before each call. Amounts are millionths of a US dollar, so 500000000 is $500. With a gateway key issued with the admin scope (see authentication):
Shell
curl -X POST "https://immiscible.fly.dev/v1/admin/budgets" \
  -H "authorization: Bearer $IMMISCIBLE_ADMIN_KEY" -H "content-type: application/json" \
  -d '{ "scope": "org", "baseAllocation": 500000000, "hardCeiling": 600000000, "ownerId": "finance@example.com" }'

Near the allocation the gateway nudges, then routes to cheaper eligible models; at the allocation it answers 402 approval_required naming the owner; past the ceiling, 429 budget_exhausted. 4. Switch to enforce. Every workspace starts in shadow mode, where nothing is blocked, not even an exhausted budget: run a week, read Assessment, then switch to Enforce under Rules, Models and enforcement. 5. Find the spend that bypasses it. Discovery reads OpenAI, Anthropic and OpenRouter admin APIs for keys and projects outside the gateway, and finance dashboards put spend by team, provider and agent where finance already looks.

Inference that runs in a vendor’s own backend (Devin, GitHub Copilot, Cursor’s hosted models) cannot pass through any gateway; it is reconciled from the vendor’s API and marked governed: false. See the gateway.

#The MCP server

https://immiscible.fly.dev/mcp speaks Streamable HTTP (JSON-RPC 2.0, protocol 2025-06-18, also 2025-03-26 and 2024-11-05). Authenticate with an agent key as Authorization: Bearer ask_..., or an OAuth access token from the connector sign-in. Its tools:

ToolCall itRead only
request_paymentbefore spending any moneyno
request_personal_databefore giving anyone the person’s detailsno
authorize_actionbefore any other consequential action: email, calendar, account changes, tool callsno
check_action_statusto poll after approval_requiredyes
explain_decisionto say in plain English why something was allowed, held or refusedyes
settle_actiononce, after an allowed action, with what happenedno
spend_summarywhen the person asks what the company spent on AI, by provider, model, team or keyyes
find_wastewhen they ask what could be cheaper: routing and caching estimates, each an upper boundyes
unwatched_keyswhen they ask which API keys nobody is watchingyes
set_budgetto ask for a monthly budget; a person approves before it is setno
revoke_keyto ask to switch a key off; a person approves, then an owner confirmsno

The last five are the AI spend analyst; add the analyst to Claude says how they answer and act. A refused call comes back as a tool result with isError: true and text naming the error, the fix and the docs link, so the model can correct itself. The server’s card is at https://immiscible.fly.dev/mcp/server-card.

#Machine-readable

WhatWhere
Docs index for modelshttps://immiscible.fly.dev/llms.txt
The core of the docs in one filehttps://immiscible.fly.dev/llms-full.txt; every other section at https://immiscible.fly.dev/llms-full/<section>.txt, listed in llms.txt
Any page as Markdownthe page address plus .md, for example https://immiscible.fly.dev/docs/quickstart.md
OpenAPI 3.1 for the decision APIhttps://immiscible.fly.dev/openapi.json
MCP server cardhttps://immiscible.fly.dev/mcp/server-card, listed in https://immiscible.fly.dev/.well-known/ai-catalog.json
Receipt signing keyshttps://immiscible.fly.dev/.well-known/immiscible-keys.json