```mermaid
flowchart TD
  A["Agent: tools/call account<br/>banking.paymentrequired.com/mcp"] --> B["banking :8796 handleMcp<br/>accountReadyFor, then mcp-account-tool call()"]
  B --> SW{"action"}
  SW -->|status| ST["products get_account_status<br/>identity, card, euro account"]
  SW -->|verify| VE["create account (email, country)<br/>or resume: KYC link, SEPA step"]
  SW -->|limits op view| LV["spend_policy view<br/>wallet and bank caps"]
  SW -->|limits op set| LS["owner-pinned approval link<br/>then same call applies"]
  LV -->|"helper safe_address"| WS["wallet service"]
  LS -->|"helper safe_address"| WS
  VE --> WX(("Wirex BaaS"))
  ST --> WX
  LS --> SG["approval page / signer"]
```

# `account` (Banking)

[← Index](../index.md) · [Overview](../overview.md) · [Signer](../signer.md) · [Issued links](../links.md)

> Evidence labels: **CODE** = read in the cited file at the recorded revision/hash; **FIXTURE** = parent-accepted combined cloud fixture run 99/99 (`directory-wave/parent-directory-wave-acceptance.json`), not live; **LIVE-RO** = observed read-only on the live host 2026-10-14 (raw files in `evidence/live-probes/`); **UNVERIFIED** = not executed here. Topology = accepted directory-wave source, **not deployed**. Nothing in this handbook moved money, signed, or activated a card.

## What it does

Who the person is: the identity check (KYC) with the card and bank provider, the readiness of the card and euro account, and the spending limits that let the agent pay without a tap.

**Catalog:** banking.paymentrequired.com/mcp (banking service tools/list: `account`, `bank`). **Schema & dispatch:** Input/output `banking:src/mcp-account-tool.mjs:5-34`; dispatch `banking:src/mcp-account-tool.mjs:320-325`. Status enum `done, pending, approval_required, connect_required, failed` `banking:src/mcp-account-tool.mjs:26`.

## Actions / operations

| action | what happens | effect | source |
|---|---|---|---|
| `status` | Reads identity-check state: `verified`, `card`, `account` (euro IBAN), `status`, `next`, plus `approval_url` (KYC link) when one exists. An unknown provider user → `pending`, 'Call verify with email, country.' | read | `banking:src/mcp-account-tool.mjs:162` |
| `verify` | Starts or resumes onboarding. On first use it needs `email` and `country`; they are never echoed back. Later calls report progress and hand out the KYC link. After KYC it drives the euro account (SEPA path `wallet_link` → signature link, `standard` → provisioning, `wait`). `request_id` makes it idempotent: a repeat never opens a second account. | WRITE (creates provider account; personal data) | `banking:src/mcp-account-tool.mjs:174`, `banking:src/mcp-account-tool.mjs:125` |
| `limits` | `op: view` (default) shows `limits.wallet {per_payment, per_day, spent_24h}` and `limits.bank {per_transfer, per_day, spent_24h}`, with null = none. `op: set` with any of `wallet_per_payment, wallet_per_day, bank_per_transfer, bank_per_day` (like `5.00`), or `clear: ["wallet"|"bank"]`, returns an owner-pinned `approval_url` and a `request_id`. The same call after the tap applies it. The link expires after 24 h. | WRITE (permission change, owner tap) | `banking:src/mcp-account-tool.mjs:224` |

## Prerequisites

A connected `session` (connect link on first call). `limits` for the wallet side needs an existing agent wallet ('Call wallet balance first').

**Session / connect (shared by `wallet`, `account`, `bank`).** A call without `session` (or with a dead one) does not fail: the connection layer answers `status: "connect_required"` with an `approval_url` the person opens once, then the same call is repeated (`banking:src/mcp-connection.mjs:233`, `banking:src/mcp-connection.mjs:109`). `session` must match `SESSION_PATTERN` `banking:src/mcp-session.mjs:3`. A connect reference passed as `request_id` (43 chars) without `session` is resolved to the session first (`banking:src/mcp-connection.mjs:215`). For `account`/`bank` on a handed-off identity, a one-time *lazy bank grant* link may appear first (`banking:src/mcp-connection.mjs:153`).

**Per-action price.** Off by default. If the operator sets `TOOL_PRICES_JSON`, a priced action is paid from the caller's agent wallet *before* it runs, and an unpaid charge means the action does not run (`banking:src/tool-prices.mjs:1`, `banking:src/mcp-connection.mjs:293`). On banking the charge goes to the wallet service over RPC; if that peer is down the answer is `service_unavailable … Nothing was charged` (`banking:src/server.mjs:170`). Live price table: UNVERIFIED (env, not in repo).

## How to use it (exact `tools/call` JSON)

Endpoint: `POST https://banking.paymentrequired.com/mcp` (JSON-RPC 2.0). Placeholders in `<…>` are never real values; secrets (`session`, `wallet_key`) are never pasted into docs or logs.

**Check identity status (read-only)**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "account",
    "arguments": {
      "action": "status",
      "session": "<session>"
    }
  }
}
```

Expected result (`structuredContent`, shape from code; values illustrative):

```json
{"ok":true,"verified":true,"status":"done","card":true,"account":true,"next":"All set."}
```

**View limits (read-only)**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "account",
    "arguments": {
      "action": "limits",
      "session": "<session>"
    }
  }
}
```

Expected result (`structuredContent`, shape from code; values illustrative):

```json
{"ok":true,"limits":{"wallet":null,"bank":null},"next":"No limits set. Set them with account limits, op set."}
```

**Set limits (permission change, owner tap)**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "account",
    "arguments": {
      "action": "limits",
      "op": "set",
      "session": "<session>",
      "wallet_per_payment": "5.00",
      "bank_per_day": "50.00",
      "request_id": "lim-001"
    }
  }
}
```

Expected result (`structuredContent`, shape from code; values illustrative):

```json
{"ok":true,"status":"approval_required","approval_url":"https://…","request_id":"lim-001","next":"Show link. After tap: same call."}
```

## Approval, signature and money

`limits set` is applied only after `bridge.approved()` verifies the pinned owner's signature (`banking:src/mcp-account-tool.mjs:286`). `verify` may surface the provider's KYC link and the euro-account signature link. No money moves in this tool.

## Failure, peer-down and retry

On banking, every call first runs `accountReadyFor` (bounded 20 s account advance for links approved on another service) (`banking:src/server.mjs:135`). If the wallet peer is down, `limits` raises the transport sentence instead of crashing (`banking:src/server.mjs:157`). The onboarding map is in memory; the provider record is the source of truth.

**Peer-down / retry contract (cross-service RPC).** Calls between wallet and banking (and the apex directory's `helper`/`bridge` calls for issued links) go over loopback `POST /rpc/tool` with a bearer secret (`banking:src/service-rpc.mjs:50`). A refused connection answers `service_unavailable … Nothing was sent` (`banking:src/service-rpc.mjs:65`); any later failure answers `status: "unknown"`, `service_unconfirmed … repeat the same call with the same request_id` (`banking:src/service-rpc.mjs:56`). Rule for the agent: on `unknown`, repeat with the **same** `request_id`; never start a new one.

## What was actually observed

FIXTURE: banking `tools/list` `account` schema equals the directory pin (probe-schema 3/3); `limits` with a retained apex pass answered on banking direct in the accepted 99/99 run (no real Privy/Wirex). LIVE-RO 2026-10-14T18:09Z: `banking.paymentrequired.com/mcp` answers 503 'Service not deployed yet'. No live call (needs a person's session): UNVERIFIED live.
