Ask the secondhand market what something is worth — live eBay listings plus a priced verdict on whether a given listing is actually a deal.
git clone https://github.com/nirholas/x402-shop-scout
cd x402-shop-scout && npm install
npm run dev # http://localhost:4022
| Route | Price | What you get back |
|---|---|---|
GET /search | $0.002 | normalized listings with price, shipping cost, condition, seller feedback, location and URL |
GET /deal-check/:itemId | $0.002 | a verdict (`great-deal` … `overpriced`), the comparable sample, median/mean total cost, delta, percentage, and plain-English reasoning |
GET /health | free | Liveness, data source, configured rails |
GET /.well-known/x402 | free | Machine-readable discovery manifest |
Every paid route returns the purchased artifact in the 200 body — nothing is deferred to a webhook or a second fetch.
GET /searchX-PAYMENTPay in USDC on Base or Solana — your client picks. The 402 body always lists both.
USDC on base-sepolia (or base). The wallet signs an EIP-3009 authorization locally — no server round-trip.
USDC SPL transfer on solana. A facilitator sponsor pays the SOL network fee, so buyers need only USDC. Helpers at /api/x402-checkout build and wrap the transaction.
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment } from "x402-fetch";
const payFetch = wrapFetchWithPayment(fetch, privateKeyToAccount(process.env.PRIVATE_KEY));
// What's out there?
const { listings } = await (await payFetch(
"http://localhost:4022/search?q=sony+wh-1000xm5&limit=5"
)).json();
// Is the cheapest one actually a deal?
const verdict = await (await payFetch(
`http://localhost:4022/deal-check/${encodeURIComponent(listings[0].itemId)}`
)).json();
console.log(verdict.verdict, verdict.reasoning); // "good-deal", "Item total cost ..."
Live eBay data is used when `EBAY_CLIENT_ID` + `EBAY_CLIENT_SECRET` (or a raw `EBAY_OAUTH_TOKEN`) are set — a free developer account at developer.ebay.com. Without them the service answers from a deterministic fixture marketplace of 15 listings across five product families and labels every response `"source": "fixture"`. The verdict logic is identical in both modes; only the listing corpus changes.