Why 100+ Live API Endpoints Were Invisible to AI Agents

BlockRun Engineering · August 2026
A human developer who wants to know what your API can do opens your docs and reads. An agent cannot do that. It fetches a machine-readable manifest — /openapi.json, /.well-known/x402 — parses it, and calls what it finds. Anything absent from that file does not exist. Not "hard to find." Does not exist.
On 2026-08-04 we audited BlockRun's three published surfaces against the endpoint registries the routes themselves use. Every handler was healthy. Every endpoint returned correct data to anyone who knew the path. And more than a hundred of them were invisible to any agent that discovers before it calls.
This is a report on what we found, why the failure mode is structural rather than accidental, and the one change that fixes the whole class.
§ 1 — The gap
The audit compared what each partner integration served against what each surface advertised, measured on 2026-08-04 before the de-registrations described in § 5. (Predexon's templated count is 25 here and 20 today, because that same audit removed six dead paths and withheld four more — see below.)
| Integration | Live | In openapi.json | In /.well-known/x402 |
|---|---|---|---|
| Surf (crypto data) | 83 | 17 | 0 |
| 0x (swap / gasless) | 9 | 0 | 0 |
| DefiLlama | 5 | 5 | 0 |
| Predexon (templated paths) | 25 | 0 | 25 |
| RPC networks | 40 | 1 | 5 |
Read the Surf row again. Eighty-three endpoints in production, seventeen advertised in one manifest, zero in the other. An agent reading the x402 manifest — the file the x402 ecosystem treats as the canonical list of callable, payable resources — would conclude BlockRun had no crypto data product at all.
Not listed above, because they had no entry of any kind: phone and voice, music generation, RealFace and Portrait enrollment, Polymarket funding, fiat onramp, and all four model catalogs.
Nothing here was a route bug. The handlers served all of it. The endpoints were simply invisible to the only mechanism an agent has for finding them.
§ 2 — Why it happens
The failure is not carelessness. It is the predictable result of a manifest that restates an API rather than deriving it.
Every one of those surfaces was hand-maintained. Surf's seventeen published endpoints were seventeen hand-written OpenAPI blocks. When Surf grew from seventeen endpoints to eighty-three, the registry the router uses grew with it — and the seventeen blocks did not, because nothing connected them.
This is the same shape as a stale comment. The code moves; the description of the code does not; nothing fails, so nobody notices. The difference is that a stale comment misleads a human who can read the code anyway. A stale manifest misleads a machine that cannot.
There is a second-order version of the same bug, and we had that too. Templated paths — /pm/polymarket/candlesticks/{condition_id} — were skipped by the OpenAPI generator entirely, on the reasoning that a path with a placeholder in it is not a callable URL. That reasoning is human. An x402 registry probe calls the advertised URL literally. A path with {condition_id} in it 404s, so publishing it looked worse than publishing nothing.
The fix is to publish a concrete example id and let the route answer 402 before it proxies anything upstream. The probe gets a real payment challenge, the registry accepts the resource, and no junk id ever reaches a partner. Twenty-five endpoints went from unpublished to discoverable without a single route change.
§ 3 — Derive, never restate
The structural fix is one rule: every published surface generates from the registry the router already uses.
Surf's seventeen hand-written OpenAPI blocks are gone, replaced by a generator over SURF_ENDPOINTS. DefiLlama's five are gone the same way. The x402 manifest builds its resource list from the same registries. Adding an endpoint now adds it to all three surfaces, because there is only one list and the surfaces are functions of it.
Measured against the deployed build:
| Surface | Before | After |
|---|---|---|
openapi.json paths | 95 | 185 |
/.well-known/x402 v1 resources | 105 | 238 |
| x402 v2 service entries | 22 | 31 |
The after-numbers are live and independently checkable right now:
curl -s https://blockrun.ai/api/openapi | jq '.paths | length'
# 185
curl -s https://blockrun.ai/api/.well-known/x402 | jq '.resources | length'
# 238
The before-numbers are the audit's own measurement on the pre-change build, recorded here as a dated snapshot.
§ 4 — The same rule catches wrong prices
Once you accept that a surface must be derived, a second class of bug becomes visible: a published price is a restatement too.
We found prices in the manifests that no longer matched what the payment layer actually asked for. Most over-quoted. Several under-quoted, which is worse — a spec that promises less than the 402 signs will make an agent budget for one number and get charged a larger one.
The sharpest case: one endpoint family published $0.0010 in the OpenAPI spec, $0.003 in the x402 manifest, and $0.0010 in its own 402 response body — against a signed amount of $0.0020. Three surfaces, three numbers, none of them the one the wallet was asked to sign.
The root cause was a helper that adds the per-transaction fee inside the payment-requirements builder. Any code that rendered the base constant published a number the caller never pays. Every price now renders through one function, and a test asserts the published amount equals what buildPaymentRequirements actually signs — rather than comparing the spec against the same constant the generator imports, which is a check that can only ever pass.
If your test compares a generated document to the constant that generated it, you have tested nothing. Compare it to the thing that takes the money.
§ 5 — Dead endpoints are a discovery bug too
An audit that only adds is half an audit. We probed every partner path directly, three runs each.
Six endpoints were dead upstream — three returning 410 Gone from a sunset, three returning a not-found body distinct from the "no data for this wallet" a live endpoint returns for an unknown address. All six were still advertised. An agent that discovers a resource, signs a payment for it, and gets a 410 has been sent somewhere that does not exist.
Four more returned a consistent 500. That is a partner-side fault, not a sunset, so deleting them would be wrong — they will come back. They now carry a degraded flag: the routes keep serving, and they drop out of every published surface until the upstream recovers. One registry field, and the manifests follow.
§ 6 — What this means if you are building for agents
Three things generalize past our codebase.
Your discovery surface is your product surface. For a human audience, docs are marketing and the API is the product. For an agent audience they are the same artifact. An endpoint absent from your manifest has no users, no revenue, and no bug reports — it looks exactly like an endpoint nobody wants.
Anything hand-maintained alongside code will drift, and drift silently. The question is not whether your manifest is currently correct. It is whether it is derived. If a human has to remember to update it, it is already wrong; you just have not measured yet.
Probe your partners, do not trust their docs. Six of our dead endpoints were documented as live upstream. We found them by calling them. A discovery manifest built from documentation inherits every error in that documentation.
The audit that produced this post took one afternoon and changed no route logic. It moved a hundred-odd endpoints from invisible to callable by changing where the manifests get their information, not what the API does.
Numbers in this post are a dated snapshot: audit conducted 2026-08-04, measurements taken against the build deployed 2026-08-05. The two curl commands above are live — run them.