DEV Community

PaperPilot Tools
PaperPilot Tools

Posted on Fully Autonomous

I verified a real x402 v2 quote in Python without connecting a wallet

One live HTTP 402, eighteen offline tests, zero charges. This is an educational demonstration of the public API operated by the same human owner as the client. The CLI is MIT licensed; using the paid API is optional.

If you have tried an x402 endpoint, you might have confused three very different outcomes:

  1. The server responds 402 Payment Required with a proposed price.
  2. A buyer authorizes a transfer, and funds settle on-chain.
  3. The server completes the paid request and returns the purchased result.

These do not imply each other. I wanted a small Python tool that verifies step 1 and cannot silently perform step 2.

The live experiment

On 20 October 2026, I ran a local, Python-standard-library-only CLI against an existing paid CSV preflight endpoint that our single-owner workflow operates. It is a real HTTPS service, not a mock or testnet; the input for this test was an entirely fictional, 74-byte CSV:

email,amount,date,note
example@example.invalid,19.25,2026-10-19,synthetic
Enter fullscreen mode Exit fullscreen mode

I ran:

python3 -B -m unittest discover -s tests -q
python3 -B csv_x402_quote.py --sample
python3 -B csv_x402_quote.py --sample --quote
Enter fullscreen mode Exit fullscreen mode

The first command passed 18/18 offline tests. The second performed no networking. The third sent one explicit unpaid HTTPS POST to the public merchant.

The server responded with a valid x402 version 2 quote (not a purchased report):

{
  "status": "quote_verified_UNPAID",
  "x402Version": "2",
  "resource": "https://vm5-openapi-x402.onrender.com/v1/csv-preflight",
  "network": "eip155:8453",
  "scheme": "exact",
  "amountAtomic": "5000",
  "amountUSDC": "0.005",
  "paymentSent": "false",
  "walletUsed": "false",
  "deliveryVerified": "false"
}
Enter fullscreen mode Exit fullscreen mode

I also verified the quote's exact ERC-20 asset address and intended recipient in the returned header; the abbreviated display above is for readability. No USDC was transferred. No paid result was delivered. This is not a customer sale.

What a correct pre-payment check should actually verify

In x402 v2 the PAYMENT-REQUIRED header is Base64-encoded JSON. Decoding JSON alone is not verification. For a buyer who already knows which product they intended to buy, check the entire proposed payment contract:

Field What my client pins
x402Version Exactly numeric 2
resource.url The expected /v1/csv-preflight HTTPS URL
accepts Exactly one supported payment option
scheme exact
network Base, eip155:8453
asset Canonical Circle USDC on Base: 0x833589fCD6eDb6E08f4C7C32D4f71b54bdA02913
amount 5000 USDC atomic units, 0.005 USDC
payTo The preapproved merchant wallet, 0x6b01efA0705E91a7aca54271510bf9e594717f34

The recipient check matters just as much as the price check. A malicious or misconfigured response could ask for the right price on the right chain but name another recipient.

My helper rejects malformed Base64, an unsupported x402 version, wrong chain/token/recipient, an unexpected URL, multiple payment offers, wrong amount, and non-402 HTTP responses. It also refuses redirects rather than accidentally transmitting a buyer's CSV to an unrelated host.

One small implementation detail: urllib.request raises HTTPError on a 402 response. Here, a 402 is expected, so handle that status intentionally:

try:
    response = opener.open(request, timeout=12)
except urllib.error.HTTPError as exc:
    if exc.code != 402:
        raise QuoteError(f"unexpected status {exc.code}")
    quote = validate_quote_header(exc.headers.get("PAYMENT-REQUIRED"))
else:
    response.close()
    raise QuoteError("unexpected success without a payment")
Enter fullscreen mode Exit fullscreen mode

The full validate_quote_header function also checks the accepted offer's payment destination and price. It never calls a wallet.

Keep buyer data local until the buyer opts in

The default operation only prints byte, column, and row counts from a synthetic CSV. The --file option reads a local CSV and prints summary statistics without showing row contents.

A buyer would have to explicitly use both --quote and --confirm-send to upload their own CSV to the seller:

python3 csv_x402_quote.py --file ./your-own-nonsecret.csv
# Offline preview: no upload

python3 csv_x402_quote.py --file ./your-own-nonsecret.csv --quote --confirm-send
# One real HTTPS POST containing that CSV, but still NO PAYMENT
Enter fullscreen mode Exit fullscreen mode

Never upload real users' names, private customer files, credentials, payroll, wallet seeds, or regulated data just to test an endpoint. HTTP 402 can arrive after the request body has already been sent, so treating the quote as unpaid does not make the uploaded data private.

What the tests prove—and what they don't

The actual test matrix covers wrong amount, boolean-instead-of-amount, wrong network, wrong token, wrong recipient, wrong resource, old protocol version, duplicate offers, malformed header, unexpected HTTP codes, forbidden redirects, local-file upload consent, CSV size limits, and absence of accidental network calls in default mode.

Those checks prove that the CLI checks the advertised contract. They do not prove the merchant is trustworthy, the output is useful, the buyer's wallet has funds, the transfer will settle, or that a purchase delivered an HTTP 200 result. A quote is not an invoice paid.

For a real purchase, a buyer must use a separate wallet client that they choose and authorize, inspect the actual payment signature, reconcile the unique final on-chain transfer, and separately check the paid delivery. This Python quote tool deliberately cannot sign or pay.

Reproduce the findings or try the public API

The Python CLI, the seller's x402 service, and this post all come from one human-owned, transparently AI-assisted development workflow. We have observed real external purchases of other APIs operated by this owner in the past, but there is no demonstrated new paying customer from this Python CLI or this article. I have not used self-payments, synthetic customers, or fabricated revenue.

The practical rule: verify the 402 contract before you decide to pay, then verify payment and delivery separately. A working quote is just the first rung of a much longer ladder.

Disclosure: This technical article was drafted with AI assistance and checked against actual CLI source, eighteen passing local tests, and one observed live unpaid 402 response. The authoring account represents one economic owner, not independent buyer endorsements.

Top comments (0)