An x402 service advertises a machine-readable example of its input in the 402 response. Agents read that example, sometimes run it verbatim, sometimes edit one field and send it back. It is the closest thing our API has to a usage contract that a program can execute.
Yesterday we found that 261 of ours were ambiguous. Not broken. Ambiguous, which is worse.
Handlers here are written defensively. A service that geolocates an IP accepts either name:
const ip = String(input?.ip || input?.address || "").trim();
That is good code. It meets callers where they are. Then the example generator does its job: it walks the declared schema and fills every field it can produce a legal value for. The published example ends up carrying both names:
{"ip": "8.8.8.8", "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"}
The handler reads ip first. The second key is inert. A client that edits address to its own value and leaves ip alone gets 200, a well-formed answer, and the answer is about 8.8.8.8. No error. No warning. Just a confident reply to a question nobody asked.
A runnable example is not necessarily an unambiguous one. The failure mode of a bad example is not a crash โ it is a silent wrong answer.
Typical cases: xml-path advertised {xml, data, text, input} with four different values for one document; vector-norm advertised {v, vector, a}; variance advertised values: [1,2,3] next to data: "hello". All of them returned ok: true. All of them were contracts with two readings.
We did not rewrite values. We removed keys that could never be read. The rule is deliberately mechanical, and it has four conditions โ a key is dropped only if all hold:
Condition 4 is the one that matters. A key that looks inert in one chain can be read by another branch. x402-cbor fell into exactly that trap: text was a fallback for base64, but the same handler also reads input?.json ?? input?.text. It stayed until we taught the tool that ?? only falls through on null and undefined โ and that a present json shadows text there too.
Two passes removed 600 keys across 391 services. Published top-level input keys went from 3,382 to 2,782. Not one value changed.
The rule is conservative, but "conservative" is not evidence. We sent the pruned examples back to the live handlers over loopback. The first sample was 60 randomly chosen services; the second covered all 20 nullish-chain changes plus 40 resampled services. All 120 returned ok: true. Comparing old and new responses, 56 of the first 60 were byte-identical; the four that differed were three services with nondeterministic output and one upstream 429 that succeeded on retry. In the second pass there were zero stable differences.
Then we ran all of them โ 391 services, one call each. 387 returned ok: true. The four failures were stable upstream errors, unchanged on retry: two rate-limited 429s, one 500, and one upstream error payload. In every case the failing service was still reading the key we kept; no response differed because of a removed key.
The example generator now applies the same filter before it validates a candidate, so a future regeneration will not add the dead keys back. The filter runs first, the live call runs second; an example is still only published if it actually returned ok: true.
If your discovery surface advertises an example, that example is documentation that executes. Two names for one parameter is not a convenience feature โ it is two different contracts shipped under one shape, and the caller has no way to know which one is in force. The fix is not a smarter reader. It is an example with exactly one interpretation.
You can read any endpoint's current input and output example without paying: curl -s https://minia2a.uk/x402/xml-path | jq .extensions.bazaar.info. The first five calls per signed wallet are free โ no registration.