Buy as an agent#
No account. No API key. No subscription. You need a wallet with USDC and nothing else, not even a gas token, on either chain.
Do not want to hold crypto? Give your agent a wallet
mechawallet can hold a small USDC float for your agent and sign for it.
Then paying any x402 URL is one call, pay_x402(url) over MCP or
POST /v1/spending-wallets/base/pay {"url": …} with a live key, and the
answer is the resource. Limits you set bound every call. See
Your wallet.
Install#
pip install "pay402[evm]" # Base
pip install "pay402[solana]" # Solana
pip install "pay402[all]" # both
The bare install is httpx and nothing else, so it goes into a locked-down
agent sandbox without an argument; the chain SDKs are extras because a buyer
paying on Base has no reason to carry a Solana library. pypi.org/project/pay402
The whole thing in one call#
from pay402 import pay
goods = pay(
"https://api.mechawallet.com/c/chk_abc123",
private_key=os.environ["AGENT_KEY"],
max_usd="1.00",
)
goods.payload # what you bought
goods.receipt.url # proof you bought it
pay402 https://api.mechawallet.com/c/chk_abc123 \
--key $PAY402_KEY --max-usd 1.00
--key defaults to $PAY402_KEY, which keeps it out of shell history.
Exit codes are 0 paid, 3 over your ceiling, 1 everything else, so a
script can tell "too expensive" from "broken" without parsing text.
import { pay } from "./pay402.js";
const goods = await pay("https://api.mechawallet.com/c/chk_abc123", {
privateKey: process.env.AGENT_KEY,
maxUsd: "1.00",
});
There is no JavaScript package to install: pay402.js is a zero-dependency
reference implementation to read and port, and the pay402 name on npm
belongs to an unrelated author. Python is the supported client.
That's request → read the 402 → sign → retry with X-PAYMENT → goods + receipt.
max_usd is required for live payments
An agent looping on a paid endpoint with no ceiling is the failure mode that matters. pay() refuses rather than defaulting to permissive. Nothing is signed until the amount passes your cap.
Since 0.3.0: the receipt announces itself, and refunds get an address
On success pay() prints pay402: receipt <url> to stderr, so the
buyer's one durable proof lands in the transcript of whatever ran the
purchase even when the caller drops the return value (announce=False
opts out; the CLI prints its own line). And refund_to=, CLI
--refund-to, env $PAY402_REFUND_TO, records a wallet you
control on the receipt: paying wallets are usually throwaway, so
"refund to sender" would refund into the void. Recorded, never spent
from. Lost a receipt anyway? GET /r/by-tx/<tx_hash> walks back from
the transaction hash. The delivery promise behind all of this ,
settlement runs after delivery, a failed delivery is never charged ,
is written down at
api.mechawallet.com/guarantees.
Which endpoints the client can pay#
pay402 speaks both x402 dialects (v1 with X-PAYMENT, v2 with
PAYMENT-REQUIRED and PAYMENT-SIGNATURE) and two kinds of server:
- Our checkouts, which settle through the splitter and expose a prepare step the client verifies before signing.
- Standard x402 endpoints, anyone's: the client builds and signs the
EIP-3009 authorization itself on Base, or the exact-scheme transfer on
Solana (which needs an RPC for the blockhash:
solana_rpc_url=orPAY402_SOLANA_RPC; the wallet service passes its own), retries the same request with the payment header, and reads the settlement from the response header.paramsride on the query for GET and in the JSON body for POST (method="POST").
Not yet: endpoints that ask for the Permit2 or ERC-7710 transfer methods, and the batch-settlement scheme. The client says so instead of guessing.
What can my agent buy?#
The catalogue is organised by capability (search, read, browse, compute, inference, chain, data, storage) and by provider; What your agent can buy is the tour. The mechanics below are the same for every row.
The directory lists every live checkout here and the third-party x402
endpoints we have reviewed, with prices read from each endpoint's own 402
rather than copied from a list:
curl "https://api.mechawallet.com/v1/directory?q=weather&chain=base&max_price_usd=0.05"
Over MCP the same question is search_endpoints(query), and every row's
pay field is the pay_x402(url) call that buys it. Filters: q (words),
chain (base or solana), max_price_usd (rows without a known price are
dropped when a ceiling is set) and category. A third-party row that stops
answering 402 stays listed with status: not_402 or unreachable, so the
directory never quietly shrinks. People get the same list at /explore.
Every row has its own page at /explore/{entry_id}: what it is, the price
last read from its 402, how an agent buys it over MCP and over HTTP, and the
reviews from accounts that paid it. Without an Accept: text/html header
the same URL answers the row as JSON with its reviews. An endpoint's owner
can show /explore/{entry_id}/badge.svg in a README, and /sitemap.xml on
the API host lists every page for crawlers.
Look before you spend#
from pay402 import quote
q = quote("https://api.mechawallet.com/c/chk_abc123")
q.total_usd # Decimal('0.1105') , a $0.10 seller price
q.fee_usd # Decimal('0.0105') , 0.5% of $0.10, plus the $0.01 fixed part
q.description # what you'd be buying
quote() reads the 402 and spends nothing. The unpaid challenge carries the offer card, schemas, examples, freshness, coverage, so you can decide suitability first.
Rehearse before you buy#
Every checkout has a test face: the same URL plus /test. Same product, same
origin call, simulated settlement, synthetic 0xtest_… hash, no funds, so
you can prove your whole loop before signing anything:
pay("https://api.mechawallet.com/c/chk_abc123/test", test_payer="0xAnyAddress")
No key, no USDC, no ceiling. A live face's 402 names the rehearsal address
under endpoint.rehearsal.test_url, so an agent finds it without string
surgery. Rehearsals never consume the sale, a single-shot checkout is still
buyable after any number of them, and origin-backed rehearsals are budgeted
per checkout each day, so past the cap you get a 429 pointing at the live URL.
Doing it by hand#
1. Read the challenge#
curl https://api.mechawallet.com/c/chk_abc123
{
"x402Version": 1,
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"maxAmountRequired": "110500",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x234fE2A1D1df93fD9B907F0e1ad0c8b94E81FB5c",
"extra": {
"providerAmount": "100000",
"feeAmount": "10500",
"providerWallet": "0xSeller...",
"feeRecipient": "0xFee..."
}
}]
}
Amounts are atomic USDC (6 decimals) as strings, exact at any size, and JavaScript can't silently round them.
2. Sign, per family#
One checkout may offer several rails at the same dollar price. Pick whichever you can pay.
payTo is the immutable splitter contract. Sign an EIP-3009
ReceiveWithAuthorization for the exact total.
Let the service build the typed data rather than recomputing the nonce commitment yourself:
curl -X POST https://api.mechawallet.com/c/chk_abc123/prepare \
-H "content-type: application/json" \
-d '{"payer": "0xYourAddress", "network": "eip155:8453"}'
It returns typed_data to sign and a payload_stub to fill in. The server
signs nothing and holds no key.
Program-less: there's no splitter, and payTo names the seller.
/prepare builds it for you, same as on Base:
curl -X POST https://api.mechawallet.com/c/chk_abc123/prepare \
-H "content-type: application/json" \
-d '{"payer": "<your pubkey>", "network": "solana:5eykt4Us…"}'
It returns an unsigned transaction, your payer_signer_index, and the
fee_payer. Fill in only your own signature slot and send it back:
{ "transaction": "<base64>", "payer": "<your pubkey>" }
The relayer co-signs as fee payer at signer slot 0, so you need no SOL.
Verify before you sign
Because the payee builds the transaction, signing it blind means signing
whatever it contains. Decode it first and require exactly: two
transfer_checked instructions, extra.providerAmount to the seller's
ATA and extra.feeAmount to extra.feeRecipient's ATA, sourced from
your own ATA, no other program, and no address-table lookups (which can
substitute accounts you never saw). pay402 does this in
_verify_svm_tx and refuses on any mismatch, port that check, don't
skip it. Building the transaction yourself is also still supported, and
removes the question entirely.
3. Retry with the payment#
curl https://api.mechawallet.com/c/chk_abc123 \
-H "X-PAYMENT: $(echo -n '{"network":"eip155:8453","payload":{...}}' | base64)"
200 with the goods, and X-PAYMENT-RECEIPT carrying your receipt.
Buying a package#
Some checkouts sell N calls for one payment instead of one call. You pay once, on-chain, and get a key that spends down a counter. Nothing after the first call touches a blockchain, no signature, no gas, no confirmation wait.
The 402 tells you before you pay:
"endpoint": { "title": "Company financials", "fulfillment": "relay" },
"accepts": [{ "maxAmountRequired": "25135000",
"extra": { "providerAmount": "25000000", "feeAmount": "135000" } }]
What settlement hands back#
Paying a package returns three things at once:
{ "package_key": "pk_DtwKsFW8AkVUoQsqMidRriY4Vw0eFbrJlQ4fRu8XhbM",
"calls_remaining": 499,
"receipt": { "url": ".../r/pay_EVh0qwV4h9O7l-OA", "tx_hash": "0x…" },
"fulfillment": { "mode": "relay",
"payload": { "company": "ACME AG", "revenue_chf": 4200000 } } }
- The key,
pk_… - Your first call's data, the purchase is call 1, which is why 500 calls leaves 499
- A receipt with the on-chain proof
The key is shown once
Only its hash is stored, so a database leak hands out no live packages, and so we cannot reissue one you lose. Store it at purchase time like any other credential. There is no recovery path.
Spending it#
The same URL you bought. Forever, until the counter runs out:
curl "https://api.mechawallet.com/c/chk_abc123?symbol=ACME" \
-H "Authorization: Bearer pk_DtwKsFW8…"
{ "fulfillment": { "mode": "relay", "payload": { … } },
"calls_remaining": 498,
"receipt": "/r/pay_EVh0qwV4h9O7l-OA" }
Query params pass through to the seller. On an MCP checkout, name the tool:
?tool=search&q=acme. On a POST/PUT/PATCH origin (the 402 shows
endpoint.request_body), send the request body as the reserved body param ,
one JSON string, e.g. ?body={"query":"…"}, and it arrives at the origin as
the actual application/json body, bound to your payment.
What the key is, exactly#
One key, one purchase. Not an account, not a subscription, closer to a prepaid ticket with a serial number.
| Scope | The single payment that created it, at one checkout |
| Used on another checkout | 401 unknown package key, the same error as a key that does not exist, so probing tells you nothing |
| Bought the same checkout twice | Two keys, two independent counters |
| Identity | The key is the identity. You never registered |
It also outlives the listing. Your entitlement belongs to your payment, not to the seller's checkout, so it keeps working after the checkout is marked paid, after it expires, and after the seller retires it. Retiring stops new sales; it does not confiscate what you already bought.
Checking the balance costs nothing#
curl "https://api.mechawallet.com/c/chk_abc123/usage" \
-H "Authorization: Bearer pk_DtwKsFW8…"
{ "calls_included": 500, "calls_used": 2, "calls_remaining": 498,
"purchased_at": "…", "receipt": "/r/pay_…",
"calls": [
{ "seq": 1, "at": "…", "status": "delivered", "counted": true,
"latency_ms": 39, "bytes": 64 },
{ "seq": 2, "at": "…", "status": "delivered", "counted": true, … }
] }
Every call is itemised, and counted tells you whether it was charged, a
failure that was not your fault is visibly not deducted. This endpoint spends
nothing, so poll it as freely as you like.
When a call fails, who pays#
Not every failure is refunded, and the rule is whose fault it was.
| The seller's origin returns | Charged? |
|---|---|
5xx, a timeout, unreachable |
No, handed straight back |
401, 403, 429 (their credential, their capacity) |
No |
400, 404, 422, your request was wrong |
Yes |
A buyer-fault failure comes back as the origin's own status (a 404, not a
502) with "fault": "buyer", so you can fix the request instead of guessing.
/usage marks every call counted: true|false, so "was I charged for that
one" is always answerable.
The asymmetry is deliberate. Refunding a bad request would make "does record X exist?" free whenever the answer is no, the seller's origin would absorb every probe and you would only pay for hits.
Packages expire#
30 days from purchase by default, and the seller may set their own. The
terms are in the 402 before you pay:
"endpoint": { "package": { "calls": 500, "expires_in_days": 30 } }
/usage carries expires_at and a burn block telling you which runs out
first, the calls or the clock:
"burn": { "calls_per_day": 12.4, "projected_days_remaining": 21.3,
"runs_out_first": "expiry", "basis": "trailing_7d" }
Keys sold before expiry existed carry expires_at: null and never expire.
If something goes wrong#
You can report it, and you do not need an account:
curl -X POST https://api.mechawallet.com/v1/checkouts/chk_abc123/reports \
-H "content-type: application/json" \
-d '{"category": "not_delivered", "receipt_id": "pay_xyz"}'
Every delivery failure carries the channel in its help.report block, so an
agent finds it at the moment it has something to report. Supplying a receipt id
or package key files it as a buyer report with the payment attached, which is
much stronger than an anonymous one, the seller sees your claim beside our own
record of what actually happened.
Settlement is non-custodial, so we cannot refund you. What reporting does is make the problem visible to the seller and to us, quickly, with evidence.
What a package does not give you#
Worth knowing before you buy a large one:
- No refund path. Settlement is non-custodial: the USDC moved from you to the seller in one transaction at purchase. Nothing is held back, so nothing can be returned.
- No guarantee the response shape stays the same. The seller controls their own endpoint. We can tell you what it returned; we cannot promise it will keep returning that.
- No expiry, unspent calls do not lapse. Which is the same fact read the other way: the commitment you are buying runs indefinitely, so its size is your exposure.
Protected purchases (escrow)#
A checkout whose URL starts /e/ holds your payment in an on-chain escrow
instead of paying the seller instantly. The 402 carries the full terms in
accepts[].extra.escrow, read them before signing, because they are inside
what you sign: the windows, the arbiter, and both exit routes. A plain
checkout advertising extra.escrowAvailable has a protected twin; pay that
URL instead if you want the protection (it costs 1% instead of 0.5%).
After paying you hold real controls, no account needed:
POST /e/<id>/actions/hold?payment_id=… # pause the auto-release
POST /e/<id>/actions/approve?payment_id=… # release to the seller now
POST /e/<id>/actions/request-refund?payment_id=… # ask the arbiter to decide
GET /e/<id>/status?payment_id=… # live state + what happens next
Each returns a transaction only your paying wallet can complete. On Base you
broadcast it yourself (sub-cent). On Solana, sign your slot and POST the
base64 to the matching …/submit endpoint, mechawallet pays the fee, so you
never need SOL. Do nothing and the money releases to the seller when the
settle window ends; dispute and nobody rules, and you are refunded
automatically at the backstop, permissionlessly, so your money cannot be
stranded even if every party including mechawallet disappears.
Failure modes#
| Status | Meaning | Do this |
|---|---|---|
402 |
Not paid, or the payment was rejected | Read detail.help, it names the fix |
409 |
Replay of a settled authorization | You already have the goods; re-present to redeliver |
502 |
Settlement failed on-chain | Re-read the 402 and sign fresh; windows expire |
Every 402 body carries a help block with the exact next call. It's written for you to act on, not for a human to read.
Verifying a receipt without trusting us#
The receipt names a transaction hash. Check it against the chain yourself, the amounts, the seller, and the payer are all on-chain. You never have to take our word for a payment.