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.
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 failedinitaddsIMMISCIBLE_URLandIMMISCIBLE_AGENT_KEYto.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-hookto skip it.- Not Claude Code? Connect the MCP server instead:
npx immiscible mcp --client <cursor|vscode|windsurf|codex|gemini> --jsonprints the entry for that client (see install in your assistant). - Exit codes:
3not signed in (runlogin),4a flag is needed (the error names it),10the 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 tryruns a local demo.
#The contract in six lines
- 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 toolsrequest_payment,request_personal_dataandauthorize_action. allow: go ahead with exactly that, and keep the signedreceipt.approval_required: do not act; tell your person, and poll until a person decides.deny: do not act, and do not try another route; tell your person the reasons.- Retry with the same idempotency key, never a new one.
- Afterwards, settle the action with what actually happened.
#Integrate in under five minutes
- 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. - Give it a rule. Agents, Agent limits, Add a rule. With no rule (a mandate) for an action type, the answer is
denywithno_mandate; there is no default allowance. See mandates. - Ask. Set two variables and send one request:
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" }]
}'- Follow the decision (below), then settle:
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
decision | HTTP | What the agent does |
|---|---|---|
allow | 200 | Proceed with exactly what was asked. receipt is a signed, single-use token; expiresAt says until when |
approval_required | 200 | Do 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 |
deny | 200 | Do 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:
{
"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 get | Do this |
|---|---|
400 invalid_request | correct each field in error.errors (field is a dotted path into the body) and send again |
401 invalid_api_key | the 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_frozen | stop everything; a person has stopped this agent and only a person can restart it |
409 idempotency_conflict | you reused a key for a different request; use a new key for a new request |
429 rate_limited | wait retry-after seconds, then retry with the same idempotency key |
5xx or no answer | retry 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
velocitysignal, 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/verifyand{ "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")import { Agent, run, tool } from '@openai/agents';
import { Immiscible } from '@immiscible/sdk';
import { guardOpenAITools } from '@immiscible/sdk/openai-agents';
const immiscible = new Immiscible().run(); // IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY
const agent = new Agent({
name: 'Buyer',
tools: guardOpenAITools([buy], {
client: immiscible,
mapToAction: ({ args }) => Immiscible.paymentAction({ amount: args.pence, currency: 'GBP', merchant: args.domain, provenance: [{ source: 'user' }] }),
}),
});
await run(agent, 'Renew the team licence at vendor.example.');{
"mcpServers": {
"immiscible": {
"url": "https://immiscible.fly.dev/mcp",
"headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" }
}
}
}import { Immiscible, x402Fetch } from '@immiscible/sdk';
const pay = x402Fetch(new Immiscible(), {
pay: ({ requirements, paymentRequired }) => myX402Client.createPaymentHeader(requirements, paymentRequired), // your signer, called only on allow
provenance: [{ source: 'user', detail: 'the analyst asked for this report' }],
});
const res = await pay('https://api.data-vendor.example/v1/quotes');- 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/sdkorpip install immiscible. - MCP clients: Claude, ChatGPT and other connector-capable clients can instead add
https://immiscible.fly.dev/mcpas 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 withnpx immiscible login --jsonthennpx immiscible init --yes --json, a person allowing the sign-in once. - x402: Immiscible sits between the
402and 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.
| Where | Install | What you get |
|---|---|---|
| Claude Code (plugin) | /plugin marketplace add efr7-7/immiscible-sdks, then /plugin install immiscible@immiscible | the 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 init | the hook in .claude/settings.json, the agent, its rule and .env |
| Claude Desktop | open immiscible.mcpb (built from packages/immiscible-desktop) and paste the agent key | the eleven tools, through a local bridge with no dependencies |
| Claude and ChatGPT on the web | add https://immiscible.fly.dev/mcp as a custom connector and sign in | the 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 skill | the same |
| Cursor | npx immiscible mcp --client cursor prints .cursor/mcp.json and a one-click cursor:// install link | the eleven tools |
| VS Code | code --add-mcp '{"name":"immiscible","type":"http","url":"https://immiscible.fly.dev/mcp"}' | the eleven tools, with OAuth sign-in |
| Gemini CLI | git 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/mcp | the eleven tools and the same instructions as context |
| Codex CLI | codex mcp add immiscible --url https://immiscible.fly.dev/mcp --bearer-token-env-var IMMISCIBLE_AGENT_KEY | the eleven tools |
| Windsurf (Devin Desktop) | npx immiscible mcp --client windsurf prints the entry | the 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?”
- 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 toPOST /api/w/:wid/mandates;perTransactionstays the ceiling nobody can talk past (above it isdeny, not a question):
{
"agentId": "agt_4f2c91a7",
"kind": "payment",
"title": "Claude Code purchases",
"currency": "GBP",
"perTransaction": 200000,
"perPeriod": 500000,
"period": "month",
"approveAbove": 50000,
"newMerchant": "approve"
}- 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_paymenttool orPOST /v1/actions/authorize, which the agent calls itself, so pair it with one of the two above.
- Close the side doors with the Claude Code hook. It sends Claude Code’s own tools (Bash, Write, Edit, WebFetch) as
tool.callactions; atool.callrule 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?”
- Connect the providers. Under Settings, Connections, paste your OpenAI and Anthropic keys. Traffic is served on your own contracts; Immiscible never resells inference.
- Point every client at the gateway. One base URL per protocol: OpenAI-shaped at
https://immiscible.fly.dev/v1, Anthropic-shaped athttps://immiscible.fly.dev/anthropic, with an Immiscible key instead of the provider’s:
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- 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
500000000is $500. With a gateway key issued with the admin scope (see authentication):
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:
| Tool | Call it | Read only |
|---|---|---|
request_payment | before spending any money | no |
request_personal_data | before giving anyone the person’s details | no |
authorize_action | before any other consequential action: email, calendar, account changes, tool calls | no |
check_action_status | to poll after approval_required | yes |
explain_decision | to say in plain English why something was allowed, held or refused | yes |
settle_action | once, after an allowed action, with what happened | no |
spend_summary | when the person asks what the company spent on AI, by provider, model, team or key | yes |
find_waste | when they ask what could be cheaper: routing and caching estimates, each an upper bound | yes |
unwatched_keys | when they ask which API keys nobody is watching | yes |
set_budget | to ask for a monthly budget; a person approves before it is set | no |
revoke_key | to ask to switch a key off; a person approves, then an owner confirms | no |
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
| What | Where |
|---|---|
| Docs index for models | https://immiscible.fly.dev/llms.txt |
| The core of the docs in one file | https://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 Markdown | the page address plus .md, for example https://immiscible.fly.dev/docs/quickstart.md |
| OpenAPI 3.1 for the decision API | https://immiscible.fly.dev/openapi.json |
| MCP server card | https://immiscible.fly.dev/mcp/server-card, listed in https://immiscible.fly.dev/.well-known/ai-catalog.json |
| Receipt signing keys | https://immiscible.fly.dev/.well-known/immiscible-keys.json |