Paying for APIs (x402)
Some HTTP endpoints answer a request with 402 Payment Required instead of the resource. The x402 protocol turns that status into a machine payable challenge: the server states its price, the client pays in USDC, and the same request goes through.
The JAW CLI implements the client side. It pays with a session key, so a terminal or an AI agent can buy an API call without a browser and without a passkey prompt per payment.
jaw x402 pay https://api.example.com/resource --payHow It Works
┌──────────────────────┐
│ Owner account │ Your USDC lives here.
│ granted the x402 │ A top-up pulls from it,
│ permission │ never above the granted cap.
└──────────┬───────────┘
│ 3. top up, only if the payer is short
▼
┌─────────┐ 1. GET ┌──────────────────────┐ 4. signed USDC ┌──────────────┐
│ jaw │ ───────────► │ Session key │ ────────────────► │ Facilitator │
│ CLI │ ◄─────────── │ (payer) │ │ settles │
└─────────┘ 2. 402 └──────────┬───────────┘ └──────┬───────┘
+ price │ │
│ every attempt appended │
▼ ▼
Local ledger 5. Resource
- The CLI fetches the URL. Anything but a
402comes back untouched, so this doubles as a plain fetch. - The
402carries the price in thePAYMENT-REQUIREDheader. It is checked against your limits, and over a cap it is refused before anything is signed. - If the payer is short on USDC, it pulls the shortfall from the owner account through the on-chain permission.
- The payment is signed with the session key and the request is retried carrying the proof. What gets signed depends on how the server priced the call: see Fixed Prices and Ceilings.
- The facilitator settles on chain and the server returns the resource.
Every attempt is appended to a local ledger, paid, refused or failed. See jaw x402 log.
Fixed Prices and Ceilings
A server prices a call in one of two ways, and the difference is worth understanding before you read a refusal.
| Scheme | The 402 states | What moves | Signed with |
|---|---|---|---|
exact | a price | that price | EIP-3009, a USDC transfer authorization |
upto | a ceiling | anything from zero up to it | Permit2, a witness bound to the recipient |
upto exists for calls whose cost is not knowable in advance, like model inference or a query priced by the rows it returns. You authorize a ceiling, the server does the work, and it charges what the work came to. The proxy that settles it refuses any amount above the ceiling, and refuses any caller but the one named in the challenge, so a signature is worth the ceiling and nothing more, to one recipient, once.
Two consequences follow, and both surprise people.
The caps measure the ceiling, not the expected charge. No cap can be enforced against a number the server has not picked yet, and until settlement the signature is worth its ceiling to whoever holds it. So an endpoint advertising a 5 USDC ceiling is refused under the default 1 USDC per payment cap even if it would have charged a hundredth of a cent. The refusal says up to 5000000 to make clear which number it is talking about. Raise the cap deliberately, or leave the endpoint unpayable.
A failed upto payment costs its whole ceiling against your caps. Once signed and sent, the authorization stays spendable up to the ceiling until it expires, and nothing yet proves it went unused. Counting the charge instead would let a run of failures spend far past the cap, so the caps hold the ceiling. It is deliberately pessimistic: repeated failures eat budget faster than repeated successes.
jaw x402 log reports the same figure the caps enforce, so a failed row shows what is held rather than what was attempted.
The One-Time Approval
Permit2 moves the token through the standard ERC-20 allowance, so the first upto payment on a chain sends one extra on-chain approval from the payer before it signs. It happens once per chain, automatically, and its gas comes out of the payer in USDC like any other operation.
It is the only call a session sends outside its permission, and it has to be: the permission manager checks every call against the granted list, which is transfers only. Being outside the permission is why it is deliberately narrow. It approves Permit2, for the registry USDC on the session's chain, and there is no argument that turns it into anything else.
Setup
You need an API key and a session with an x402 permission:
jaw config set apiKey=YOUR_API_KEY defaultChain=8453
jaw session setup --x402--x402 builds the permission from the asset registry, so the USDC address and the function signature are not written by hand. It grants a USDC transfer capped per period, 10/day by default. Tune it with --limit:
jaw session setup --x402 --limit 25/day
jaw session setup --x402 --limit 2.5/week
jaw session setup --x402 --limit 100/monthThe period accepts minute, hour, day, week, month, year and forever. A bare amount is read as per day.
Setup asks for one passkey approval in the browser. After that, payments are autonomous until the session expires.
Which Account To Fund
Two addresses are involved, and only one of them takes your USDC.
| Address | Role | |
|---|---|---|
| Owner | printed by jaw session setup --x402 | Holds the USDC. Granted the permission. Fund this one. |
| Payer | the session key EOA | Signs payments. Holds no float, pulls what it needs from the owner. |
jaw x402 status shows both, and marks the owner with <- funds go here.
Neither address needs a native token. The payment is gasless for the payer, since the facilitator pays that gas. The top-up goes through JAW's ERC-20 paymaster, which takes its fee in USDC from the owner account, so budget slightly more there than the prices you plan to pay.
Limits
Every payment is bounded by a policy plus the per call ceiling. Nothing is unbounded, even with no configuration.
| Cap | Default | Where it comes from |
|---|---|---|
maxAmountPerPayment | 1 USDC | Config, or the built in default |
maxTotalPerSession | 10 USDC | Config, or the built in default |
| per period | none | Seeded from the on chain grant |
--max-amount | none | One call only |
Under upto every one of these measures the ceiling the challenge advertised. See Fixed Prices and Ceilings.
The per-period caps mirror the permission and reset with it, so they are not settable from the CLI. A grant seeds one for every spend limit the permission puts on the token, and they replace the 10 USDC session default. JustaPermissionManager charges every limit matching a token rather than stopping at the first, so all of them apply and the tightest is what binds: a session holding 50 a day and 100 a month can move 50 today and no more than 100 across the month. jaw x402 status reports each one with its own used figure and reset time. A maxTotalPerSession you set explicitly still applies on top.
maxTotalPerSession is not a per-process cap. The running total is rebuilt from the ledger, so it survives restarts.
Read the live numbers with jaw x402 status rather than assuming the defaults.
Configuring the Policy
jaw config set x402.maxAmountPerPayment 500000 # base units, 0.5 USDC
jaw config set x402.maxTotalPerSession 5000000
jaw config set x402.topUpFloat 2000000 # refill target for the payer
jaw config set x402.allowedPayTo 0xabc...,0xdef... # comma separated
jaw config set x402.allowedHosts api.example.com
jaw config set x402.allowedAssets 0x8335...
jaw config set x402.allowedNetworks eip155:8453Amounts are in base units. USDC has 6 decimals, so 1000000 is 1 USDC.
Allow lists left unset mean "any", except assets and networks, which default to the registry USDC deployments below. A payment over a cap, or to a disallowed asset, network, host or recipient, is refused rather than paid.
These keys are deliberately unreachable from the MCP tools. An agent must not be able to raise its own spending caps, only a human at a terminal.
The same split covers what an agent may ask to be granted. An agent with shell access can run jaw session setup --x402 --limit itself, and without a bound the browser approval screen is the only check on the number it picks. Set a ceiling once, at a terminal:
jaw config set grantCeiling 10/dayA grant is refused when it asks for more than that allowance, or when it resets more often than that period, since the same allowance on a shorter period is more money over the same time. It is checked against the resolved permission, so a hand-written --permissions is bounded by it too. The ceiling is an amount of USDC, so with one set, a spend it cannot be priced against is refused rather than waved through: another token, or a chain with no USDC in the registry. Unset means no ceiling.
Supported Networks
USDC deployments the CLI knows about:
| Network | CAIP-2 | Chain ID |
|---|---|---|
| Base | eip155:8453 | 8453 |
| Base Sepolia | eip155:84532 | 84532 |
| Polygon | eip155:137 | 137 |
| Polygon Amoy | eip155:80002 | 80002 |
A server asking for any other asset is refused, so a lookalike token cannot be paid by mistake.
upto needs its settlement proxy deployed, which is verified on Base and Base Sepolia. A upto challenge on any other network is refused before anything is signed, since a permit whose settlement contract is not there could never be honoured and would still hold its ceiling against your caps.
Commands
jaw x402 pay
Fetch a URL, paying an x402 challenge with the session key.
jaw x402 pay <url> [flags]Dry run by default. Without --pay it does everything except spend, so a clean dry run means the real one would have been allowed.
| Flag | Description |
|---|---|
--pay | Actually sign and send the payment |
--max-amount | Ceiling in base units for this call, on top of the policy |
--method | HTTP method (default GET) |
--body | Request body |
-o, --output | human or json |
# See what it would cost, spend nothing
jaw x402 pay https://api.example.com/resource
# Pay it
jaw x402 pay https://api.example.com/resource --pay
# Pay, but never more than 0.05 USDC for this call
jaw x402 pay https://api.example.com/resource --pay --max-amount 50000Payments are only signed for https URLs, or localhost for testing. A 402 over cleartext http is refused, because a network attacker could rewrite the recipient in the challenge. Free http fetches still work.
jaw x402 status
Show payment readiness: which account holds the funds, the resolved caps, and what has been spent. Reads only, never pays.
jaw x402 status
jaw x402 status --output jsonStart here when a payment is refused. It names the reason: an empty owner account, funds sitting in the payer, a period allowance used up, or an expired session.
jaw x402 log
Show the local payment ledger: every attempt, paid, failed or refused, with amounts and transactions.
jaw x402 log
jaw x402 log --limit 20
jaw x402 log --status failed
jaw x402 log --output json| Flag | Description |
|---|---|
--limit | Only the most recent N entries |
--status | Filter by paid, failed or refused |
The ledger is also what maxTotalPerSession counts against, so it is the record of truth for the session cap, not a convenience log.
The amount on a row is what the caps counted for it, which for a settled payment is what was charged and for a failed one is the ceiling it still holds. Under exact those are the same number, so only an upto row can show them apart.
For AI Agents
The MCP server exposes the same capability as tools:
| Tool | Description |
|---|---|
jaw_pay_and_fetch | Fetch a URL, paying a 402 if one appears |
jaw_discover | Search the x402 Bazaar for paid services |
jaw_x402_balance | The payer's USDC balance on a network |
jaw_x402_log | The local ledger of every attempt |
The server also publishes a jaw://x402 resource: a self contained guide to funding, limits and the untrusted content rules. Agents should read it before paying.