Skip to content
LogoLogo

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 --pay

How 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
  1. The CLI fetches the URL. Anything but a 402 comes back untouched, so this doubles as a plain fetch.
  2. The 402 carries the price in the PAYMENT-REQUIRED header. It is checked against your limits, and over a cap it is refused before anything is signed.
  3. If the payer is short on USDC, it pulls the shortfall from the owner account through the on-chain permission.
  4. 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.
  5. 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.

SchemeThe 402 statesWhat movesSigned with
exacta pricethat priceEIP-3009, a USDC transfer authorization
uptoa ceilinganything from zero up to itPermit2, 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/month

The 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.

AddressRole
Ownerprinted by jaw session setup --x402Holds the USDC. Granted the permission. Fund this one.
Payerthe session key EOASigns 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.

CapDefaultWhere it comes from
maxAmountPerPayment1 USDCConfig, or the built in default
maxTotalPerSession10 USDCConfig, or the built in default
per periodnoneSeeded from the on chain grant
--max-amountnoneOne 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:8453

Amounts 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/day

A 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:

NetworkCAIP-2Chain ID
Baseeip155:84538453
Base Sepoliaeip155:8453284532
Polygoneip155:137137
Polygon Amoyeip155:8000280002

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.

FlagDescription
--payActually sign and send the payment
--max-amountCeiling in base units for this call, on top of the policy
--methodHTTP method (default GET)
--bodyRequest body
-o, --outputhuman 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 50000

Payments 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 json

Start 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
FlagDescription
--limitOnly the most recent N entries
--statusFilter 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:

ToolDescription
jaw_pay_and_fetchFetch a URL, paying a 402 if one appears
jaw_discoverSearch the x402 Bazaar for paid services
jaw_x402_balanceThe payer's USDC balance on a network
jaw_x402_logThe 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.