{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.
On a deployment with no custody provider bound, every route below answers
501 feature_not_configured.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.
Create the wallet
POST /v1/identities/{identity}/wallet — scope billing:write
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.
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 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.
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.
GET …/wallet/transactions?kind=pay lists the receipts; each carries the resource’s origin.
Agent tools
A session whose identity owns the wallet getswallet.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.