> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallet

> A custodied, spend-limited USDC wallet per identity, and x402 payments for paywalled resources.

Every route is nested under an identity. `{identity}` accepts an `idn_…` id or the identity's name.
An identity has at most one live wallet **per network**. Amounts are integer micro-USD
(`1 USD = 1000000`); USDC's six decimals map onto it one-to-one. Networks are CAIP-2: `eip155:84532`
(Base Sepolia, the default) and `eip155:8453` (Base mainnet, enabled per organization by its owner).

Every route below selects the wallet with `?network=` (`POST …/wallet` names it in the body); the
testnet wallet is selected when it is omitted. A route never answers with another network's wallet:
reading, funding or paying on a network the identity has no wallet on is `404 not_found`, and a
value that is not one of the two networks is `validation_failed` with `param: network`.

<Note>On a deployment with no custody provider bound, every route below answers `501 feature_not_configured`.</Note>

## Read the wallet

`GET /v1/identities/{identity}/wallet` — scope `agents:read`

Answers the selected network's `wallet` with its live `balance_micro_usd` and `spent_today_micro_usd`,
or `404 not_found` when the identity has none on that network. A swept wallet is retired and no
longer answers here.

```json theme={"system"}
{
  "id": "wlt_01j9x0v6m3q8r2k5n7p4t1w9ya",
  "object": "wallet",
  "identity_id": "idn_01j9x0v6m3q8r2k5n7p4t1w9yb",
  "status": "active",
  "network": "eip155:84532",
  "address": "0x4f3e9c2b1a8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f",
  "asset": "usdc",
  "balance_micro_usd": 4750000,
  "policy": { "per_tx_cap_micro_usd": 500000, "daily_cap_micro_usd": 20000000, "low_balance_micro_usd": null },
  "spent_today_micro_usd": 250000,
  "created_at": "2026-09-01T10:00:00Z",
  "updated_at": "2026-09-08T09:12:44Z"
}
```

The response never carries a private key or a custody reference; the provider keeps the key and
Vetta holds only the address, the policy and the ledger.

## Create the wallet

`POST /v1/identities/{identity}/wallet` — scope `billing:write`

```json theme={"system"}
{
  "network": "eip155:84532",
  "policy": { "per_tx_cap_micro_usd": 500000, "daily_cap_micro_usd": 20000000, "low_balance_micro_usd": null }
}
```

Both fields are optional. The policy is attached at the custody provider **before** the wallet
exists, so a wallet without a cap is never observable. Answers `201`, or `200` with the identity's
existing wallet on that network — a wallet on the other network neither blocks nor is returned, so
one identity may hold a testnet and a mainnet wallet side by side. A wallet's network never
changes after creation.

## Policy, freeze, unfreeze

`PATCH /v1/identities/{identity}/wallet/policy` — scope `billing:write`

`POST /v1/identities/{identity}/wallet/freeze` — scope `billing:write`

`POST /v1/identities/{identity}/wallet/unfreeze` — scope `billing:write`

A cap change is pushed to the provider first. `freeze` pushes a per-transaction cap of `0`; every
spend against a `frozen` wallet answers `state_conflict`.

## Fund, transfer, sweep

`POST /v1/identities/{identity}/wallet/fund` — scope `billing:write`

`{ "amount_micro_usd": 1000000, "source": "faucet" }` — `faucet` is testnet-only and grants the
faucet's fixed amount (1 USDC per request; the recorded transaction carries what actually arrived).
`treasury` draws `amount_micro_usd` from the organization's treasury wallet and answers
`501 feature_not_configured` until the owner has provisioned one. Answers the `wallet_transaction`.
Honours `Idempotency-Key`.

```json theme={"system"}
{
  "id": "wtx_01j9x0v6m3q8r2k5n7p4t1w9yc",
  "object": "wallet_transaction",
  "wallet_id": "wlt_01j9x0v6m3q8r2k5n7p4t1w9ya",
  "kind": "fund",
  "direction": "in",
  "amount_micro_usd": 1000000,
  "counterparty": null,
  "origin": null,
  "purpose": "faucet",
  "tx_hash": "0x9c1f…",
  "session_id": null,
  "status": "confirmed",
  "created_at": "2026-09-08T09:12:44Z"
}
```

