Four ways an agent spends under a mandate.
Whichever way it connects, the agent never sees a card number or your accounts, every purchase is decided by the same policy engine, and every decision lands in your ledger. Amounts are integers in minor units everywhere.
1. Connect with one click (MCP + OAuth) — Claude, ChatGPT, Cursor, and any MCP client
Mandate is a remote MCP server. Add it in your agent's connectors or MCP settings using this URL; the agent will send you here to sign in and approve, and receives a scoped token automatically. Nothing to copy.
https://mandate-ashen.vercel.app/api/mcp
The agent gets nine tools: list_mandates, check_mandate, request_purchase, then capture_purchase / void_purchase to settle what it actually paid, get_purchase, propose_plan / get_plan to get a whole shopping list approved once, and delegate to carve a narrower sub-mandate out for a helper. Scopes: mandate:read to see limits, mandate:spend to ask to spend. You can disconnect any agent from Settings.
# Claude Code claude mcp add --transport http mandate https://mandate-ashen.vercel.app/api/mcp # Cursor / Windsurf / others: add an MCP server of type "http" with the URL above.
2. Your own agent: the SDKs or the REST API with a mandate token
For code you run yourself. The token is shown once when you issue a mandate. The connect wizard fills it into these snippets for you and watches for the first call.
pip install mandate-agent # Python: from mandate_agent import Mandate
npm install mandate-agent # TypeScript: import { Mandate } from "mandate-agent"Both give you authorize / capture / void, a hold helper that voids on error, polling for pending approvals, and ready-made tools for the OpenAI Agents SDK, LangChain and the Vercel AI SDK. Underneath is plain HTTP:
POST https://mandate-ashen.vercel.app/api/agent/authorize
Authorization: Bearer mnd_...
Idempotency-Key: order-2026-09-05-001
Content-Type: application/json
{ "amount": 1299, "merchant": "OpenAI", "purpose": "API credits", "category": "computer_software_stores" }Amounts are integers in minor units. 200 approved, 403 declined with the rule, 202 pending — you've been notified; the agent retries the identical request after you approve. Repeating an Idempotency-Key returns the stored answer instead of deciding twice. Every non-approval carries a remedy: when the same request would pass (retryAt, also sent as x-mandate-retry-at), the most that would pass right now (maxAmountNow), and one sentence of advice.
An approval is a hold. Once the purchase completes, tell Mandate what was actually paid; paying less gives the difference back to the limits. A hold nobody settles is closed by the mandate's policy (captured in full, or released) when its TTL runs out — 24 hours by default.
Two more answers an agent can get. A pending with rule veto means the owner set a veto window: the purchase goes through by itself at remedy.retryAt unless they cancel — retry then. And for a multi-step task, propose a plan first: POST /api/agent/plans with the list of intended purchases; once the owner approves it, each item passes without asking.
POST https://mandate-ashen.vercel.app/api/agent/plans { "title": "Q4 tooling", "items": [{ "merchant": "OpenAI", "amount": 2000, "purpose": "credits" }] }
GET https://mandate-ashen.vercel.app/api/agent/plans/:id # proposed | approved | denied | completed …
POST https://mandate-ashen.vercel.app/api/agent/capture { "transactionId": "…", "amount": 940, "note": "order #1234" }
POST https://mandate-ashen.vercel.app/api/agent/void { "transactionId": "…", "reason": "checkout failed" }
GET https://mandate-ashen.vercel.app/api/agent/transactions/:id # held | captured | voided | released
GET https://mandate-ashen.vercel.app/api/agent/transactions/:id/voucher # signed voucher to hand the merchant
POST https://mandate-ashen.vercel.app/api/agent/delegate { "name": "Price checker", "perTxnLimit": 500, "dailyLimit": 2000, "totalLimit": 5000, "allowedMerchants": ["OpenAI"] }
GET https://mandate-ashen.vercel.app/api/agent/mandate # limits, what's left, open holds, sub-mandatesVouchers. An approved hold's answer carries voucherUrl; fetching it (GET /api/agent/transactions/:id/voucher, MCP get_voucher, SDK voucher()) returns a signed token the agent hands to the merchant — and from that moment the agent can no longer capture or void the hold itself. The merchant verifies it offline with the key at /.well-known/mandate-receipt-key (or POST /api/vouchers/verify) and redeems it at POST /api/vouchers/redeem for what was actually sold — no account, no card network. A decline with rule frozen means the owner pressed the panic button: stop and tell the user. A decline with rule parent_… means a sub-mandate ran into its parent's terms.
# merchant side, no account needed
POST https://mandate-ashen.vercel.app/api/vouchers/verify { "voucher": "mv1.…" } → valid, redeemable, amount, expiresAt
POST https://mandate-ashen.vercel.app/api/vouchers/redeem { "voucher": "mv1.…", "amount": 1800, "merchant": "Acme", "reference": "INV-42" }
# offline: Ed25519.verify(publicKey, "mandate-voucher|" + base64urlDecode(payload), signature)Sandbox. A mandate issued as a sandbox has a mnd_test_ token; it decides, asks and records exactly like a live one, never touches a card or a real API, and can be reset from its page. Use it in your integration tests.
3. Meter LLM API spend with a proxy key (OpenAI, Anthropic, Gemini) — or govern any API
Store your provider key once on the API proxy page and hand the agent a proxy key bound to a USD mandate. Point the SDK's base URL at Mandate and change nothing else: each call is priced from the request, pre-authorised against the mandate, forwarded with the real key, and settled on the tokens the provider reports. The agent never holds the real key, so it cannot spend past the mandate.
OPENAI_BASE_URL=https://mandate-ashen.vercel.app/api/proxy/openai OPENAI_API_KEY=mpx_... ANTHROPIC_BASE_URL=https://mandate-ashen.vercel.app/api/proxy/anthropic ANTHROPIC_API_KEY=mpx_... # Gemini: base URL https://mandate-ashen.vercel.app/api/proxy/gemini with x-goog-api-key: mpx_... # any other API, added as a custom target on the API proxy page (credential injected, priced per call or from the response): GET https://mandate-ashen.vercel.app/api/proxy/t/<slug>/<path> Authorization: Bearer mpx_... # any method
4. Local MCP for Claude Code / Cursor without OAuth
The repo ships mcp/server.mjs, a zero-dependency stdio server that wraps the REST API with a mandate token. Useful offline or for a single machine.
{
"mcpServers": {
"mandate": {
"command": "node",
"args": ["/path/to/mandate/mcp/server.mjs"],
"env": { "MANDATE_URL": "https://mandate-ashen.vercel.app", "MANDATE_TOKEN": "mnd_..." }
}
}
}Event webhooks — for the systems around the agent
Every ledger event (decisions, captures, approvals, revocations, member changes) can be pushed as signed JSON to your own endpoints, with retries and ordering per endpoint. Set them up under Settings → Event webhooks; verify Mandate-Signature with the secret shown once.
Virtual cards
Not enabled on this deployment yet. When they are, a mandate can carry its own virtual card, decided by the same terms in real time; until then the three rails above cover API, MCP and LLM spend.