Blog · x402 & micropayments

You Paid $0.05. It Was Free Two Doors Down.

We bought a $0.05 x402 endpoint and diffed it against the same vendor's free one: four fields were byte-identical, and its one substantial field was a passthrough of Stellar Horizon's public path finding. The x402 quote never says what you're getting — so we taught our gate to measure it.

August 10, 2026 · 9 min read

An x402 quote tells you the price. It does not tell you what you're buying.

That sounds like a small gap in a payments protocol. It isn't. Last week our agent paid $0.05 for a market-data endpoint, and when we diffed the response against the same vendor's free endpoint — no key, no payment, two paths away on the same host — four of the fields came back byte-identical. The one field that looked genuinely valuable turned out to be a passthrough of a public blockchain API that anyone can call for nothing.

The vendor isn't a scammer. Their premium endpoint has real content in it, and we'll show exactly what. The problem is that nothing in the payment flow gave our agent a way to know any of this before signing. A human buyer would have clicked around for thirty seconds and figured it out. An autonomous agent has no thirty seconds and no curiosity — it has a budget check, and a budget check says yes.

Three doors to the same room

The vendor runs a treasury agent that rebalances stablecoins against tokenized Mexican T-bills, so it publishes a market snapshot: the native token's price, the network's base fee, DEX spread, DeFi supply and borrow rates, the T-bill reference rate, pool depth. Useful stuff if you're deciding where to park money.

That snapshot is reachable three ways:

EndpointHow you prove you're allowed inPrice
/api/tickersnothing at allfree
/api/marketan API key (401 without one)plan
/api/v1/premium/marketan x402 payment$0.05 USDC

The third door is the interesting one, and it's the right idea: an autonomous agent has no account, no plan, no relationship with the vendor. It's a stranger passing through. Pay-per-call is the only door built for a stranger — that's the whole argument for x402, and it's a good argument.

The question is what's behind it.

What the quote says

Here is the actual payment-required header from that endpoint, base64-decoded (x402 v2, the exact scheme on Stellar):

{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": ".../api/v1/premium/market",
    "description": "",
    "mimeType": ""
  },
  "accepts": [{
    "scheme": "exact",
    "network": "stellar:pubnet",
    "amount": "500000",
    "asset": "CCW67TSZ…MI75",
    "payTo": "GCLBBPON…KBHP",
    "maxTimeoutSeconds": 300,
    "extra": { "areFeesSponsored": true }
  }]
}

Everything about the money is precise: 500000 atomic units of a named USDC contract, to a named account, on a named network, fees sponsored, valid for 300 seconds. Our gate can check every one of those against policy, and does.

Everything about the goods is empty. description: "". mimeType: "". The protocol has the fields — the vendor left them blank, and nothing in x402 requires otherwise. So the only way to learn what $0.05 buys is to spend it.

So we spent it

On testnet, where the tokens are worthless, so the answer cost nothing: transaction 58f43186…, 0.05 test USDC, HTTP 200, 859 bytes of JSON. Our gate held it for approval first (the price is above our $0.04 sign-off threshold), we approved it, it paid.

Flattening both payloads to leaf fields and comparing paths gives 15 fields in the paid response. The breakdown:

Four fields are byte-identical to the free endpointmarket.xlmPrice, market.baseFee, market.sdexSpread, market.soroswapPoolDepth. Same paths, same values, same host, one of them behind a paywall.

Three more are the same data wearing a different name. The paid response calls it market.blendApy.supply and market.blendApy.borrow; the free one calls it blendSupplyRate and blendBorrowRate. The paid response says market.etherfuseApy: 5.57; the free one says cetesRate: 5.57. A field-path diff scores those as "exclusive to paid," which is exactly the kind of number that makes a rename look like a feature. They're not new information.

Three are metadata — network, protocol, timestamps.

And then there's the real content. market.pathPaymentRoutes: three concrete quotes for converting the native token into USDC at sizes of 10, 100 and 1000, each with the input amount required and a profit percentage. That is not a spot price — it's what it costs to actually move that size, which is the only number an agent needs before executing. Plus two interpreted strings, feeAlert: "NORMAL" and networkConditions: "NORMAL — base fee within the usual range", and the reference rate with its source labeled.

So: not a rip-off. A superset, with one genuinely actionable field. We were ready to call it fair.

Then we checked where the good part comes from

Path finding is a native feature of the Stellar network, and Horizon — the public API every wallet on that network already talks to — exposes it. No key, no payment, no account:

curl "https://horizon.stellar.org/paths/strict-receive?\
source_assets=native&destination_asset_type=credit_alphanum4&\
destination_asset_code=USDC&destination_asset_issuer=<issuer>&\
destination_amount=10"

The paid payload said it takes 5.5691028 of the native token to receive 10 USDC. Horizon, free, said 5.5691823. That's a gap of 0.0014 % — the order book moving between two calls, seconds apart. It's the same number from the same place.

Which reduces what $0.05 exclusively buys to two English sentences that any client could derive from the base fee it already has for free.

Again: this is a defensible product for someone who doesn't want to implement path finding, manage a trustline, and parse Horizon. Convenience is a real thing to sell. It just isn't a thing we wanted to buy, because our payment rail already talks to Horizon directly — that's how it measures its own balance. The vendor never claimed otherwise. Nobody lied. The buyer simply had no way to know.

A budget check can't answer "is this worth it"

Look at what our gate checked before approving that payment, because on paper it's thorough. Is the network on the allowlist? Is the asset the real USDC contract and not a lookalike? Is the payee the account we expect? Is the price within the agreed band for this resource? Is it under the per-call cap? Does it fit today's budget? Does it need human sign-off?

