402 Payment Required and a payment-required header describing what it
accepts. The Payments primitive gives each agent its own crypto wallet (USDC
on Base) and lets it settle those 402s autonomously: quote the price, sign a
USDC payment, retry the request, keep the receipt.
Payments is opt-in. Unlike most primitives it is disabled in a fresh
AccountKit and must be enabled explicitly — see
Account Kits. The wallet is per-agent: there is
no company-level fallback mount, so CLI and API calls need a subject
(
naive use <user>).CLI First
Tools
The wallet’s admin verbs (
fund, transfer, policy, sweep) are deliberately
not MCP tools and not in the SDK — they are the operator surface only. See
Governance.How it works
pay is the full buy-side flow. quote runs only the first two steps, which is
why it has no side effects at all.
1
Fetch
The resource is fetched normally. If it returns anything other than
402,
nothing was paywalled — pay returns the body with paid: false and no
receipt is written.2
Read the requirements
On a
402, the payment-required header is decoded into a list of
accepts options (amount, asset, network, payTo).3
Select under policy
The options are filtered against the wallet’s
perTxMax, the per-call
max_amount, and the allowed networks. The effective cap is
min(max_amount, perTxMax).4
Check balance and budget
The wallet balance and (if set) the rolling
dailyBudget counter are
checked. A refusal here happens before anything is signed and emits a
payment.denied event.5
Sign
The custody provider signs an EIP-3009
TransferWithAuthorization. The
provider re-enforces perTxMax inside the custody plane at signing time.6
Retry once
The request is retried with a
PAYMENT-SIGNATURE header attached.7
Hard stop
If the retried request fails, that is the end — there is no second
retry. A
payment.failed event is emitted and the call throws.
On success, the payment-response envelope is stored and a receipt is
written.Quote
free: true with the real HTTP
status and an empty accepts — fetch it normally, no payment needed.
Pay
Receipts
Every settled payment is recorded.direction is buy (paid out) or sell
(received).
GET /v1/users/{user_id}/payments/receipts and GET /v1/users/{user_id}/wallet/balances
return bare arrays, not wrapped objects. Receipt and wallet JSON fields are
camelCase (txHash, payTo).Spend control
This is the part that most deserves reading. Unlike Cards and Trading, payments have no approval workflow — there is nopending_approval, no approval queue, and that is deliberate, not an
omission. Three controls bound a payment instead:
Why no approval prompt?
perTxMax is enforced twice: once at runtime in
the pay flow, and again as a static account policy inside the custody plane,
checked when the payment is signed. That second check holds even if the agent
runtime is fully compromised — a guarantee an approval prompt cannot make, since
a compromised runtime can simply stop asking. Fund the wallet with what you can
afford to lose, set perTxMax, and the ceiling is structural.--max-amount (or max_amount) adds a per-call ceiling on top of
perTxMax for one specific payment.
Governance
The wallet splits into two surfaces. Per spec §3.7, the admin verbs are operator-surface only — not agent tools:
The operator verbs are gated server-side by
enforceWalletAdmin, and the CLI
refuses them client-side too. An agent key is refused. They are also absent
from the MCP tool list and the SDK entirely — non-exposure plus an explicit gate.
An AccountKit can additionally deny wallet administration for a user by listing
the payments.wallet.admin capability in
primitives_config.payments.capabilities.deny.
Agents as code
Declare a wallet on an agent withhas.crypto. This implies the payments slug
in the allowlist and adds a crypto_wallet provisioning step.
Configuration
Network: USDC on Base (
eip155:8453).
When
WALLET_ENABLED is false, the crypto_wallet provisioning step resolves to
needs_action — it never fails the deployment. With WALLET_PROVIDER=cdp,
credentials are BYO from the tenant vault; missing creds produce a typed
WalletNotConfigured error and a “connect your CDP account” prompt rather than an
opaque failure.