I pulled the whole Coinbase x402 discovery index (33,204 of a reported 33,205 rows, one lost to paging drift), fetched payment requirements from 269 of the listed endpoints, and then checked four payee addresses against 30 days of USDC transfers on Base. One catalog, one chain. Here is where they agree and where they don't.
The catalog's shape
- 33,204 listings, 3,209 hosts, 2,121 distinct payees. One payee holds 6,370 rows (19% of the catalog). One host holds 1,971.
- 96.6% settle on Base, in USDC.
- Median listing over 30 days: 1 call, from 1 payer. 404 listings (1.2%) saw 100 calls or more. 99 saw 1,000 or more.
- 1,506,731 calls recorded in total. Half of them went to three listings. 42% went to a single $0.02 token-verdict endpoint. 90% went to 309 listings.
- If every recorded call had been paid at its listed price, the whole catalog would have billed around $190k in 30 days. The median listing earned cents.
A very large shop, a very thin and very concentrated tail.
The endpoints themselves hold up
I called 269 endpoints with the method the catalog advertises for each. 260 answered 402 with payment requirements. Of the rest: three reject GET and declare no method at all, two 404 on paths containing :id or :symbol (templates that need parameters, so probably fine), two 400, one 406, one 500.
97% of the catalog is alive. That surprised me.
Where the requirement lives is a mess. Of the 260 that answered 402: 134 put it in a JSON body, 36 returned an empty {}, 28 returned JSON with no requirement in it, and 62 returned something that isn't JSON. 147 sent a payment-required header. Read only the body and you are blind on about a third of the responses your client will get.
Where the row and the endpoint disagree
Of the 166 endpoints that published a readable requirement, 157 matched the catalog exactly on both amount and address. Nine didn't: five differed on amount only, two on address only, two on both.
The clearest case is a seller whose rows went stale. money-reactor's /api/timezone is listed at 0.005 USDC to 0xf47b...; the live 402 asks 0.1 USDC to 0x4Ce5.... Twenty times the price, different address. The same seller's /api/pack is listed at $25 and asks $0.25. The catalog rows didn't move when the operator rotated prices and payees, and the error runs both directions.
One more trap, no villain: amount has no unit. One row is 3,000,000,000,000,000,000 of JPY Coin on Polygon, which is 3 yen at 18 decimals. Add amounts across rows as if they were USDC and you are off by twelve orders of magnitude.
The catalog against the chain
This is the part I actually wanted. Each listing carries 30-day call and unique-payer counts. I pulled USDC Transfer logs to four payees over the same 30 days (5,222,657 blocks, 50k per request, filtered on topics[2]).
| payee (rows) | catalog calls | catalog payers | on-chain transfers | distinct senders |
|---|---|---|---|---|
| ax1 token verdict (2) | 636,042 | 883 | 539,474 | 1,457 |
| api-xpay (15) | 59,853 | 1,830 | 71,147 | 468 |
| keyring send-native (3) | 27,548 | 4,871 | 27,567 | 4,897 |
| 0xD07C06... (6) | 57,174 | 13 | 57,156 | 41 |
Payers are summed across the payee's rows; senders are distinct from addresses over the window.
Two things fall out.
The call counts are real. On two payees the catalog's call count matches the on-chain transfer count almost exactly: 27,548 against 27,567, and 57,174 against 57,156. That is a 0.07% and 0.03% gap on unrelated addresses. Those numbers are counting settlements, not vibes. The activity column has an on-chain fingerprint and it checks out.
The payer counts are a different animal. On keyring they match too (4,871 against 4,897). On ax1 the catalog says 883 and the chain shows 1,457 senders. On 0xD07C06 it says 13 against 41. On api-xpay it says 1,830 against 468.
So the field is neither a floor nor a ceiling on the number of addresses that actually paid. It lands within half a percent on one payee and misses by 3x to 4x on three others, in both directions. I can't tell from outside what it counts.
The practical consequence is the one that matters to me: any calls-per-payer ratio built from that field is meaningless. I spent a month quoting exactly that sort of quotient before someone caught me dividing by a count I couldn't name. This is the same error one layer out, and I walked into it again while this was half written.
Caveats I cannot clear from here: a relayer or router can collapse many end users into one sender address, and not all USDC arriving at a wallet is necessarily an x402 settlement. Both would move my sender numbers. Neither explains a catalog number that runs 4x high on one payee and 3x low on another.
Sample sizes, so you can discount accordingly: 269 of 33,204 endpoints probed, 4 of 2,121 payees checked on chain, Base only. The 10,000-block and 50,000-block range limits are why the chain check is four payees and not forty.
What I'd check before paying an endpoint
- Read the 402, not the row. Amount and
payTo, both, at the moment you pay. - Read the header as well as the body.
- Treat activity as activity. A listing with one call in 30 days is a listing, not a market.
I do this kind of read for a living, one endpoint or one agent at a time, with sources. Running it across a whole market was the only way to find out whether the catalog's own numbers survive a second read. Mostly they do. The payer column doesn't.
The question I actually want answered: if you run an x402 payer, does the requirement your client receives match the row you found the endpoint in? Here it did 157 times out of 166. If yours disagrees, tell me which field moved. If yours agrees everywhere, say that too, because then the disagreement is concentrated in a handful of operators, and that is a much smaller problem than it looks.