Every one of those questions is about whether we can afford it and whether we're being overcharged relative to a price we already agreed to. Not one is about whether the thing has value. A vendor could charge $0.05 for an empty JSON object and pass all seven checks, as long as $0.05 was the agreed price.

That's the gap, and it isn't specific to one vendor. It's structural: the protocol carries a price and omits a description, so governance built on the protocol inherits a blind spot exactly where the money meets the goods.

What we built instead

A new rule in the gate, price.overlap. It works like this:

A policy can declare that a paid resource has a public alternative — a URL that covers the same ground. After the first purchase, the gate measures the overlap: what fraction of the payload it just paid for was byte-identical to what that free source gives away. On the next request for that resource, if the overlap crosses a threshold, the verdict escalates to HOLD — human sign-off required — with the number in the reason:

27 % of the last paid payload for /api/v1/premium/market was byte-identical
to the free /api/tickers (4/15 fields; 11 fields exclusive to the paid
version) — $0.05 needs human sign-off on VALUE, not budget

Four design decisions worth stating, because each one was a choice:

It holds, it never blocks. Overlap is not worthlessness. Our own measured case overlapped 27 % and still carried a field we'd have paid for. A rule that blocked here would be wrong more often than right. What can't happen is a threshold quietly deciding on its own.

It can't judge a first purchase. You cannot measure a payload you have never seen, so the first buy of any resource passes untouched and teaches the gate what it's buying. From the second one on, the gate knows. The audit trail is the memory — which is, conveniently, the thing we already built.

The probe fails open. If the free endpoint is down, unreachable, or returns something that isn't JSON, there's no measurement, and no measurement means no escalation. A value rule that failed closed would turn someone else's outage into a jammed cash register.

The threshold's default is 25 %, and that number came from the measurement, not from taste. Our real case sits at 27 %. A tidier-sounding default of 50 % would have let the very case that motivated the rule sail through in silence.

Seven tests cover it, using the two real payloads as fixtures rather than invented ones, and the suite is at 55 passing.

A footnote on the money door

We had planned to buy that endpoint once on mainnet, with real USDC, to capture a payload with real liquidity in it. It didn't work: the paid retry came back HTTP 402, no transaction was ever submitted to the network, and nothing left the wallet.

Readers of the last post in this series will recognize the shape and notice it's inverted. That time the chain settled a payment HTTP had reported as failed. This time HTTP reported failure and the chain agreed. Same money door, two different ways to be wrong about it.

We initially blamed the vendor, and we were wrong — worth saying plainly, since this post is otherwise about them. We wired up two unrelated sellers on the same network and got the identical failure from all three: a 402 on the paid retry with an empty requirements document, no settlement, no transaction. Our client sends what the canonical library sends, on a dependency set unchanged since the payments that did work days earlier. Three independent vendors sharing one facilitator, failing the same way, is not three vendor bugs. It's one shared-infrastructure bug, and the vendors are as stuck as we are.

There's a lesson in that for anyone building on this: we thought our risk was depending on a single vendor. Our actual single point of failure was one layer down, in the facilitator every seller on that network happens to use. Adding vendors bought us nothing against it.

The part that matters to us: the ledger recorded it as UNCONFIRMED — a verdict we added specifically because of the last incident, for outcomes where the vendor refuses and no debit is measured, since settlement may still land late. Reconciliation ran a minute later, compared the on-chain balance against the ledger, found them equal to the last decimal, and closed the day clean. The improvement we promised in public did its job on its first real outing, which is more than we can say for the endpoint.

It also exposed a bug of our own, which is the honest half of this footnote. Our reconciliation only knew how to close an UNCONFIRMED by finding the missing money — and here no money was missing, so the entry would have stayed open forever. That's worse than untidy: unresolved entries are matched oldest-first, so a stale one would have absorbed the shortfall of some later payment that really did settle late, filing that debit against the wrong decision. An audit trail that misattributes a charge is a defect in the only thing it exists to do. So we added a third outcome: after a 24-hour grace period — roughly 2,880 times the 30 seconds the phantom settlement took — an UNCONFIRMED with no shortfall behind it closes as x402.never-settled, and the ledger stops confusing waiting with resolved in the negative.

Because we investigated instead of assuming, we found one bug on each side of the wire. Theirs cost us nothing. Ours would have cost us the trustworthiness of the record, eventually, quietly.

Every entry above is in the public ledger — the blocks, the holds, the unconfirmed settlement, the reconciliation checkpoints. You don't have to take our word for any of it, which is the entire point of building it that way.

What to take from this

If you're building an agent that pays: your budget guard is not a value guard. Before you wire a paid endpoint into an autonomous loop, spend the five minutes a human would spend — read the vendor's public routes, check whether the data has a canonical free source, and diff a paid response against it once. Then encode what you learned, so the agent doesn't have to learn it again.

If you're selling data over x402: fill in description and mimeType. They cost you nothing and they're the difference between an agent that can evaluate your offer and one that can only afford it. And if your premium tier is convenience over a public source — that's a fine thing to sell, so say it. The buyers who need it will still pay.

If you're writing the protocol: a price without a description is an invoice without a line item. Every other payment rail in the world figured out that the line item matters. Machine buyers need it more than we do, not less.

AgentLedger puts budgets, approvals and a tamper-evident audit trail on top of the wallets your agents already use — and now measures what they're buying, not just what they're spending.

Join the waitlist →