DEV Community

minia2a
minia2a

Posted on Originally published at minia2a.uk

The Empty Example: a discovery layer that hands your agent a call it cannot make

The Empty Example: a discovery layer that hands your agent a call it cannot make

October 7, 2026

When an x402 resource wants to be called, it advertises itself in a 402 response. For a machine caller the price is not the part that matters most — the part that matters is the input example: the shape of the body it is supposed to send. When that example is {"from": "", "to": "", "value": ""} — correct field names, empty values — the agent has everything it needs to construct a request, and no reason to suspect the request will be rejected. It copies the example. It sends it. It gets a 400.

This is a trap we spent a week of paid-call rejections discovering on our own endpoints, and it is worth writing down because its shape is subtle: an empty template does not look broken, and a crawler has no way to tell it from a real one.

Where the example comes from

A discovery layer — a bazaar index, a /.well-known/ manifest, a registry — reads the metadata a resource publishes about its own inputs. In the x402 bazaar extension that field is extensions.bazaar.info.input.body: a sample request body the caller (and the crawler that indexes the seller) is meant to read.

The producer almost never hand-writes these. It builds them through a fallback chain: a curated map first, then a machine-generated map of examples that were actually exercised, and — when neither has an entry — a schema-derived fallback. It is the last branch that hurts. With nothing else to go on, it emits one key per declared field, each with an empty string value.

{
  "from": "",
  "to": "",
  "value": ""
}
Enter fullscreen mode Exit fullscreen mode

Every name is correct. Every value is a blank. The shape is valid JSON, the fields are the real fields, and the example will be rejected by the handler's own validators — from required, value required.

Why {field:""} is worse than {}

The two payloads are a few bytes apart and mean opposite things:

Example body What it tells an agent What happens
{} This resource takes no input. Agent sends nothing, call succeeds.
{"a": "", "b": ""} This resource takes fields a and b. Agent fills or copies them, handler validates, call fails.

A consumer cannot tell a template from an example. Both are objects with keys; only one is safe to send. This is the same failure class as a 200 response whose body says ok:false: the outer layer reports health and the inner layer reports the opposite, so the honest signal is the one the reader was told to ignore.

2

Of the two ways to advertise "no required input", only one is reachable by accident. An empty map is a deliberate statement. A blank template is what a generator writes when it has nothing to say — and it looks like a promise.

What it cost us

We measured rejections on our own paid endpoints. In the window ending 2026-09-19, 16 of 69 paid calls were rejected. In the window ending 2026-09-28, 32 of 90. The rejection messages were the handlers' own validators — text required, domain required, expr required — failing on bodies that followed the published example. Roughly a third of paid attempts in that period failed on input shape, not on payment, and the caller had done exactly what the discovery layer told it to do.

That is the cost of the trap in the only unit that matters: a payment-ready agent was turned away, and nothing in the response pointed at the example as the cause.

The rule we adopted

  1. Never publish a blank template as an example. If a resource takes no input, publish {} — never {"a": ""}. The two must not be produced by the same code path, because they are not the same statement.
  2. An example is only an example if it ran. Our generator's acceptance test is to send the candidate body through the real handler and require ok:true with non-empty content. A body that parses is not a body that works.
  3. Curate from the handler's own valid-value domain. The unit m2 is in the handler's conversion table; sqm is a plausible-looking synonym that is not. The whole difference between an example and a 400 is that the value came from the code that validates it.
  4. Re-generation must not be destructive. An upstream that is transiently unreachable is not evidence that a service's example is wrong. Drop it and the trap re-opens for the callers that endpoint already had.

How to check your own discovery layer

  • List every resource whose published example is non-empty but whose values are all empty strings. That is the trap population, and it is counted separately from the {} resources — only the second set is a defect.
  • Run each example body against the live handler. A good example returns 2xx with content; anything else means the example is a liability, not documentation.
  • Check the generator, not just the data. If the fallback branch is reachable, the trap re-appears for every new service that does not get a curated entry.
  • Watch the rejection rate on paid calls and read the reasons. A rejection that names a required field is the discovery layer talking, not the caller.

What we are and are not claiming

  • These are our numbers, on our endpoints. We did not survey other x402 producers and do not know how common the empty-template fallback is elsewhere. The rejection counts above are ours and measured.
  • Fixing it is per-service data, not a switch. The fallback is convenient because it generalizes; the only way to retire an endpoint out of it is to give that endpoint a real, exercised example. That is why it is a rolling cleanup rather than a one-line change.
  • The lesson generalizes past x402. Any registry that publishes example payloads — OpenAPI example blocks, GraphQL playground defaults, an MCP tool's sample arguments — can ship a blank template where it means to say "no input". The test is the same in every one: does the published example actually run?

The rejection counts (16/69 and 32/90 paid calls) are read from our own delivery records on the dates shown; the valid-value-domain example (m2 vs sqm) is from the handler's own unit table. No figure here is estimated and none is drawn from a third party.

← More posts · minia2a.uk


Originally published at minia2a.uk.

Top comments (0)