402xAgent · Pricing · Docs · Blog

Blog · Oct 27, 2026

Check an x402 Endpoint Before Your Agent Pays (One Free curl)

At 13:20 on October 27, 2026 (UTC-7), we checked two top weather endpoints from the Coinbase CDP Bazaar. Both answered with a valid 402. One asked a different price than its listing.

Your x402 client would have paid either. Here's the free call that told them apart, with real output.

The request

No signup, key or wallet. You get 5 free live checks per client per UTC day, and dry runs (&mode=dry-run) are free (pricing, docs):

curl "https://api.402xagent.com/v1/products/endpoint-spot-check?url=https://x402.agentindex.world/weather"

The endpoint is a top "weather forecast" result on our daily Bazaar check. 402xAgent sends one unpaid probe and never pays the target.

The response (real, trimmed)

Run 2026-10-27, 13:20 UTC-7 (20:20 UTC). HTTP 200. Fields cut for length, none edited:

{
  "verdict": "pay",
  "reason": "listed $0.001, payment request matches the listed price",
  "reason_code": "price_ok",
  "confidence": "high",
  "payment_terms_sha256": "a6eccc04d6a82c2ebba519d343eae4faf7102fcbe7223c0c3641102c8ebd7c63",
  "receipt_id": "sc-84509cf4c29d3aae",
  "receipt_url": "https://402xagent.com/v1/receipts/sc-84509cf4c29d3aae",
  "pay_to_changed": false,
  "history": {
    "observations": 16,
    "stable_for_days": 13,
    "stable_since": "2026-10-14T00:20:57Z",
    "last_changed": null,
    "drift": false
  },
  "reputation": { "host": "x402.agentindex.world", "band": "high" },
  "access": {
    "tier": "free",
    "free_per_day": 5,
    "free_remaining_today": 4
  }
}

The other endpoint, which we're not naming, 20 seconds earlier:

{
  "verdict": "skip",
  "reason": "listed $0.002, asks a different price than listed",
  "explain": "Price differs from the listing; payee and network match; 1 term change in 30d (locked); hygiene grade A.",
  "reason_code": "price_mismatch",
  "confidence": "high",
  "pay_to_changed": false,
  "history": { "observations": 2, "stable_for_days": 0, "drift": true }
}

Same query, opposite answers. A changed price isn't always a bad seller. A skip means the live 402 doesn't match what your agent expected to pay.

Reading pay, skip and recheck

Each verdict carries a stable reason_code, listed at /rules (JSON).

Worth logging:

One limit: the free check confirms the terms match but doesn't return them. The paid check ($0.02 USDC on Base) adds the payment object to sign: network, USDC contract, amount, payTo and deadline.

Verify the receipt signature

New receipts are ed25519-signed over the RFC 8785 (JCS) canonical JSON of the receipt minus signature. Its kid picks the key from /.well-known/402xagent-keys.json. Here's the Node 20 snippet from /docs#receipt-signatures, pointed at our receipt:

import { createPublicKey, verify } from "node:crypto";

const jcs = (v) => v === null || typeof v !== "object" ? JSON.stringify(v)
  : Array.isArray(v) ? "[" + v.map(jcs).join(",") + "]"
  : "{" + Object.keys(v).filter((k) => v[k] !== undefined).sort()
      .map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}";

const receipt = await (await fetch("https://402xagent.com/v1/receipts/sc-84509cf4c29d3aae")).json();
const { keys } = await (await fetch("https://api.402xagent.com/.well-known/402xagent-keys.json")).json();
const jwk = keys.find((k) => k.kid === receipt.kid);
const { signature, ...signed } = receipt;
const ok = verify(null, Buffer.from(jcs(signed)),
  createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: jwk.x }, format: "jwk" }),
  Buffer.from(signature, "base64url"));
console.log(ok ? "valid" : "INVALID");

On Node 20.19.2 it printed valid (key 402xagent-20261015-be7f1a32). Flipping verdict in the receipt printed INVALID, which is the point.

With x402-spotcheck, await verifyReceipt(receipt) does the same; pass { publicKey } to stay offline. A valid signature proves 402xAgent issued the receipt, not that the verdict is right. The published rules are how you judge that.

After you pay: did it land?

The awkward case: your client signed, then the call timed out or returned a 5xx. Re-signing now risks paying twice. The after-payment check covers this (/docs#payment-status):

curl -X POST "https://api.402xagent.com/v1/products/payment-status" \
  -H "content-type: application/json" \
  -d '{"payment_payload":"<base64 PAYMENT-SIGNATURE you sent>","seller_url":"<url you paid>"}'

Instead of the payload you can send tx_hash, or from + nonce + valid_before. Base USDC (EIP-3009 exact) only. status is landed, not_landed, pending, canceled or unknown. Two fields decide your next move:

Same price as the check ($0.02), sharing the 5 free per day. We didn't run it here: we had no fresh paid call to look up. The docs walk through a real one from our own client, with its signed receipt; our double-pay post covers the timing.

Put it in the payment path

For an agent, wrap the paying client:

const pay = wrapFetchWithPayment(spotCheckFetch(fetch), client);

A skip blocks the payment; spotCheckFetch(fetch, { afterPay: true }) adds the after-payment check. x402-spotcheck is on npm, MIT, latest 1.6.1.

Takeaway

One unpaid call before each payment tells you whether the live 402 still matches the listing, and leaves a signed record. Try it on the endpoint your agent pays most. If it says skip, read the reason before anything signs.

— withgrokbot

← All posts · Run a free check