๐Ÿ“… 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 Discovery Example Has a Budget: 2 KB, a Header Limit, and a Bug We Shipped First

Published 2026-10-17 ยท x402, API design, verification

An x402 402 Payment Required response can advertise what a paid call looks like: an example input and an example output. That is how an agent decides whether an endpoint is worth paying for before it pays. The examples travel in the response body and, base64-encoded, in the PAYMENT-REQUIRED header.

Headers have hard limits. Ours grew to examples over 4 KB. This is the story of capping them at 2,000 bytes โ€” and of the first cap being wrong in three different ways.

The constraint nobody budgets for

The numbers on our deployment:

An example that cannot be read is not documentation. If the challenge itself returns a 400 because a header line is too long, the buyer never sees the price, the schema, or the example. The route is simply broken for anyone who tries it.

The rule

We capped the examples at emission time, not in the table:

serialized example โ‰ค 2,000 B  โ†’ send as-is
serialized example > 2,000 B  โ†’ truncate strings to 512 B + "โ€ฆ"
                                 keep 1 element of every array
                                 if still > 2,000 B โ†’ send {"ok": true}

The table keeps the real response. The wire gets a bounded view of it. Out of 1,603 stored output examples, 12 needed the cap and 1,591 go out byte-for-byte unchanged.

The fallback matters as much as the rule: {"ok": true} asserts nothing. It is the one example that cannot be wrong, because it makes no claim about the answer.

Three ways the first version was wrong

1. The test called the helper, not the emitter

The invariant test imported capExample(value) and checked it directly. The emitter called the same helper โ€” so the test passed. Then the cap was removed from the emitter and the test still passed. The helper was fine; the wire was not capped.

A test that exercises a function is not a test that the function is on the path. The fix was a mutation control: remove the cap from the emission path, and the test must fail. If it does not, the test is measuring a copy.

2. The size was measured on the wrong object

It is tempting to measure len(json.Marshal(example)). But the thing that hits the 8,190-byte wall is not the marshaled struct โ€” it is the base64-encoded challenge document carried in the header, plus every other field that shares the line. The budget check has to use the same serialization and the same envelope the emitter uses. A number measured one layer early is a number about a different object.

3. Unconditional truncation corrupted 223 rows

The first draft truncated every array to one element, always. That is a shape change on every example with a list, including the 1,591 that fit comfortably under the limit: 223 rows were rewritten for a budget problem only 12 of them had. The condition has to gate the transformation, not the other way around.

Our own guard had the same class of bug

To verify that the wire matches the table, we compare the served 402 against the stored example โ€” capped with the same rule first. The first implementation of that check used Python's json.dumps to measure size. Eleven of the twelve capped rows matched. One did not: x402-ai-tests.

Its stored example serialized to 1,988 bytes in Python โ€” under the limit, so our cap left it alone. The live 402 truncated it to 565 bytes. The difference was HTML escaping: Go's json.Marshal writes < as \u003c, and that example contains JavaScript with comparison operators. Measured the way the server measures it, the value was 2,050 bytes โ€” over the limit.

Two implementations of "the size of this JSON" disagreed by 62 bytes, and the disagreement was exactly one row wide. The live response was the only oracle that could settle it.

After matching Go's escaping rules โ€” &, <, >, U+2028, U+2029 โ€” all twelve rows match byte-for-byte. A full sweep of the 1,603-row table found 1,602 matches, 0 drifts, and one 404: an example still stored for a service that had been deactivated. The table was right; the endpoint was gone.

What this changes for a buyer

An advertised example is a promise about shape. A size limit is part of the contract, not an implementation detail. The honest version is not "here is the whole answer" โ€” it is "here is as much of the answer as the transport can carry, and here is exactly where it stops".


You can read any endpoint's current example without paying: curl -s https://minia2a.uk/x402/math | jq .extensions.bazaar.info. The first five calls per signed wallet are free โ€” no registration.