`POST /v1/identities/{identity}/wallet/transfer` — scope `billing:write`

`{ "to": "0x…", "amount_micro_usd": 250000, "purpose": "API credit" }` — `to` is an address or a
sibling identity's `idn_…` in the same organization (the sibling's ledger records the matching
`in` transaction). A sibling is resolved to its wallet on the **sender's** network; a sibling with no
wallet there is `validation_failed` (`param: to`), never a cross-network transfer. `purpose` is required. Capped by the per-transaction and daily caps
(`validation_failed`), refused when the balance is short (`validation_failed`,
`insufficient_balance`). Honours `Idempotency-Key`.

`POST /v1/identities/{identity}/wallet/sweep` — scope `billing:write`

Drains the balance to `{ "to": "0x…" }` (or to the organization's treasury when omitted — `501
feature_not_configured` until one is provisioned) and retires the wallet as `swept`. A retired
wallet does not block `POST …/wallet` from provisioning a fresh one.

## Transactions

`GET /v1/identities/{identity}/wallet/transactions` — scope `agents:read`

A [paginated list](/docs/api/pagination) of the selected network's `wallet_transaction` objects.
Filters: `network`, `kind` (`fund | transfer | pay | sweep`), `direction` (`in | out`), `origin`,
`since`. x402 receipts are `?kind=pay`.

## Pay for a paywalled resource

`POST /v1/identities/{identity}/wallet/quote` — scope `agents:read`

`{ "url": "https://api.example.com/report", "method": "GET" }` — fetches the URL and answers a
`wallet_quote` describing what it asks for, or `payable: false`. Zero side effects: nothing is signed
and nothing is recorded. Only public hosts are fetched and redirects are never followed; a private,
loopback or link-local address is `validation_failed`.

```json theme={"system"}
{
  "object": "wallet_quote",
  "url": "https://api.example.com/report",
  "payable": true,
  "network": "eip155:84532",
  "asset": "usdc",
  "amount_micro_usd": 100000,
  "pay_to": "0x2c7e1f9a8b6d5c4e3f2a1b0c9d8e7f6a5b4c3d2e",
  "description": "Weekly market report",
  "max_timeout_seconds": 60
}
```

`POST /v1/identities/{identity}/wallet/pay` — scope `billing:write`

`{ "url": "https://api.example.com/report", "max_amount_micro_usd": 250000 }` — pays the smaller of
`max_amount_micro_usd` and the per-transaction cap, reserves the daily budget, retries the request
once with the payment attached and answers a `wallet_payment` with the response. A short balance
answers `validation_failed` (`insufficient_balance`); the organization's credits are never used.
Honours `Idempotency-Key`. `response_body` is truncated to the first 64 KiB.

```json theme={"system"}
{
  "object": "wallet_payment",
  "paid": true,
  "transaction_id": "wtx_01j9x0v6m3q8r2k5n7p4t1w9yd",
  "amount_micro_usd": 100000,
  "response_status": 200,
  "response_body": "{\"report\":…}"
}
```

`GET …/wallet/transactions?kind=pay` lists the receipts; each carries the resource's `origin`.

## Agent tools

A session whose identity owns the wallet gets `wallet.balance`, `wallet.transactions`,
`wallet.receipts`, `wallet.quote`, `wallet.pay` and `wallet.transfer`. The four reads run under
the agent's ordinary tool policy; `wallet.pay` and `wallet.transfer` move money and default to
`ask`, so the operator approves each one before it is signed. Provisioning, policy, funding and
sweeping are operator routes with no tool.

## Errors

`not_found` for an identity with no wallet; `validation_failed` for caps, a short balance, faucet
on mainnet, a self-transfer, or a requirement the wallet cannot meet; `forbidden` for a mainnet
wallet the organization has not enabled or a transfer to an identity outside it;
`state_conflict` for a write against a `frozen` or `swept` wallet; `rate_limited` when the faucet
is exhausted; `feature_not_configured` when custody or the treasury is not bound. See
[Errors](/docs/api/errors).
