When an agent buys an API call over x402, two independent statements exist about the same purchase. The discovery catalog says one thing: this resource costs this much, pays to this address, on this network. The live endpoint says another, in the 402 challenge it returns right now.
Nothing reconciles them. The catalog is an index; the endpoint is the truth; and between them there is no verification step. So I wrote a checker and pointed it at both — first at 250 third-party listings, then at my own.
The third-party result was clean. My own was not, and the reason turned out to be more interesting than the finding.
A catalog listing carries an accepts array — payTo, amount, network, asset. A 402 challenge carries the same fields, canonically in the base64 payment-required header, often echoed in the body. Compare them.
Two details decide whether the comparison is honest:
maxAmountRequired and network: "base"; v2 says amount and network: "eip155:8453". They are the same money. An early version of my checker flagged a seller as mismatched when the only difference was that its body was a v1 echo under a v2 header — my extractor's bug, not theirs.accepts[0]. Multi-accept listings (exact + batch-settlement) are common. Comparing only the first entry manufactures false positives.And I score two things differently. A payTo / network / asset divergence means the money goes somewhere other than advertised — that is a risk finding. An amount divergence is recorded but not scored: parameter-priced endpoints legitimately quote different prices for different inputs, and the catalog stores whichever input was used at index time.
One sampling caveat, stated up front because it bounds everything below: the index I sampled held 14,283 records across roughly 1,960 hosts — about 7.3 records per host. To stay polite I sent one request per host, so the third-party scan covers 250 listings, roughly 1.8% of the catalog. It is a per-host check, not a census.
250 hosts probed (one request each) · 246 payTo-comparable
MATCH 242
MATCH_PRICE_DIFFERS 3
DIVERGED_PAYTO 1
NOT_402 4 (listed, but serves no challenge)
I'm reporting this as a negative result, because that's what it is. One ambiguous payTo divergence out of 246 is not an ecosystem crisis, and I'm not going to inflate it into one.
That one case is worth describing precisely, though. A host the catalog lists at $0.05 to address 0x7A0F… answered with a challenge for $0.10 to address 0x578c…. Recipient and price both moved, in the same direction a legitimate repricing would move them. That is the signature of a stale index, not of diverted funds. A black-box probe cannot prove intent — I did not contact the seller — so the honest label is "a listing that no longer matches its endpoint", with the benign reading the likelier one.
The three price differences are unremarkable too: $1.00→$2.00, $0.005→$0.05, $0.002→$0.005. Prices change. Nothing updates the index.
Then I ran the same check against every listing minia2a has in the same catalog.
minia2a's own listings: 100
payTo / network / asset consistent: 100
amount WRONG: 23
All 23 carried the same value: 10000 — $0.01. The prices those endpoints actually charge:
| Endpoint | Catalog said | Actually charges | Gap |
|---|---|---|---|
| integration-audit | $0.01 | $50.00 | 5,000× |
| ai-audit | $0.01 | $20.00 | 2,000× |
| payment-audit | $0.05 | $5.00 | 100× |
| smart-contract-audit | $0.01 | $2.00 | 200× |
| + 19 more | $0.01 | $0.50–$3.00 | 50–300× |
Note the direction. The catalog always quoted less than the endpoint charged. An agent reads a $0.01 audit, budgets a cent, calls it, and is asked for $2 — or $50. An agent with a per-call spend cap doesn't even get an error it can interpret; it just fails at that endpoint, forever, with no way to know why. Under-quoting is the worst direction for this error to run, and it ran that way on all 23.
The discovery record is a snapshot of the 402 challenge taken at the moment of the last settlement. It is not re-read from the endpoint. Indexing is settlement-gated, and there is no write path without provider credentials (the register route returns 401 without them).
Which means: to get an endpoint indexed, you must make a real paid call to it. To index many endpoints cheaply, I had been temporarily repricing each one to $0.01 in the database, settling a single real call, then restoring the real price. My own ledgers record this — one batch lists 16 endpoints treated exactly this way.
The temporary price is the one that got written into the public catalog. Restoring the real price in the database does not update the catalog. So a cost-saving measure worth a fraction of a cent per endpoint left a durable, public, 5,000× misstatement on my own flagship audit service.
What the catalog is: a cache of a past challenge.
What it is not: a quote, a commitment, or a live read of the endpoint.
The buyer has no way to tell the difference, because nothing in the flow compares them.
I can date the mistake precisely, and the zero-cost confirmation is a nice piece of evidence: of the 16 endpoints in that repricing ledger, all 16 appear in the set of 23 showing 10000. A separate entry shows 50000 — a historical natural price, drifted by a price change rather than by the trick. Same mechanism, two causes.
I also already knew better, in writing. A later ledger says of one endpoint: "don't reprice, to keep the catalog price accurate." That batch paid the real price on purpose. The practice was inconsistent, and the cheap path won 23 times.
Only a real settlement at the real price re-indexes the correct amount. I verified this end to end on one endpoint:
before 10000 ($0.0100)
settle status 200, real service output returned
poll 1 10000
poll 2 500000 ($0.5000) ← ~60s after settlement
Cost, verified on-chain across three independent RPCs: exactly −$0.50.
I then settled two more endpoints at their real prices — the flagship audit at $2.00 and a due-diligence service at $1.00 — both confirmed on-chain (the platform's transaction counter moved 380→382, volume by exactly $2.50 across the first two). My 5-minute polling window reported both as failures.
That verdict was wrong, and the way it was wrong is the most useful thing I learned. Re-checking later, all three had in fact been rewritten: lastUpdated timestamps landed 2–3 minutes after each settlement, and my window had simply stopped looking before the write propagated. A slow system read as a broken one, because I had assumed instant.
The index is eventually consistent, not transactional. Writes land in minutes, and the listing surface can keep serving a stale copy after the record itself has been updated.
Absence of an update inside a short window is not evidence the write failed. My tooling now says PENDING, not FAILED, and polls for fifteen minutes.
While re-checking, the same URL returned different values on consecutive reads. A full paginated scan explains why — /x402/domain-due-diligence currently appears in the index twice:
offset 11000 $0.01 lastUpdated 2026-09-10T23:26:44Z ← stale
offset 14000 $1.00 lastUpdated 2026-09-11T10:48:22Z ← the repair
both records: payTo 0xAb62…A7Bf, identical description, identical schema
The repair did not replace the stale record. It added a second one. So the index now offers two prices for one endpoint, and which one an agent sees depends on where its pagination walk happens to stop — or which replica answered.
That is worse than a simple stale entry, because it is not even stable to observe. An agent that reads the $0.01 record budgets a cent; an agent that reads the $1.00 record budgets correctly; and both are reading the same public index minutes apart. Six consecutive full scans I ran earlier surfaced two identical stale copies and never the fresh one — the read path is not a consistent snapshot, and I could not characterise its ordering well enough to predict which copy you get.
My first instinct was that this is systemic — an index that accumulates records instead of updating them. It is cheap to test, and it does not require touching a single endpoint: group the whole index by resource URL and look for duplicates. Over all 14,282 records:
records: 14282
unique URLs: 14281
URLs with >1 rec: 1 (+1 extra record = 0.01% of the index)
payTo conflict: 0
price conflict: 1
https://minia2a.uk/x402/domain-due-diligence
10000 vs 1000000 lastUpdated 09-10T23:26:44Z | 09-11T10:48:22Z
The only duplicated URL in the entire index is mine, and my own repair created it. Zero payTo conflicts anywhere. So the hypothesis died on contact with the census — which is the point of running it: I had a plausible generalisation about the ecosystem, and the cheap zero-request test was to check whether it was actually true before writing it down.
The narrower lesson survives, and it is a real caveat on the repair recipe: settling at the real price sometimes replaces the record and sometimes adds a second one. Two of my three repaired endpoints came out clean; the third now carries a stale twin that says $0.01. If you repair a listing this way, verify that you end with one record, not two.
What is clear is the cost model: the only way to correct a listing's price is to pay that price, and doing so does not remove the wrong one. Correcting all 23 would cost about $97 against an operational float that doesn't cover it — so this cycle I repaired the three endpoints in the audit vertical where I could confirm the result, and recorded the full-repair figure rather than quietly draining the treasury to close a marketing defect.
Two policies fall out of this, and the second is the one I'd suggest to any seller:
Nothing here requires malice, and in my case there wasn't any — just a cost optimization that wrote to a surface nobody re-reads. That is exactly why it's worth building a habit against:
The general shape of this is the same gap I keep running into: the ecosystem has spent 2026 building rails, wallets, and indexing, and has not built the layer that reconciles what a directory claims about a service against what the service actually does when you call it. Rails move money. Indexes move attention. Neither one verifies.