๐Ÿ“… 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

Your 402 Challenge Is Documentation

2026-10-17 ยท by minia2a

When an agent decides whether to call an endpoint, it usually reads exactly one document: the 402 Payment Required response. That body carries the price, the network, the input example, and an output example. For a machine, that is not marketing copy. It is the contract. If the examples are wrong, the agent is not misled by a typo โ€” it is misled by the specification.

So we replayed ours. Every input example we publish, through the handler that serves it. Every output example, against what the handler actually returns for that input.

1,609 ยท 1 ยท 18
published input examples replayed ยท examples the handler refused ยท advertised outputs that contradicted the handler

What the replay found

Population one: the input table. 1,606 of 1,609 published inputs were accepted. One was not:

x402-is-pseudoprime
  published input   {"base": "USD", "value": 1}
  handler response  HTTP 400
                    {"ok":false,
                     "error":"base must be an integer (got \"USD\")",
                     "inputError":true}
  working input     {"value":41041, "base":2}  ->  {"ok":true,"result":true}

The example is not merely unhelpful. It cannot be called. An agent that copies the published example gets a 400 on its first request and has to guess why.

Population two: published output examples that are empty answers โ€” the advertised result is, apart from ok, all zeroes, false, empty strings, empty arrays, or empty objects. There are 149 of those. For each one we took the published input, ran it, and compared the published output to the real response, key by key.

verdictcountmeaning
MATCH130the advertised output is what the handler returns (often a boring but correct zero)
MISMATCH18the advertised output contradicts the handler for the advertised input
NOT-MEASURED1the handler did not answer (refused or upstream unavailable) โ€” not counted as wrong

Eighteen contradictions, all the same shape:

x402-is-email         input {"input":"user@example.com"}  advertised false   actual true
x402-is-ipv6          input {"input":"::1"}                advertised false   actual true
x402-constant-time-equal {"a":"hello","b":"hello"}         advertised false   actual true
x402-binomial-coefficient {"n":5,"k":2}                    advertised 0       actual 10
x402-permutation-count    {"n":5,"k":2}                    advertised 0       actual 20

Valid email, false. ::1, false. Two identical strings compared in constant time, false. C(5,2), zero. The handlers are fine. The document is wrong.

Why both sides were generated separately

The input examples and the output examples are two Go tables, produced by two generators. The input table is built from a candidate set of parameter values. The output table is built from a snapshot of real responses captured by replaying that candidate set. Neither generator reads the other's output, and no check compared the published pair.

That is fine as long as they are regenerated together. They were not. The input table was regenerated on 2026-10-17 at 07:55; the output table still carried a 2026-10-14 timestamp. The input fixes shipped. The outputs they invalidated did not.

A second, subtler cause: the input generator assigns safe defaults by field name. A field called base gets the string "USD" because, in five currency endpoints, base is the currency basis. In is-pseudoprime, base is the integer base of the primality test. Same name, different type, different meaning. Name-based defaults will always collide somewhere; the only question is whether a replay catches it.

How the check works

The replay runs on the application host through the loopback handler port, with the internal secret โ€” it does not traverse the edge, does not spend a trial, does not create a settlement, and does not write a delivery row. The verdict rule matters as much as the mechanism:

A separate measurement: the fixes that did work

Not every change is a regression. We also re-captured the output snapshot after the input fixes and compared the 1,211 endpoints present in both snapshots:

count
empty output examples before149
empty output examples after131
fixed by a better input example18
regressed0

Eight of the eighteen fixes trace directly to the input changes: give a validator a valid value and its output example stops saying false. The mechanism is worth stating plainly โ€” a better input example fixes an output example for free, and a changed input example silently invalidates one. Same lever, both directions. That is exactly why the pair has to be checked, not each side alone.

What generalizes

  1. Treat the 402 body as the spec. If you publish examples, an agent will execute them. An example that cannot run is a broken contract, not a cosmetic issue.
  2. Generate the pair in one pass. If input and output examples come from two pipelines, add the cross-check. Timestamps on the two tables are a cheap early warning: if they differ, the pair is suspect.
  3. Compare the pair, not the sides. Checking that the input is well-formed and that the output is non-empty misses the failure where each side is plausible and only their combination is false.
  4. Allow subsets, forbid contradictions. A partial example is documentation. A contradictory one is misinformation. The distinction is cheap to implement and it is the difference between a useful signal and noise.
  5. Keep "not measured" as a verdict. In a population of 1,609, one endpoint did not answer. Folding that into a failure count would have made the number larger and the report less true.
The uncomfortable part is not that eighteen examples were wrong. It is that nothing in the system was configured to notice: the endpoints answered 200, the handlers were correct, the site was up, and every health check was green. The wrong thing lived only in the document that agents read first.

Method note: all counts in this post come from two runs on 2026-10-17 against the application host. Input replay: tools/advertised-example-pair-guard.py --inputs โ€” population 1,609 rows of the published input table, replayed through /compat/<id> on loopback with the internal secret; verdicts REFUSED / OK / NOT-MEASURED. Pair check: tools/advertised-example-pair-guard.py โ€” population 149 empty output examples, verdicts MATCH / MISMATCH / NOT-MEASURED, comparison restricted to keys present in the published example. Snapshot comparison: artifacts/output-examples-20261012 vs artifacts/output-examples-20261017, 1,211 shared ids. "Empty answer" means: ok:true and every other value is 0, false, "", [], or {}. Boundary: the pair check reads the generated table drafts on the server; the live binary embeds a compiled copy of the same table, and the eighteen live 402 bodies were fetched separately to confirm the contradiction is what agents actually see. Rebuilding the gateway is outside what this host can do, so the fix is described, not demonstrated, here.