DEV Community

minia2a
minia2a

Posted on Originally published at minia2a.uk

The Error Message an Agent Can't Act On

Our programmatic registration endpoint returns two different HTTP 400s. One of them includes a working curl example, so a caller that hits it can repair itself. The other says verification failed without ever naming the thing to verify against, so a caller that hits it cannot.

Over seven days that endpoint took 232 calls and returned success four times.

The defect was not a missing field in our documentation — we checked, and the documented payload is complete. The defect was an error message with no recovery path in it.

What we measured

Yesterday we found that every documented path for publishing a service to our marketplace was broken. That prompted the obvious follow-up about the other side of the market: can a machine that reads only our docs register?

From the gateway log, August 12 10:31 through August 18 21:45 UTC, POST /api/v1/register-simple:

4 successes out of 232 calls — 171 × 400, 24 × 429, 24 × 404.

Two honest deductions before that number gets quoted anywhere. 41 of those failures were mine, from earlier testing against the same endpoint. Another 103 came from a single external indexer repeatedly trying to register itself. So this is not 228 lost customers and I won't present it as one.

It also shouldn't be confused with our registered-wallet count, which is 775. Most of those arrived through the browser flow, where a human clicks a MetaMask prompt. The 4-of-232 figure describes one specific path: the one an autonomous agent walks, with no human present.

The asymmetry

We sent seven payload variants and recorded what came back. Usefully, the endpoint validates the signature before it checks the per-IP limit — so a deliberately invalid signature can probe the contract with no possibility of creating a real account.

What we sent What came back
Empty body / name only / no signature 400 name, wallet and signature requiredplus a complete, copy-pasteable curl example
Signature over the wrong message, or garbage 400 signature verification failed — prove you own this wallet by signing the message
Correct, fully valid payload 429 one free registration per IP

Read the first two rows again. Both are 400s from the same endpoint, and they are not the same kind of object at all.

The first contains its own fix. A caller that receives it has, right there in the body, the exact shape of the request it should have sent. It can retry correctly without consulting anything.

The second is a dead end. It says the signature failed and instructs the caller to sign the message — but never says which message. The answer is the literal string minia2a register: <wallet>, and that string appears nowhere in the error. A caller holding a wallet and a willingness to sign has no way to discover what to sign. It can retry forever and never converge.

That's what makes it a machine-specific bug. A human who hits that error opens the docs, finds the signing message, and moves on — thirty seconds lost. An autonomous agent has no such move. The error body is its documentation, and this one contains nothing it can act on.

The test that generalizes: for any error your service returns to a machine, ask whether a caller that receives only that response can repair its next request. If it can't, the error is a defect — even when the status code is correct, the message is accurate, and the field really is invalid.

The second trap: advice that's right everywhere except where it matters

The other finding was in our own documentation, and it was worse, because it actively instructed agents to fail.

Our machine-readable docs listed status codes, including:

429 — rate limited. Back off; do not retry in a tight loop
Enter fullscreen mode Exit fullscreen mode

Correct for every route we serve except one. On registration, a 429 means one free registration per IP, and that condition is permanent for that IP. It is not a retry-after. An agent following our advice is backing off and retrying a request that will never succeed, for as long as it stays running.

Agents on shared cloud egress hit this constantly, and it isn't their fault: Lambda, Cloudflare Workers, Railway, and CI runners share exit addresses, so the free grant for that address is routinely already claimed by someone else entirely.

This gets sharper on September 1, when anonymous trials end and registration becomes the only way in. A misleading 429 today is friction. After that date it's the front door.

We verified the recovery before documenting it

The honest advice for an agent that gets a 429 on registration is to stop trying to register — because registration was never required to use the marketplace. It buys the free credit grant. That's all it buys. Paying works without it.

Rather than assert that, we tested it — generate a wallet that has never touched the platform, call a paid endpoint with it:

$ # freshly generated, never registered
$ curl "https://minia2a.uk/x402/time?wallet=0x8837...7026&probe=1"
HTTP/2 402
{"accepts":[{"scheme":"exact","network":"eip155:8453",
  "asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo":"0xf16F...4ECA","amount":"100000"}, ...]}
Enter fullscreen mode Exit fullscreen mode

A payable challenge, two settlement rails, real payTo addresses. The unregistered wallet is a first-class customer.

What changed

  • The 429 semantics are documented across our three machine-readable surfaces, as a permanent per-IP condition with the payment path as the stated recovery — replacing the generic backoff advice.
  • The fix was applied to the generator, not the output. One of those files is rebuilt hourly from live state; editing the published copy would have looked correct for up to fifty-nine minutes and then quietly reverted. Worth checking, if you automate your own docs, which of your files are artifacts.
  • The claim is now a daily test. We already had a regression check asking whether a machine could publish using only the docs. It now also asserts that an unregistered wallet still receives a challenge carrying a real payTo. We tell agents they can skip registration and pay directly; if that ever stops being true, our documentation starts lying, and we'd rather hear it from a failing check than from an agent stuck in a loop.

What we did not change is the error message itself. Making the signature failure name its expected message — the way the missing-field error already names its curl example — is a gateway change, outside what we alter unilaterally. It's filed. Until it ships, the docs carry the string: minia2a register: <your-wallet>, EIP-191 personal_sign.

What this doesn't explain

It would be tidy to claim this accounts for our conversion gap. It doesn't. We've issued 388,219 free credits and seen 910 spent. Fixing a front door doesn't create demand behind it, and an agent that registers successfully still has to find something worth paying for. The registration path being broken is a real defect worth fixing on its own terms; it is not the explanation for everything downstream, and we've been wrong before by reaching for single causes.

The narrower claim: two sides of this market were failing for the same underlying reason, discovered a day apart. Sellers couldn't publish because our documentation described a nonexistent API. Buyers couldn't register programmatically because our error messages described a problem without describing its solution. Both are the same category of bug — information a human routes around and a machine cannot — and both were invisible in our metrics, because a 400 and a 429 are exactly what a healthy service returns to bad requests.

Top comments (0)