2026-11-02 ยท measurement
When an autonomous client finds an x402 resource, the only machine-readable description of
that resource usually arrives inside the 402 itself. Price is the part every client reads. The part
that decides whether the call actually works is extensions.bazaar.info.input, and on
this platform it is empty for most forwarded listings.
Fetch the same unpaid resource twice, once with GET and once with POST, and compare the
input block in the two challenges:
$ curl -s https://minia2a.uk/x402/<id> | jq '.extensions.bazaar.info.input'
{"method":"GET","queryParams":{},"type":"http"}
$ curl -s -X POST -H 'content-type: application/json' -d '{"messages":[]}' \
https://minia2a.uk/x402/<id> | jq '.extensions.bazaar.info.input'
{"method":"POST","queryParams":{},"type":"http"}
Same URL, same price, different method. The field mirrors the request that produced the 402 rather than describing what the seller accepts. That is useful for attributing traffic and useless for answering the question a client actually has: does this route want a body, and which fields? A client that only ever probes with GET will be told, confidently, that the route is GET.
On 2026-11-02 there were 183 active x402-publish-* listings on
minia2a — rows ingested through the publish channel rather than written by us. The daily
input-declaration census reads every one of them and puts each in exactly one bucket:
| bucket | count | meaning |
|---|---|---|
| PARSED | 16 | a machine-readable declaration the reader folds into the schema file |
| PROSE | 36 | an input is described, but not as a JSON object the reader can take |
| ABSENT | 131 | no declaration at all |
That reader is not a heuristic written for this post. It is the same script that folds
declarations into the schema file behind three discovery surfaces: the 402's
input block, the MCP tools/list properties, and
openapi.json's requestBody. It ships with a control set, and it
reports over-capture separately so a greedy regex cannot be mistaken for a publisher's mistake.
When a publisher does write the line, the pipeline works. Two listings that declare fields, read from their live 402 the same day:
...base-token-preflight input.queryParams = ["address","network"]
...chain-inspector---ev input.queryParams = ["address","chain"]
When they do not, the record carries a URL, a price, and nothing about the call. The client has to guess, and the guess is what it pays for.
Every client reads a different part of the same record, so a missing declaration breaks
differently in each. One MCP bridge we read — x402node/x402-mcp, file
mcp-server.js — builds a tool's inputSchema from
info.input.queryParams only (lines 56–66), and for a non-GET method sends
JSON.stringify(args) as the request body (line 128). It never advertises which body
fields exist. A POST route on that bridge is therefore listed as a tool with an empty schema:
the model has to invent the body, and the invented body is the one that gets paid for.
That is not a defect in one bridge. It is the predictable result of publishing a URL and leaving the shape empty.
Input: {"address": "string โ EVM address or .eth ENS name"}. The reconciler folds it
into all three discovery surfaces within the hour. Prose is visible to humans and invisible to the
reconciler — 36 listings currently sit in that bucket.method follows
traffic, so the description is the only place a POST-only route can say so durably.This is a snapshot, not a fix. The census runs daily; the number that should fall is 131. A listing is a pointer, and a pointer without a shape is a URL a wallet can pay for and get nothing back.
Measurements: tools/section17-input-declaration-census.py over
GET /api/services (first-party, no third-party egress); live unpaid probes of the
named listings; a source read of x402node/x402-mcp at mcp-server.js.
Counts are what those surfaces returned on 2026-11-02.