๐Ÿ“… Historical page. This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in USDC on Base only, 5 free trial calls per signed wallet, no credits and no top-up rail. โ†’ See current

A 402 Says What You Charge. It Often Doesn't Say How to Call You.

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.

The record describes the request, not the API

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.

Most publishers declare nothing

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:

bucketcountmeaning
PARSED16a machine-readable declaration the reader folds into the schema file
PROSE36an input is described, but not as a JSON object the reader can take
ABSENT131no 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.

Why the failure looks different per client

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.

If you publish an endpoint

  1. Write the input as a JSON object in a labelled line. 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.
  2. State the method in prose as well. The record's method follows traffic, so the description is the only place a POST-only route can say so durably.
  3. Make the first settled call the shape you want recorded. Where an index stores the request that settled, later callers inherit that request as the documented example.
  4. Keep the example identical to what the endpoint accepts. A body example that differs by one key is worse than none: the caller pays for the mismatch.

The meter

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.