Fetching latest headlines…

Dev

Your x402 endpoint settles but isn't in the Bazaar? The buyer's client decides (and a no-money fix)

Dev.toUnited States · NORTH AMERICA

Disclosure: I run ScrapeMole, a small set of pay-per-call x402 APIs. This post is about a discovery bug we hit in our own setup; the fix works for any x402 seller. Our endpoints had been live on Base...

3 views0 likes0 comments

Disclosure: I run ScrapeMole, a small set of pay-per-call x402 APIs. This post is about a discovery bug we hit in our own setup; the fix works for any x402 seller.

The symptom

Our endpoints had been live on Base mainnet for about a day. Real outside wallets had paid for 18 calls, every one settled through the PayAI facilitator, and every 402 response carried a valid extensions.bazaar declaration.

Yet when we queried the facilitator's public catalog for our receive address:

curl "https://facilitator.payai.network/discovery/resources?payTo=0xYOUR_PAY_TO&limit=100"
# {"items":[], "pagination":{"total":0}}

Zero. Meanwhile our testnet endpoints were in the catalog.

Why: the catalog indexes what the buyer echoes, not what you serve

Bazaar-style catalogs (PayAI's, and per the x402 spec, any facilitator's) don't crawl your 402 response. They read the discovery declaration from the payment payload that arrives at /verify or /settle. In x402 v2 the buyer's client is supposed to copy extensions (and resource) from your 402 into that payload.

Many buyer clients don't. So you can have plenty of real settlements and still never be catalogued. Our testnet endpoints were listed only because our own e2e test client used the official @x402/fetch wrapper, which does echo the extension.

PayAI's docs say this outright: a successful settlement alone does not list a resource; the payload has to carry the bazaar extension. Their /verify and /settle now return an EXTENSION-RESPONSES header so you can tell what happened:

header meaning
{"bazaar":{"status":"processing"}} received, queued for cataloging
{"bazaar":{"status":"rejected",...}} received, failed validation
no header the payload had no bazaar extension

The fix (no money moves)

You only need one payload with the extension to reach the facilitator, and /verify is enough. For a GET resource, the catalog admits it when its own read-only probe of your URL gets a 402 back.

So: sign a payment from your own receive wallet to itself (net zero even if it were ever settled), attach the bazaar extension from your live 402, and send it to /verify only, never /settle.

import { privateKeyToAccount } from 'viem/accounts';
import { x402Client } from '@x402/core/client';
import { ExactEvmScheme } from '@x402/evm';

const acct = privateKeyToAccount(process.env.PAY_TO_KEY);   // your receive wallet
const client = new x402Client().register('eip155:8453', new ExactEvmScheme(acct));

for (const url of process.argv.slice(2)) {
  const r = await fetch(url);                                // unpaid -> 402
  const pr = JSON.parse(Buffer.from(r.headers.get('payment-required'), 'base64'));
  const payload = await client.createPaymentPayload(pr);
  payload.extensions ??= { bazaar: pr.extensions.bazaar };   // make sure it's echoed
  payload.resource ??= pr.resource;
  const v = await fetch('https://facilitator.payai.network/verify', {
    method: 'POST', headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ x402Version: 2, paymentPayload: payload, paymentRequirements: payload.accepted ?? pr.accepts[0] }),
  });
  const ext = v.headers.get('extension-responses');
  console.log(v.status, url, ext && Buffer.from(ext, 'base64').toString());
}

The wallet needs a USDC balance at least the price for /verify to report isValid: true. Nothing is transferred, because we never call /settle.

We ran it over our 40 mainnet GET endpoints. Every one came back {"bazaar":{"status":"processing"}}, and about a minute later the catalog query above returned total: 40.

Things worth checking on your own service

  1. Query /discovery/resources?payTo=<your address> on every facilitator you settle through. Don't assume settlements mean you're listed.
  2. Look for the EXTENSION-RESPONSES header in your own test runs. If it's missing, your client isn't echoing extensions.
  3. Each facilitator has its own catalog. Being in PayAI's says nothing about Coinbase CDP's Bazaar, which only sees payloads settled through CDP.
  4. Re-run the verify step when you add endpoints. New routes won't show up until some payload carrying their declaration reaches the facilitator.

A note on who's buying

When we looked at the two wallets that paid us (wallet activity is public on Basescan), both also paid several other x402 sellers in the same minutes, mostly through the Coinbase facilitator. One wallet had funded the other directly, so it's most likely a single operator running a research agent across many paid APIs. The lesson for us: buyers shop across catalogs, so being missing from one catalog is lost sales, not a cosmetic gap.

If you sell x402 APIs and have hit other discovery gotchas, I'd like to hear them in the comments.

Comments (0)

Sign in to join the discussion

Be the first to comment!