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

The Probe That Reads a Live x402 Endpoint as Dead

September 16, 2026

We ran our seller review queue tonight and the script told us that 16 of 20 submitted endpoints were dead.

Twelve of them were alive.

That is not a story about a script being sloppy. The script did exactly what it was written to do. It is a story about a single sentence that sounds like one claim and is actually two, and about the fact that in a payment protocol the two claims lead to opposite actions.

12 / 16
endpoints our gate called "dead" that answered when we probed them properly โ€” measured tonight against a live review queue

What the gate did

The predicate was any response that is not 402 means dead, and the probe was a single bare GET. Both halves are load-bearing, and both are wrong.

Here is what a bare GET actually sees, next to what a real caller sees. Every row below is a live service that was queued for rejection:

What the endpoint isBare GETReal call
A POST-only paid route404POST โ†’ 402
A GET route that validates its query params before the payment middleware runs400POST โ†’ 405; a sibling route on the same worker returns 402 on both methods
A free checker that explains its own usage errors200200 + a body saying "This is a usage error, not a payment failure: nothing was charged for it"
A free informational endpoint200200 + real JSON

Six of those twelve were the same shape: a route that answers 404 to GET and a clean 402 to POST. You cannot find that with a method you picked in advance.

The sentence that is two claims

"This endpoint is not payable" and "this endpoint is not there" feel like one observation. They are two, and they differ in every way that matters:

Collapse them and you get a verdict that fires on both. Then you point that verdict at an action that removes things.

The asymmetry is the whole point. A false "alive" costs you one bad listing โ€” annoying, visible, reversible. A false "dead" deletes a paying seller who will never learn why their listing vanished, and who has no way to appeal a decision they cannot see. If your liveness check feeds anything that delists, two verdicts are not optional. One verdict is a supply-destroying machine with a delay built in.

The fix is a shape, not a threshold

We did not tune a timeout or add retries. We split the verdict and made the probe try more than one way in.

probe(url):
    get  = http(GET,  url)
    post = http(POST, url, body={a small, valid JSON object})

classify(get, post, body):
    if 402 in (get, post):        -> live gate   (then: duplicates, invoice scheme)
    if get == 0 and post == 0:    -> DEAD        (nothing answered โ€” reachability)
    if get in (404,410) and post in (404,410):
                                  -> DEAD        (the published path is absent)
    otherwise:                    -> UNDETERMINED (the origin answered; a human looks)

Three details that are not incidental:

  1. The POST carries a small, valid body. The point is to get past a "missing parameter" guard so the payment gate behind it can answer. An empty {} often bounces off the same validator that produced the 400, and you learn nothing.
  2. Dead needs both methods to agree. 404 on GET with 402 on POST is not a dead endpoint โ€” it is an endpoint you probed wrong. Requiring agreement on the negative is what stops one method from speaking for the whole service.
  3. "Undetermined" is a real verdict, not a failure to decide. It routes to a human instead of to a delete. A check that can only say yes or no will always resolve ambiguity toward whichever default you wired, and ours defaulted to destroying supply.

Test it against the errors you already made

A liveness check is one of those programs that is almost never observed failing, because its failures are silent: it does not crash, it just quietly reports the wrong thing. So a self-test that only exercises healthy inputs proves nothing.

We pinned the twelve measured false-positives as regression controls โ€” each case is the exact status pair we observed, and the test fails if the classifier ever calls any of them dead again. It also asserts the shapes that must keep failing: GET 404 + POST 402 is not dead, no-answer on both is dead, and a 402 carrying a Lightning invoice is rejected for being unpayable rather than being mislabelled as absent. 13/13.

The same discipline caught a second, cheaper mistake in the same file: the probe's body-sniffing for the invoice scheme only ever ran on a 402 it had already found, so a gate reachable only by POST was never checked for payability at all.

The counter-lesson, because this one has two

It would be easy to read the above as "add POST and you are done." You are not. The sibling bug โ€” the one we fixed in our public endpoint scanner months ago and forgot to port into the review gate โ€” runs the other direction: a scanner that treats any non-402 as dead will also flag

Two health observations, both true, both useless as a liveness signal on their own: 402 is not the only healthy response, and a non-402 is not a death certificate.

Why this will bite the whole ecosystem

Every x402 directory, monitor, trust scorer and "is this host live" badge is running some version of this probe. Most of them were written against a mental model where a paid endpoint is a URL that returns a 402 when you knock.

That model is wrong for a large fraction of real deployments, and it fails in the direction that looks like success: your dashboard goes green on the endpoints that happen to be GET-gated and quietly removes the rest. Nobody files a bug, because the only party who would notice is the seller who just disappeared โ€” and they are looking at your site, not your logs.

If you maintain one of these, the test is cheap. Take the endpoints you currently report as dead, replay them with a second method and a valid body, and see how many answer. Whatever number you get, that is how much supply you have been deleting.


Measured on 2026-09-16 against a live 20-endpoint review queue. The classifier's verdict distribution moved from 16 rejected / 4 for review to 4 rejected / 16 for review; the four still-rejected endpoints were unreachable from two independent hosts, which is the only shape we now act on automatically. Our public endpoint scanner had already required GET+POST agreement โ€” the review gate was the copy that had not been ported, which is the more common way this bug survives: not written wrong, just written once and never brought forward.

minia2a is an x402 pay-per-call marketplace: 1,600+ endpoints, settled in USDC on Base.

Browse the catalog