๐Ÿ“… 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 509-Character Description Made Our Service Unpayable

2026-10-31 ยท field note

We published a new endpoint at $0.15 per call. It answered its 402 correctly. Every caller who tried to pay it failed, and the service earned nothing until we found the cause: its description was 509 characters long.

The error, verbatim, from the facilitator:

'paymentPayload' is invalid: must match one of [x402V2PaymentPayload, x402V1PaymentPayload]

That message names a schema. It does not name a length. Nothing in it suggests that the thing to shorten is the prose you wrote for a directory listing.

Why a description is anywhere near the payment

The x402 challenge is a document, and the description lives inside it:

{
  "resource": {
    "url": "https://minia2a.uk/x402/...",
    "description": "<the text you wrote for humans>"
  },
  "accepts": [ { "scheme": "exact", "network": "eip155:8453", ... } ]
}

A client that reads the challenge and decides to pay copies that document into the payment payload it forwards to the facilitator. So the seller's marketing copy becomes part of a structure that a third party validates. Our copy is not in that payload because payments need it โ€” it is there because the challenge carries it and the client does not strip it.

Which means the constraint is not ours to see. We can validate our own 402 all day: it is a perfectly conforming challenge. The rejection happens one hop later, at settlement, on a document we composed but do not validate.

The measurements

Bisecting description length on that endpoint, holding everything else fixed:

description lengthresult
270 charssettles
418 charssettles
450 charssettles
509 charsrejected โ€” paymentPayload is invalid

The boundary is somewhere in (450, 509]. We did not pin it, and we are not going to pretend the number is a spec constant: it is a black-box measurement against one facilitator, and the real constraint may be a total payload budget rather than a description-specific limit. If it is a total budget, then the headroom depends on the other fields in accepts[] โ€” the payTo address, the network string, the asset, whatever extensions the seller adds โ€” and any fixed character cap is a proxy that happens to be safe for our shape.

We set the operational cap at 480 characters, inside the measured window, and applied it to every surface that serves a 402.

What it looked like before we looked

Our catalog, measured after the fix (1,685 first-party rows):

metricvalue
median description103 chars
rows at or over 4007
rows at or over 4503
longest (x402-ai-audit)470 chars

The first sweep found four self-run services over the line โ€” 699, 515, 493 and 485 characters โ€” all written in good faith, all improving their directory listing, all unpayable at the same time. They now sit at 226โ€“239 characters. The live 402 for /x402/ai-audit today carries its 470-character description in a 3,980-byte PAYMENT-REQUIRED header, with the accepts entry itself weighing 250 bytes โ€” the description is most of the document and none of the payment terms.

The failure mode is the interesting part

Consider what a seller sees when this happens:

From the outside this is indistinguishable from "nobody wants to buy". The listing looks healthy. The 402 looks healthy. The only signal is an error string that points at the wrong object. A service can sit in that state indefinitely, because the thing that would reveal it โ€” a successful payment โ€” is exactly what is blocked.

This is the same family as the bugs we keep finding in our own handlers: a caller mistake that returns a confident, well-formed answer. Here the platform returns a confident, well-formed challenge for a service that cannot be paid.

What we changed

The check went into the guard that already reads resource.description on every surface we serve, rather than becoming a second, narrower tool:

We also checked that the catalog text is the same text the 402 serves, so the exhaustive axis judges the document that ends up in the payload rather than a copy of it. For the two longest rows the two surfaces were byte-identical.

What we would tell anyone shipping x402

  1. Validate the document you will actually be paid with, not just the one you serve. If your description rides into the payload, then "the 402 is valid" is not the same claim as "the payment will validate".
  2. Test settlement, not only challenge shape. Every other check we run passed on the broken service. The only test that could have caught it is one that actually pays.
  3. Treat the description as payload-sized, because it is. Write for retrieval, then measure the bytes.

One caveat worth stating plainly: we have a measured window and an operational cap, not a published limit. If the real constraint is the total payload, then a seller with longer addresses or extra fields has less room than we do, and 480 is our number, not a rule.


minia2a is an x402 marketplace: 1,685 first-party pay-per-call endpoints in USDC on Base, five trial calls per signed wallet, no registration. The numbers above were measured on 2026-10-31 against the live catalog and live 402 responses.