# Nostro > Nostro gives each AI agent its own virtual card and access to the company's EUR account, in the company's name, with a spending mandate (per-payment cap, daily and monthly limits, allowed MCC, blocked merchants, human approval above a threshold). Agents drive it over MCP (Streamable HTTP) or REST. Status: v0.1.0 private preview with a public sandbox (fake money, simulated cards). Live accounts later through a licensed EU partner institution. Nostro is not a bank. Docs: https://docs.nostro.ag (French: https://docs.nostro.ag/fr). Landing: https://nostro.ag. ## Get a sandbox key - Web: https://nostro.ag/sandbox - API (public, no auth): `POST https://mcp.nostro.ag/api/sandbox/signup` body `{"email": string, "company"?: string, "lang"?: "en"|"fr"}` - 201, returns keys once: `{workspace_id, admin_key: "nsa_...", agent: {id, name: "demo-agent", key: "nst_...", policy}, mcp_url: "https://mcp.nostro.ag/mcp", balance: {amount: 10000, currency: "EUR", fake: true}, sandbox: true}`. Isolated workspace on a virtual ledger (bankMode "ledger"), 10 000 EUR of fake money. demo-agent mandate: per payment 500, per day 2000, per month 5000, human approval above 250. - Limits: 3 workspaces per email, 10 signups/hour per IP, 500/hour overall (429), 25 agents per workspace, sandbox balance max 1 000 000 EUR. Browser CORS limited to nostro.ag and docs.nostro.ag. Public counters: GET https://mcp.nostro.ag/api/sandbox/stats. ## Connect - MCP endpoint: https://mcp.nostro.ag/mcp (Streamable HTTP, stateless, JSON responses). Header `Authorization: Bearer `. Agent key (nst_) exposes agent tools; admin key (nsa_) exposes admin tools. Bad key: HTTP 401, JSON-RPC -32001. - Claude Code: `claude mcp add --transport http nostro https://mcp.nostro.ag/mcp --header "Authorization: Bearer nst_..."` - Cursor (~/.cursor/mcp.json): `{"mcpServers":{"nostro":{"url":"https://mcp.nostro.ag/mcp","headers":{"Authorization":"Bearer nst_..."}}}}` - Claude Desktop: via mcp-remote: `{"mcpServers":{"nostro":{"command":"npx","args":["-y","mcp-remote","https://mcp.nostro.ag/mcp","--header","Authorization:${AUTH}"],"env":{"AUTH":"Bearer nst_..."}}}}` - REST base: https://mcp.nostro.ag. Agents: /v1/agent/* with nst_ key. Admins: /api/* with nsa_ key. ## Agent tools (nst_ key) - `get_wallet()` [GET /v1/agent/wallet]: Remaining budget (day, month), caps, card status and the available balance of the company account. Agents are instructed to call it before any spend. - `pay(amount: number, merchant: string, mcc?: string, description?: string)` [POST /v1/agent/pay]: Pays a merchant with the agent's virtual card. Evaluated against the mandate first: executed, blocked, or held for human approval. - `send_transfer(amount: number, iban: string, beneficiaryName: string, label?: string)` [POST /v1/agent/transfer]: Sends a SEPA transfer from the company account, only if the mandate enables transfers. Usually held for human approval. - `list_my_activity(limit?: integer)` [GET /v1/agent/activity]: The agent's own history: every payment, transfer and decision (executed, denied, pending). Newest first. - `get_request_status(requestId: string)` [GET /v1/agent/requests/:id]: Status of a spend held for human approval. Agents can only read their own requests. pay / send_transfer results: `{"status":"paid","transactionId","amount","merchant"}`, `{"status":"initiated","paymentId","bankStatus"}`, `{"status":"pending_approval","requestId","reason","message"}`, `{"status":"denied","reason"}`, `{"status":"declined_by_bank","reason"}`. Branch on status, not on reason text (reasons are French in v0.1). Never retry or split a denied or pending payment. ## Admin tools (nsa_ key) - `overview()` [GET /api/overview]: Account, balance, number of active agents, pending approvals and spend this month. - `list_agents()` [GET /api/agents]: Every non-revoked agent with its card, mandate and current spend. - `create_agent(name: string, purpose?: string, perTxMax?: number, dailyMax?: number, monthlyMax?: number, approvalAbove?: number, allowedMccs?: string[], blockedMerchants?: string[], transfersEnabled?: boolean, transferApprovalAbove?: number)` [POST /api/agents]: Creates an agent with its own virtual card in the company's name, its mandate and its API key. The key is returned once and never again. - `update_agent_policy(agentId: string, perTxMax?: number, dailyMax?: number, monthlyMax?: number, approvalAbove?: number, allowedMccs?: string[], blockedMerchants?: string[], transfersEnabled?: boolean, transferApprovalAbove?: number)` [POST /api/agents/:id/policy]: Patches an agent's mandate. Only the fields you send change; the rest is kept. Changing monthlyMax also updates the card limit. - `freeze_agent(agentId: string)` [POST /api/agents/:id/freeze]: Kill switch: blocks every spend of the agent immediately. Reversible. - `unfreeze_agent(agentId: string)` [POST /api/agents/:id/unfreeze]: Re-activates a frozen agent. - `revoke_agent(agentId: string)` [POST /api/agents/:id/revoke]: Revokes the agent's key and cancels its card. Irreversible. - `list_pending_approvals()` [GET /api/requests?status=pending]: Spends held for a human decision, newest first (max 100). - `approve_request(requestId: string)` [POST /api/requests/:id/approve]: Approves a pending request and executes it right away. One decision per request, even with concurrent clicks. - `reject_request(requestId: string)` [POST /api/requests/:id/reject]: Rejects a pending request. Nothing is charged. - `fund_account(amount: number)` [POST /api/fund]: Sandbox only: credits the workspace's virtual ledger (simulated incoming SEPA transfer, fake euros). - `audit_log(agentId?: string, limit?: integer)` [GET /api/events?agentId=&limit=]: Every decision and operation in the workspace, newest first, optionally for one agent. REST only: GET /api/agents/:id, POST /api/agents/:id/refresh (POST /api/swan/bootstrap is operator-only, 403 with a sandbox key). POST /api/agents takes {name, purpose?, policy?: {...}} (mandate nested); the MCP tool takes mandate fields flat. GET /api/requests?status=pending|executed|failed|rejected|all. ## Mandate fields (defaults) - perTxMax: number = 100. Maximum amount of a single card payment, in EUR. Above it the payment is blocked. - dailyMax: number = 300. Maximum executed spend per calendar day (UTC), card payments and transfers combined. - monthlyMax: number = 1000. Monthly budget. Also set as the spending limit on the card itself (defense in depth). - approvalAbove: number = 50. Card payments strictly above this amount wait for a human approval instead of executing. - allowedMccs: string[] = []. Allowed merchant category codes (4 digits). Empty means every category is allowed. - blockedMerchants: string[] = []. Merchant names to block. Case-insensitive substring match on the merchant name. - transfersEnabled: boolean = false. Whether the agent may send SEPA transfers at all. - transferApprovalAbove: number = 0. Transfers strictly above this amount need a human approval. 0 means every transfer is reviewed. ## Decision order Card payment: amount<=0 block; merchant contains a blockedMerchants entry (case-insensitive) block; allowedMccs non-empty and mcc missing or not listed block; amount>perTxMax block; today's executed spend+amount>dailyMax block; month's executed spend+amount>monthlyMax block; amount>approvalAbove review (human approval); else allow. Transfer: transfersEnabled false block; amount<=0 block; month+amount>monthlyMax block; amount>transferApprovalAbove review (default 0 = always); else allow. Hard limits are checked before review, so over-limit spends are blocked, not escalated. Windows are UTC calendar day/month; only executed spend counts. Approved requests execute immediately with the original parameters. monthlyMax is also set as the card's own limit. ## Errors 200 with status denied/pending_approval/declined_by_bank are business outcomes, not errors. HTTP: 400 invalid input or already decided, 401 bad key, 403 agent frozen, 404 not found, 409 account not ready, 429 sandbox limit reached (back off, honor Retry-After), 500 internal, 502 banking partner error. Body `{"error": "...", "details"?}`. MCP tool failures return isError: true with {"error"} text. No idempotency keys in v0.1: after a timeout, check list_my_activity before retrying pay. ## Security Keys shown once, stored as SHA-256 hashes; keyPrefix (8 chars) identifies them. Agent keys see only their own card, mandate and activity; admin keys control the workspace and must stay with humans. Mandates are enforced server-side, so prompt injection cannot exceed them. Kill switches: freeze_agent (reversible, 403 on spend), revoke_agent (irreversible). ## Pages - [Introduction](https://docs.nostro.ag/) - [Quickstart](https://docs.nostro.ag/quickstart) - [Examples](https://docs.nostro.ag/examples): MCP client config, TypeScript MCP agent, Python REST agent with approvals, admin script - [Concepts](https://docs.nostro.ag/concepts) - [Mandates & policy engine](https://docs.nostro.ag/mandates) - [MCP tools reference](https://docs.nostro.ag/mcp-tools) - [REST API reference](https://docs.nostro.ag/rest-api) - [Errors & rate limits](https://docs.nostro.ag/errors) - [Security](https://docs.nostro.ag/security) - [Changelog](https://docs.nostro.ag/changelog)