AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Paste an x402 endpoint URL, see exactly what breaks in its payment flow, and get the code that fixes it. Free on the web.
Developer toolsJavaScript · HTMLGitHub @Fizzl13OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
a tool to run diagnostics for x402
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Diagnoses why an x402-payable endpoint's payment flow is broken, without needing a funded wallet.
Paste a URL (web app) or run x402-doctor (CLI, CI) and get back exactly which check failed, why, and how
to fix it. Every check traces back to a real bug hit while shipping
PlainText and
Ichimoku Signal.
▶ Watch the 70-second explainer (with voice and captions):
diagnosing a broken endpoint, the pre-payment check for agents, and the daily Trust Index.Read the full README ↗· Live status ·
Diagnoses why an x402-payable endpoint's payment flow is broken, without needing a funded wallet.
Paste a URL (web app) or run x402-doctor <url> (CLI, CI) and get back exactly which check failed, why, and how
to fix it. Every check traces back to a real bug hit while shipping
Ichimoku Signal and presign-guard.
▶ Watch the 70-second explainer (with voice and captions): diagnosing a broken endpoint, the pre-payment check for agents, and the daily Trust Index.
▶ New: the paid fix in 70 seconds: for $0.05 the Doctor hands you the medicine, the exact code that fixes each problem for your stack, in the browser or as JSON for agents.
| Group | Check | Catches |
|---|---|---|
| 402 challenge | returns-402 | No 402 for GET or POST (tries both, or --method). Behind Cloudflare or Amazon CloudFront the hint names the usual Monetization Gateway or AWS WAF Monetize rule misses. An endpoint that asks for x401 proof first is info, not a failure. A POST that answers 400 or 422 to an empty body checks its input before payment: Doctor sends the requestBody example from the seller's /openapi.json (or one built from its schema), or your own example body, and without one the check is a warning (needs input), not a failure |
x401 | An x401 proof request (PROOF-REQUEST, x401.id) instead of a 402: the endpoint asks who is behind the agent before it asks for payment. Pass when the base64url payload names scheme x401, a version and credential requests (with their protocols, the token endpoint and the payment hint); a warning when agents can't read it; info for the older header name PROOF-REQUIRED | |
protocol-version, challenge-header | v1 vs v2, missing x402Version, challenge in the wrong header, undecodable header | |
envelope-body-mirror | Header-only challenge (@x402/express sends {}), header and body disagreeing | |
| MCP (payment per tool call) | mcp-server | When the URL answers no 402 but speaks MCP (Streamable HTTP, JSON or SSE, with or without a session): initialize and tools/list without payment |
mcp-paid-tools | No tool description says it is paid (x402, USDC, a price), so agents can't tell | |
mcp-payment-required | One unpaid tools/call per paid-looking tool (at most 2; explicitly paid tools first — "Paid …" or a price / "via x402" in the title; never tools that call themselves free, destructive tools, or write tools unless explicitly paid). A free answer warns only for an explicitly paid tool, otherwise it is info: the x402 payment requirement in the tool result with isError: true (as @x402/mcp), or an HTTP 402 with PAYMENT-REQUIRED instead (payable over HTTP, not by @x402/mcp clients), a JSON-RPC error, a tool that answers for free, or example arguments refused before the payment step (publish one valid call as to avoid that). The requirement then gets the payment-option, settlement and wallet checks below |
Statuses: pass, warn (works, but something is off), fail (payments fail or it is not valid x402), info.
npm install
npm start # http://localhost:3001
Reports are shareable: https://<host>/?url=<endpoint>&method=GET runs the diagnosis on load (&method=POST&body=<json> for a POST with an example body).
API: POST /api/diagnose with { "url": "...", "method": "GET" | "POST", "body": { ... } } (method and body optional)
returns { url, method, overall, checks[], challenge, share_url }. body is an example request body (JSON, up to
16 KB) for a POST that checks its input before the 402; it implies POST, and the report says which body reached the 402
(body, and body_source when it came from the seller's /openapi.json). share_url is the page link above, to paste into an issue or a chat. Rate-limited to 10 diagnoses per minute per IP.
GET /api/v1/diagnose?url=<endpoint>&method=GET|POST returns the same report, $0.01 USDC per call on Base or
Solana via x402, with no rate limit. It is meant for agents and CI pipelines; the web page stays free.
PAYMENT-REQUIRED header, mirrored in the body) with a Bazaar input and
output schema.body=<URL-encoded JSON> (optional, also on preflight, deep and fix, and as body in the MCP tools): an example
request body for a POST route that checks its input before the 402. It implies POST; with method=GET, or when it
is not JSON, the answer is a 400 before payment. The batch takes no body.url
answers 402, so indexers such as x402scan can register it. Paying without a url returns a 400./openapi.json (with x-payment-info) and /.well-known/x402.# pay-per-call from Node with @x402/fetch
const paidFetch = wrapFetchWithPayment(fetch, client); // client with an EVM or SVM signer
const report = await (await paidFetch("https://<host>/api/v1/diagnose?url=https://api.example.com/paid")).json();
GET /api/v1/preflightGET /api/v1/preflight?url=<endpoint>&max_usd=0.05&network=<caip2>&method=GET|POST, $0.001 USDC per call:
call it before your agent pays an x402 endpoint it has not used before. It never pays the endpoint. Add
client=gateway when your agent pays with Circle's Gateway client from a Gateway balance: Circle Gateway-only
endpoints (gateway_only, otherwise a caution) are then a go, and a Gateway option is recommended first.
{
"verdict": "go", // go | caution | no_go
"safe_to_pay": true,
"summary": "OK to pay: $0.02 on Solana.",
"recommended_option": 1, // index into the endpoint's accepts[]
"options": [{ "network": "eip155:8453", "asset_symbol": "USDC", "usd": 0.02, "payable": true, "problems": [] }, …],
"signals": { "https": true, "advertised_price_usd": 0.02, "listed_in_cdp_bazaar": true },
"reasons": [], // [{ level: no_go | caution | info, code, message }]
"cached": false
}
| Verdict | When |
|---|---|
no_go | No 402 or no valid challenge; every option would fail to settle (bad payTo/amount, a payTo that is the zero or another burn address (payto_burn, caution when another option is payable), missing fee payer or EIP-712 domain, Solana payout wallet without a token account); cheapest option above max_usd; no payable option on the requested network |
caution | Charges more than its OpenAPI advertises; not HTTPS; not a known USDC (or BNB Chain dollar token) contract; decimal amount, or a 6-decimal amount on an 18-decimal token; resource URL differs from the requested URL; testnet only; bait (below); 20x or more above comparable services (below) |
go | None of the above. recommended_option is the cheapest payable USDC option (on network if given); at the same price, one every x402 client can pay (EIP-3009, not Permit2) and USDC before other dollar tokens |
BNB Chain (eip155:56) has no Circle USDC. Sellers price in 18-decimal dollar tokens: Binance-Peg USDC, USDT,
USD1 and United Stables (U), so $0.01 is "10000000000000000". Doctor prices those options like USDC. USDC and USDT
there have no EIP-3009, so an option without assetTransferMethod: "permit2" fails; a value standard x402 clients
don't know is a warning, because @x402/evm then signs EIP-3009 instead. "permit2-exact" and "permit2-upto" are the
methods of Binance's B402 facilitator, so only its own clients pay those options.
Bait check (TypeSafe's Jev, when TYPESAFE_API_KEY is set): for an endpoint that could be paid, Jev judges
what the protocol checks can't. Does it claim to be, or to be official from or endorsed by, a brand that doesn't own
its domain (brand_impersonation; reselling or proxying a brand's product under the seller's own name, such as an
LLM gateway naming the models it serves, is not), lure with free tokens, airdrops or guaranteed returns (lure_description), or
promise something its declared output doesn't deliver (output_mismatch)? A clear yes is a caution, an
in-between answer info; it never makes a verdict no_go and never turns one into go. The probabilities are
in signals.jev_lure. On 300 real CDP Bazaar listings it raised no caution for brand or lure wording. When a bait
question changes (BAIT_VERSION in lib/jev-lure.js), the Trust Index's next run asks again, first, every service
the old wording flagged or was unsure about; clear answers carry over.
Namesakes (same key, from the daily Trust Index scan, lib/namesakes.js): the x402 Bazaar indexes each
listing's service name, so an agent that searches for a known service finds every listing with that name. When
another seller, with its own payout address and fewer payers, lists under the brand name of an established service,
the preflight adds namesake (info: the name, the original's site and both payer counts). Sites that share a payout
address, a distinctive host-name word or three or more names count as one owner, and TypeSafe's Jev must be sure
(0.85) that the name is a brand rather than a description such as "web search". On 23 Oct 2026, 83 names were listed
by more than one owner and one was a brand with a better-known original. It never changes the verdict: a namesake
can be a fair reseller or an unrelated service.
Price check (same key): is the price far above what comparable services ask? Doctor takes up to 24 paid APIs
of other sellers from the x402 Trust Index whose descriptions share rare words with this one (at most two per
seller), and Jev judges per candidate whether a buyer would be served just as well by it. With at least 5 such
services, their median price is the comparison: 5x or more (and at least $0.05 more) is info, 20x or more (and
at least $0.25 more) a caution, both price_high and naming up to three cheaper alternatives. The comparison is
in signals.price_check (comparable_median_usd, comparables, ratio, cheaper[]). It never makes a verdict
no_go: the price is the seller's choice, the agent decides whether it is worth it.
Not being listed in the CDP Bazaar is reported as info only. Results are cached for 10 minutes per URL, budget
and network (cached: true), so checking before every payment stays fast.
GET /api/v1/preflight/batchGET /api/v1/preflight/batch?url=<a>&url=<b>&max_usd=0.05, $0.005 USDC per call for up to 10 endpoints (one
url parameter each; duplicates count once). For marketplaces, directories and agents that weigh several services.
Each endpoint gets the same verdict as a single preflight; one that errors or does not answer within 20 seconds is
unknown and does not hold up the rest.
{
"count": 2,
"counts": { "go": 1, "caution": 0, "no_go": 1, "unknown": 0 },
"results": [
{ "url": "https://a.example/paid", "verdict": "go", "safe_to_pay": true, "summary": "OK to pay: $0.02 on Base.", "recommended_option": 0, "options": […], "reasons": [] },
{ "url": "https://b.example/paid", "verdict": "no_go", "safe_to_pay": false, "summary": "Do not pay: …", … }
]
}
GET /api/v1/preflight/deepGET /api/v1/preflight/deep?url=<endpoint>&max_usd=0.5, $0.01 USDC per call: everything the preflight answers,
plus
diagnosis: the full diagnosis (overall, every check, and the failing or warning ones as problems);history: the daily scans of the last 30 days, one { date, status } per day;seller: the seller's other scanned endpoints on the same origin (how many, payable today, average payable ratio,
how many were unreliable). A seller whose endpoints were payable on less than half of the scans turns go into
caution (unreliable_seller);domain: the domain-move checks (well-known, bazaar-listing), e.g. a Bazaar listing only under an old domain.GET /api/v1/delivery-checkGET /api/v1/delivery-check?url=<endpoint>&method=GET|POST, $0.25 USDC per check: x402 Doctor buys the endpoint
once, with the example input its own listing declares, and publishes what came back. For sellers who want proof that
their endpoint delivers, and for buyers before a large order.
go from the pre-payment check,
be payable in USDC on Base and cost at most $0.10. Anything else answers 400 (or 503 when the day's 20 checks are
taken), and nothing is charged. A request with a payment always waits for the whole check before anything
settles. A probe without a payment wants the 402: it gets one unless the request itself is malformed (not one
https URL, a fizzl.eu route, a bad method), also for an endpoint that can't be bought now, since uptime monitors
fill url with a placeholder such as https://example.com, and within 5 seconds (the CDP Bazaar's validator and
health probe give up after 10). To get the answer before paying, send X-Delivery-Precheck: 1; the homepage does.id and a status_url.
delivery-check.yml takes the queue (GET /api/v1/delivery-check/queue,
free) and buys with the dedicated spot-buy wallet (SPOT_BUY_KEY; no wallet key on the server). Only a job whose
payment settled is released to the queue. The server starts the workflow as soon as a payment settles
(DELIVERY_DISPATCH_TOKEN: a fine-grained GitHub token for this repository with only Actions read and write;
lib/delivery-dispatch.js); its schedule, every 10 minutes, is the fallback, since GitHub runs scheduled
workflows late or not at all.lib/spot-buy.js): a fresh preflight with the OFAC payout screen right before
paying, the seller's declared example input, the answer checked against the declared output schema and judged by TypeSafe's Jev (is it what was
paid for, does it carry instructions for the agent: lib/jev-answer.js), and the charge measured on chain.GET /api/v1/delivery-check/<id> (free) shows queued, then done with the result
(delivered, off_schema, empty, charged_not_delivered, payment_rejected, error_not_charged, or the step
that stopped it before paying) and the transaction. Every result is on the delivery-data branch.cache says whether a shared cache (a CDN) may store the paid answer
and serve it to callers who did not pay (: , or , with the
it sent). The x402 payment travels in its own header, not in , so only or
keeps it out (, attack III).Jobs are kept in the credits Redis (CREDITS_REDIS_URL); without it the route answers 503 before payment.
GET /api/v1/credits/1000Agents that check often can prepay: one x402 payment of $0.80 for 1000 credits or $7.00 for 10,000 (20% and 30% off), valid for a year. A credit is $0.001: a preflight costs 1, a batch 5, a deep preflight or a diagnosis 10, the fix 50. The pack answers with a credit_key; send it as the x-credit-key header with any paid call instead of paying. GET /api/v1/credits (free) shows the prices and, with the header, your balance. A call that fails gives its credits back; with no key or too few credits the normal 402 applies. Needs CREDITS_REDIS_URL (a persistent Redis such as Upstash; the presign-guard instance is fine, the keys are apart); without it, no packs are sold.
GET /api/v1/fixGET /api/v1/fix?url=<endpoint>&method=GET|POST&stack=<optional>, $0.05 USDC per call: diagnoses the endpoint,
then returns, per failed or warning check, the concrete change that fixes it, as code for your stack and filled in
with your own values (payTo, amount, network, route).
{
"stack": { "id": "express", "name": "Express (@x402/express)", "detected_from": "x-powered-by: Express" },
"summary": "3 fixes (1 blocking), for Express (@x402/express).",
"fixes": [
{
"recipe": "amount",
"severity": "warn",
"checks": ["accepts[0]-amount"],
"title": "Amount \"0.01\" → \"10000\" ($0.01)",
"why": "x402 amounts are integer strings in the token's smallest unit; USDC has 6 decimals.",
"steps": ["Easiest: let the SDK convert it by using price: \"$0.01\" instead of an amount."],
"code": [{ "language": "javascript", "stack": "node", "label": "Route config", "snippet": "accepts: [{ scheme: \"exact\", price: \"$0.01\", ... }]" }],
"verify": "Run x402 Doctor again on https://…; accepts[0]-amount should pass."
}
],
"unfixed": []
}
The stack is read from the response headers (x-powered-by, server, Vercel/Render/Cloudflare headers) and the
challenge: express, next, hono, node (an @x402 SDK behind another framework), python or generic (raw
HTTP, for any language). Pass stack= when the guess is wrong. Node snippets use the @x402 v2 route config, which is
the same for @x402/express, /next and /hono; lines that only apply to Express say so.
Recipes (lib/recipes.js, one test each in test/recipes.test.js): no 402 at all (200 before the paywall, auth or
validation first, 404/405, unreachable), v1 or invalid challenge, wrong header name, no body mirror, no accepts,
scheme, legacy network names, invalid payTo (spots quotes, spaces, the wrong chain), USDC asset, decimal amounts,
Solana fee payer, EIP-712 domain, http resource URL behind a proxy, resource URL and metadata, Bazaar declaration and
example, Solana payout token account, Phantom on PayAI, testnet paywall on mainnet, and openapi.json. Anything
without a recipe is returned under unfixed with the diagnosis hint.
POST /api/v1/outcomeAfter a paid preflight, a buyer can report (free) what happened when it then paid the endpoint: paid_ok, paid_failed (402 again) or paid_error. x402-safe-fetch does it with shareOutcomes: true.
{ "outcome": "paid_failed", "status": 402, "preflight": { …the signed preflight answer… }, "query": { "url": "…", "method": "GET", "max_usd": "0.05", "network": "eip155:8453" } }
receipt.request_id), so a false report costs a paid preflight.payments_fail_after_preflight (caution); signals.agent_outcomes shows the counts.Every paid answer (diagnose, preflight, batch, deep and fix; HTTP and MCP) carries a receipt signed by Doctor, so an agent can later prove which verdict it got for which endpoint, for example why it did or didn't pay:
"receipt": {
"request_id": "…", "route": "GET /api/v1/preflight", "input_sha256": "…",
"payment": { "network": "eip155:8453", "amount": "1000", "payer": "0x…", "nonce": "0x…", "proof": "eip3009" },
"signed_at": "2026-09-27T08:00:00.000Z", "signer": "0x…",
"algorithm": "eip191-canonical-json-v1", "signature": "0x…"
}
receipt.signature, as canonical JSON (profile js-json-stringify-sorted-utf16-ascii-v1: keys sorted by UTF-16 code units, compact, every code unit from U+007F up as lowercase \uXXXX, numbers as JavaScript's JSON.stringify writes them — 1.0 → 1, 0.000001 → 0.000001 — then UTF-8 bytes; Python's json.dumps matches only for ASCII keys and integers, so use examples/canonical.py, which also has verify_receipt(answer)), with EIP-191 personal_sign. Flipping the verdict or moving it to another request breaks it.payment: the payment that bought this answer, from your x402 payment payload. On Base the payer and the EIP-3009 nonce, so anyone can find the settlement on-chain as the USDC contract's AuthorizationUsed(payer, nonce) event; on Solana the payer and a SHA-256 of the signed transaction you sent.input_sha256: SHA-256 of the canonical JSON of {"route", "input"}, where input is your query parameters as strings (HTTP) or the tool arguments (MCP, route mcp <tool>)./.well-known/x402-doctor-signer.json, with retired signers listed so old receipts keep verifying.POST /api/v1/verify with {"response": …, "route": …, "input": …} returns valid, signer, known_signer and input_matches. Over MCP: the free tool verify_receipt with the same arguments (route like mcp x402_preflight, input the tool arguments).Key rotation without client updates: the payout wallet (0x6B0F4651eD42893ab58139938175E4a69f175F25) authorises each signing key with a personal_sign over fizzl receipt signer / service: x402-doctor / signer: <address> / valid_from: <YYYY-MM-DD>. Set it as RECEIPT_SIGNER_CERT (YYYY-MM-DD:0x<signature>, produced by /sign-receipt-key in the wallet's browser). It is checked at startup and carried in every receipt as receipt.cert, so a client that pins only the payout wallet verifies a new key offline.
Same format as presign-guard, so one verifier works for both. The key comes from RECEIPT_SIGNER_SECRET (holds no funds); without it answers are unsigned.
Once a day, trust-scan.yml runs the pre-payment check against every resource in
the CDP Bazaar, the MPP directory, the GoPlausible facilitator's discovery list (x402 on Algorand, about 2,500 endpoints the Bazaar doesn't list) Circle's Agent Marketplace catalog (about 2,800 endpoints, many paid through Circle Gateway) and the XRPL AI Hub (the merchants paid through t54's XRPL facilitator; it has no listing API, so its merchant pages are read, about 190 pages a day) and nohumans.directory's catalogue (CC BY 4.0, mirroring it is a supported use; about 9,600 active listings, some 1,550 of them in no other registry on 25 Oct 2026) and PayAPI Market's catalogue (its open /agent/list; about 940 listings and 1,000 routes, some 300 of them in no other registry on 25 Oct 2026) (read-only: it stops at the 402 challenge, at most two requests per host at a time, one
/openapi.json per origin, a User-Agent that links to /trust). The results roll into a 30-day history per resource,
one letter per day (g go, c caution, n no-go, x unreachable, - not scanned), published as
index/ + summary.json on the trust-data branch. The index is written in parts of at most 24 MB
(index/meta.json, then index/<nn>.ndjson with one [key, resource] per line; lib/index-files.js), so no file
nears GitHub's 100 MB limit as the index grows; index.json and index.ndjson, the whole index as one file, are
published as well while they stay under 90 MB. The scan runs in parallel jobs, each with whole sellers (a plan job
reads the catalogs, four scan jobs, a publish job merges them): a share that fails leaves its endpoints not checked
that day, never marked down. A seller with more than 5,000 endpoints gets a share of them checked each day, so that
each one has its turn within a week (the rest are - that day and keep their last verdict; one seller listed 62,924
on 27 Oct 2026).
The server doesn't load that index (42 MB, ~88,000 resources on 25 Oct 2026): after the scan,
() writes it as small precomputed files on the same branch, from the same code (
with the summary, , , , the price check's word index under
, one per network (one over 16 MB as a head and parts, listed in
), and the resources in 256 buckets by key under ).
reads only what a request needs (one ~130 kB bucket per lookup, at most 32 kept) with the same
answers as the full index, and falls back to the full index while the views aren't published. Measured on the 20 Oct
index: 116 MB peak instead of 285 MB, 37 MB of heap instead of ~100 MB.
After each scan, publishes it as an open dataset on Hugging Face,
(CC BY 4.0): (every endpoint in the latest scan with its
verdict, reasons, price, networks, category and history), (go / caution / no_go per day),
(every purchase of the weekly spot-buy, from the branch),
(signed USDC payments on Base per payout address and day, from the branch) and the dataset card
(). It also keeps the dataset in the Hugging Face collection "x402 agent payments" when the token
may write collections. It needs the repository secret (a Hugging Face write token); the variable
overrides the dataset name.
publishes the
the same way
(): a dataset with every run and its transcript (from x402-examples'
branch), the published tables as , and a static leaderboard Space of the same name. It
runs when the script changes, every Monday and by hand; the variable overrides the name.
publishes as the Space
: a Gradio app that is also an MCP server
(, or added under Hugging Face's MCP settings), with four
free tools over the public API: (), (),
() and (). It never pays or holds a key; the variable overrides the name.
Hugging Face hosts Gradio Spaces only for PRO accounts: without PRO the workflow warns and publishes nothing, and the
Space waits in . Doctor's own MCP server is .
signals.track_record and a caution (unreliable_history) when a seller was
payable on fewer than half of at least 3 scanned days.spot-buy.yml buys a small random sample of "go" endpoints (lib/spot-buy.js):
x402 exact in USDC on Base only, one endpoint per host, never a fizzl.eu or test host, the seller's own declared
example as the request (when it reaches the paywall), a fresh preflight with the OFAC payout screen right before
paying, and hard caps (default 30 endpoints, $0.05 per call, $2 per run; at most 50, $0.10 and $5). Whether money
left the wallet is measured on chain (its USDC balance before and after), and the seller's PAYMENT-RESPONSE is
checked against the chain. Each result is delivered, off_schema (an answer that doesn't match the declared
output schema), empty, charged_not_delivered, payment_rejected, error_not_charged or payment_failed;
the run is appended to the spot-data branch (latest.json, results/<date>.json, a second run that day as results/<date>-2.json). It pays only with the secret
SPOT_BUY_KEY, a dedicated wallet holding a few USDC; without it every run is a dry run that pays nothing.
The latest run is on /trust ("Paid and delivered?") and at GET /api/trust/spot (summary and every purchase, no
wallet details); GET /api/trust?url=… adds spot_buy when that endpoint was bought in it (lib/spot-results.js).
The paid pre-payment check adds the same as signals.spot_buy, and a caution (charged_without_answer) when the
endpoint took the payment for the example request its own listing declares and answered with an error or not within
90 seconds. Each purchase also records its answer time (answer_s, from the first request to the paid answer): the
summary counts answers slower than 30 seconds (slow_answers, and delivered_within_30s_pct, what an agent that
waits 30 seconds gets), and a slow one adds slow_after_payment (info) to the pre-payment check.Every Monday, weekly-report.yml turns the last 7 days of the Trust Index into a
report: how many Bazaar resources are clean, payable with a caution or not payable, the most common reasons with the fix
for each, which networks payable resources accept, and what changed this week (broke, fixed, flaky, new). It names no
seller or host. The report, its numbers and drafts for X, Discord and Reddit (for a person to review and post) go to
reports/<date>.md, .json and -posts.md on the weekly-reports branch. Locally:
node scripts/weekly-report.js --out reports [--index trust-data/index.json].
The same run writes a digest per network, Algorand and the XRP Ledger (network-digest.js):
how many endpoints could be paid there, from how many sellers, what changed since last week (new endpoints, ones that
stopped being payable, new sellers), how many accept only that network, the median price and the biggest sellers. It
names payable sellers only; problems are counted. Files: reports/<date>-<network>.md and -posts.md (X and community
drafts), plus reports/networks/<network>-latest.json, the snapshot next week compares with. Locally:
node scripts/network-digest.js --out reports [--index trust-data/index.json] [--previous reports/networks].
Fizzl's fourth agent, from this repository (buy-server.js, lib/buy/), runs as its own Render service. An agent
says what it needs and the inkoper buys the right paid API for it: POST /v1/buy {"need": "weather forecast for Amsterdam tomorrow"}, $0.02 over x402 (USDC on Base or Solana), for services up to $0.01.
lib/buy/search.js): the wallet server's ranked search of the whole Bazaar, kept to sellers payable in
USDC on Base within the cap and payable on at least 80% of 3 or more days x402 Doctor checked.lib/buy/fill.js): Claude answers the seller's input from the need (the keys of its Bazaar example,
path parameters into the path).lib/buy/purchase.js): a fresh preflight must say go (OFAC payout screen, track record,
namesake, bait); then the inkoper's own wallet (BUY_WALLET_KEY, kept small) pays, within BUY_DAILY_USD a day
(default 5) and its balance.lib/jev-answer.js). Not what was paid for, an error or an empty
answer moves on to the next candidate, at most three. When none delivers the answer is an error and the agent's
own payment is not settled.GET /v1/pick?need=… and the MCP tool x402_pick (free, 10 an hour) say what would be bought and why; the MCP tool
x402_buy buys, paid inside the call ($0.02 over x402, _meta["x402/payment"], the same networks as the HTTP route, and
the same lock against one payment used twice). The MCP server
(https://buy.fizzl.eu/mcp) is in the official MCP registry as io.github.Fizzl13/fizzl-buy (server.buy.json,
published with the Publish to MCP registry workflow, server fizzl-buy). In the ERC-8004 Identity Registry on Base it
is agent #988891.
node scripts/buy-dry-run.js "<need>" … shows the picks, the filled-in requests and the preflights without paying
(needs ANTHROPIC_API_KEY). On 23 Oct 2026 it found a seller with a preflight go for all seven test needs (weather,
BTC price, web search, crypto news, EUR to USD, a token check, a translation), passing over a caution and a seller
that didn't answer 402.
One payment buys one answer: x402 verifies a payment before the purchase and settles it after, so the same payment
sent several times at once would otherwise run several purchases with the inkoper's money; a payment already in use is
refused with a 409 (lib/buy/once.js). The day budget (BUY_DAILY_USD) survives a deploy: each purchase writes what
it paid sellers into the usage log, and a new instance counts what the others spent today (lib/buy/spent-log.js).
Live at https://buy.fizzl.eu since 23 Oct 2026 (the first indexing payment was delivered in 15 seconds). Like the
other fizzl.eu services, its 402 names the seller (x402-merchant) and, with BUILDER_CODE set on the Render
service, carries the Base Builder Code.
Two Agent Skills in skills/ teach a coding agent (Claude Code, Codex, Cursor and others that read SKILL.md) when and how to use Doctor:
x402-preflight: before the agent pays any x402 or MPP endpoint (awal x402 pay, x402-fetch, Privy's x402 client, mppx…), it looks up the free track record, then buys the $0.001 preflight and follows go / caution / no_go, and reports the outcome afterwards.x402-seller-check: after building or changing a paid endpoint, it runs the free check until it passes, and buys the $0.05 fix when a problem isn't obvious.npx skills add Fizzl13/x402-doctor
In Claude Code, the same skills come as a plugin together with the MCP server below, and presign-guard's skill and MCP server as a second plugin. Fizzl Buy, the Fizzl Agent Wallet and Ichimoku Signal have a plugin each, from Fizzl13/fizzl-skills, the overview of every Fizzl skill:
/plugin marketplace add Fizzl13/x402-doctor
/plugin install x402-doctor@fizzl
/plugin install presign-guard@fizzl
/plugin install fizzl-agent-wallet@fizzl
/plugin install ichimoku-signal@fizzl
/plugin install fizzl-buy@fizzl
The marketplace is .claude-plugin/marketplace.json. The paid tools of both MCP servers need an MCP client that pays x402; without one, the skills pay over HTTP with your own x402 client, as they describe.
https://x402-doctor.fizzl.eu/mcp is an MCP server (Streamable HTTP, stateless) for Claude, Cursor and agent frameworks, listed in the official MCP registry as io.github.Fizzl13/x402-doctor. Directories can read its tools without connecting from the server card at /.well-known/mcp/server-card.json (the same list as tools/list).
https://x402-doctor.fizzl.eu/mcp/free is the same server with only the free tools (x402_quick_check, check_payment_proof, x402_trust_record, x402_trust_add), for directories whose clients don't pay, such as Claude's connectors directory. Claude calls connectors from Anthropic's outbound range (160.79.104.0/21), so calls from there share one pool of 300 free calls an hour instead of 10 per address, on both endpoints.
| Tool | Price | Returns |
|---|---|---|
x402_quick_check | free, 10 calls/hour | pass/warn/fail, the number of problems and the top three |
x402_diagnose | $0.01 USDC via x402 | Every check with a fix hint, as GET /api/v1/diagnose |
x402_preflight | $0.001 USDC via x402 | go/caution/no_go before paying an endpoint, as GET /api/v1/preflight |
x402_preflight_batch | $0.005 USDC via x402 | The preflight for up to 10 endpoints (urls), as GET /api/v1/preflight/batch |
x402_preflight_deep | $0.01 USDC via x402 | The preflight plus diagnosis, history, seller and domain checks, as GET /api/v1/preflight/deep |
x402_fix | $0.05 USDC via x402 | The code changes for your stack, as GET /api/v1/fix |
x402_delivery_check | $0.25 USDC via x402 | One real purchase of the endpoint: a job id now, the result shortly after, as GET /api/v1/delivery-check |
x402_delivery_status | free | The status and, once done, the result of a delivery check, as GET /api/v1/delivery-check/{id} |
verify_receipt | free | Whether a paid answer's signed receipt is valid and for which request, as POST /api/v1/verify (see Signed verdicts) |
feedback | free | Report a bug or a missing feature, as POST /feedback (below) |
The paid tools are paid inside the MCP call with the x402 MCP transport (_meta["x402/payment"]), on Base or Solana, at the same prices and to the same payout wallets as the HTTP routes. Invalid input is refused before payment, and a failed call is not charged. A delivery check ordered over MCP is queued only once its payment settled, like the HTTP route.
Found a bug, or missing something? Send it with POST /feedback (free, no payment) or the MCP tool feedback:
curl -X POST https://x402-doctor.fizzl.eu/feedback \
-H 'content-type: application/json' \
-d '{"type": "feature", "message": "Also check the facilitator /supported list", "endpoint": "/api/v1/diagnose"}'
type is bug, feature or other; message is required (up to 2000 characters); endpoint and contact are optional. The answer is 202 with an id. At most 10 reports per hour per caller. Reports go to the usage log (shown on /admin/usage), and a person reads every one; nothing in a report is run or changed automatically. GET /feedback shows the schema. A diagnosis never calls another server's feedback tool: tools that call themselves free are skipped.
npx github:Fizzl13/x402-doctor https://api.example.com/paid
npx github:Fizzl13/x402-doctor https://api.example.com/paid --method POST --json
npx github:Fizzl13/x402-doctor https://api.example.com/v1/score --body '{"text":"hello"}'
Exit code 0 on pass/warn, 1 when a check fails (or on warnings with --strict), 2 on usage errors.
The CLI can diagnose localhost, so you can run it against a dev server.
Check your x402 endpoints on every push. The step fails on a broken 402, each problem shows up as an annotation on the run and the PR, and the job summary lists what is wrong with the fix next to it. On the GitHub Marketplace as x402 Doctor check; a public endpoint that passes also gets a badge snippet.
- uses: Fizzl13/x402-doctor-action@v1
with:
urls: |
https://your-api.example.com/paid
https://your-api.example.com/other
| Input | Default | |
|---|---|---|
urls | (required) | One per line or comma separated. localhost works for a dev server started earlier in the job. |
method | tries GET, then POST | GET or POST |
fail-on | fail | fail: a failed check fails the step. warn: warnings too. never: report only. |
Outputs: overall (pass, warn or fail, the worst across the URLs) and
report (the full reports as JSON). The Action uses the runner's own Node
(20.18 or newer, as on GitHub-hosted runners) and does not change the job's
Node version. Read-only: no wallet, no payment.
Or the CLI in a plain run: step:
- run: npx -y github:Fizzl13/x402-doctor https://your-api.example.com/paid
| Variable | Purpose |
|---|---|
PORT | Web server port (default 3001) |
SOLANA_RPC_URL | RPC for the Solana payout-account check (default: public mainnet RPC, which rate-limits) |
AGENT_PAYOUT_WALLET | Base address that receives paid-API payments (or DOCTOR_PAYOUT_WALLET) |
AGENT_PAYOUT_WALLET_SOLANA | Solana address that receives paid-API payments (or DOCTOR_PAYOUT_WALLET_SOLANA); needs a USDC token account |
ALGORAND_PAY_TO | Algorand address that receives paid-API payments in USDC (ASA 31566704), settled by GoPlausible's facilitator (ALGORAND_FACILITATOR_URL overrides). The account must have opted in to USDC. Unset or off: no Algorand option |
STELLAR_FACILITATOR_KEY | API key for OpenZeppelin's "Built on Stellar" facilitator (generate one at channels.openzeppelin.com/gen); turns on the Stellar option: paid-API payments in USDC on Stellar mainnet, fees paid by the facilitator. STELLAR_FACILITATOR_URL points at another facilitator (with or without the key) |
STELLAR_PAY_TO | Stellar account that receives those payments; default Frits's account (it trusts USDC). The account must have a USDC trustline. off: no Stellar option |
BUILDER_CODE | Base Builder Code (ERC-8021, minted at base.dev) declared in every paid route's 402 as the builder-code extension: the buyer's x402 client echoes it and the facilitator writes it into the settlement, so Base attributes the payment. Unset or not a valid code: none |
DOCTOR_PRICE | Price per paid diagnosis (default $0.01) |
DOCTOR_PREFLIGHT_PRICE | Price per pre-payment check (default $0.001) |
DOCTOR_BATCH_PRICE | Price per batch pre-payment check, up to 10 endpoints (default $0.005) |
DOCTOR_DEEP_PRICE | Price per deep pre-payment check (default $0.01) |
DOCTOR_FIX_PRICE | Price per fix (default $0.05) |
| USAGE_LOG_TOKEN | Fine-grained GitHub token with Contents read/write on the usage-log repo only. Every call is logged there (see below) |
| USAGE_LOG_REPO | The private usage-log repo (default Fizzl13/usage-log) |
| ADMIN_PASSWORD | Password for /admin/usage (any user name). Without it the admin pages do not exist |
Without either payout wallet the paid route answers 503 and the rest of the app works as before.
/admin/usage shows every call to the Fizzl services (x402 Doctor, Ichimoku Signal, presign-guard, Fizzl Buy):
totals, revenue, paying wallets, calls per day per service, the most asked inputs and the latest calls with what
was filled in, the result and the payment (with a Basescan/Solscan link). Each service appends one JSON line per
call to events/<service>/<YYYY-MM-DD>/<HH>.jsonl (one file per UTC hour since 25 Oct 2026; one per day,
events/<service>/<YYYY-MM-DD>.jsonl, before) in the private usage-log repo (lib/usage-log.js, copied into each
service); the dashboard reads both back (lib/usage-reader.js). A bot's unpaid 402s are one line per hour, route
and agent, with count and visitors (how many different callers). 402 challenges, health checks and static files
are not counted as calls, and inputs are cut to 300 characters. Logging never delays or fails a request.
Five Attacks on x402 (Li, Wang and Wang, 2026) describes five ways an x402 payment can go wrong between the HTTP request and the chain, and maps each to a mitigation. How this service handles them:
| Attack | What goes wrong | Here |
|---|---|---|
| I-A Revert-grant | The answer is released before the payment is final, and the payment then fails | The answer is held until the settlement is confirmed: @x402/express buffers it, and the XRPL and Solana paths wait for a validated, successful transaction. A failed settlement answers 402, never the content. |
| I-B Settlement preemption | Someone who sees the payment header submits the authorization first, so the server's own settlement fails | A protocol-level issue; the paper's fix is settlement bound to the facilitator. Here the payment header only travels over HTTPS and is never logged. |
| II Replay | One payment buys several answers | Each payment is claimed once: EIP-3009 nonces on Base, a settlement cache keyed by the transaction on XRPL and Solana (an XRPL payment through t54 is also refused once its transaction is on the ledger, so a restart does not reopen it), and an MPP Tempo transfer must carry the memo of one challenge (valid for minutes) and is claimed by its hash. An XRPL payment's InvoiceID names this service, and credits are taken atomically. |
| III Cache and header confusion | A shared cache (CDN, proxy) stores a paid answer and serves it to someone who did not pay | 402 challenges are Cache-Control: no-store; paid answers carry private (added by @x402/express) or no-store. |
| IV Server selection | Fake or manipulated listings steer agents to the wrong seller | openapi.json carries an ownership proof signed by the payout wallet (x402scan), and the pre-payment check flags impersonation, bait, template farms and payout addresses on the OFAC list. |
Doctor checks other sellers for two of them as well: the paid delivery check records whether a CDN may serve the paid answer for free (III), and the pre-payment check covers IV for every endpoint a buyer is about to pay.
The web app fetches user-submitted URLs server-side:
/api/diagnose (free) is rate-limited per IP; /api/v1/diagnose is paid per call and uses the same guards.It never pays the endpoint it diagnoses: no wallet, no signatures, zero financial risk. A paid probe (sign and settle one minimal payment to trace verify/settle failures) is deliberately out of scope here.
server.js Express app: /api/diagnose (free), paid API, /openapi.json, /.well-known/x402, static frontend
lib/paid-api.js GET /api/v1/diagnose, /preflight (+ /batch, /deep) and /fix behind x402 (Base + Solana USDC)
lib/recipes.js The fix per failed check, as code for the detected stack
lib/stack.js Which stack runs an endpoint, from its response headers
lib/preflight.js Pre-payment check: verdict, recommended option, reasons (cached)
lib/preflight-plus.js Batch (up to 10 endpoints) and deep pre-payment checks
lib/bazaar-index.js Cached CDP Bazaar index (listing signal for preflight)
lib/trust-scan.js Trust Index scan: catalog, polite fetch, 30-day history
lib/trust-index.js Reads the published index for preflight and /api/trust
lib/index-files.js The index in parts (index/meta.json, index/<nn>.ndjson): written, read, loaded over HTTP
scripts/trust-scan.js Daily scan entry point (trust-scan.yml): plan, one share of the sellers, merge
public/trust.html /trust page
lib/media.js Serves the explainer and paid-fix videos from their branches (/media/explainer.mp4, /media/fix.mp4)
media/explainer/ Explainer video pipeline (script, voice, recording, encoding)
lib/diagnose.js The checks
lib/request-body.js Example POST body: the caller's, or the requestBody example from the seller's /openapi.json
lib/site-scan.js MetaMask's site scanner verdict for the domain
lib/mcp-check.js x402 over MCP: tools/list, unpaid tools/call, the payment requirement in the tool result
lib/networks.js Known networks, USDC (or BNB Chain's dollar tokens) per network, address validation
lib/safe-fetch.js SSRF-safe fetch (connect-time IP check, redirects, size cap, timeout)
bin/x402-doctor.js CLI
public/index.html Frontend
skills/ Agent Skills: x402-preflight (buyers) and x402-seller-check (sellers)
.claude-plugin/ Claude Code plugin marketplace: the skills plus the MCP servers of Doctor and presign-guard
test/ node:test suites (fixture x402 servers, SSRF, CLI, API)
npm test
The suites run the doctor against local fixture servers: a healthy v2 service (Base + Solana, Bazaar, OpenAPI, mainnet paywall) and a broken one that reproduces the real bugs above, plus SSRF, size-cap, API and CLI tests.
Copyright (C) 2026 Frits Zwager. Licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only); see LICENSE. If you run a modified version as a network service, you must offer its source code to the users of that service.
_meta.examplesmcp-tool-poisoning | Hidden orders for the agent in tool descriptions (tool poisoning): hidden Unicode characters, <IMPORTANT> blocks, reading ~/.ssh or .env, "do not tell the user", sending the maximum USDC (code rules), then TypeSafe Jev per tool (with TYPESAFE_API_KEY). A sure finding is warn, an unsure one info. Calibrated on 755 tools of 96 servers from the official MCP registry: no false alarm, and five made-up poisoned tools all caught |
mcp-payment-text, mcp-payment-structured | The requirement only in structuredContent (the x402 MCP transport requires JSON text in content[0]), or only as text (structuredContent preferred) |
| Payment options | accepts[i]-scheme, -network | Missing scheme; legacy names like base / solana:mainnet instead of CAIP-2 |
accepts[i]-payto, -asset | Invalid EVM, Solana, XRPL or Algorand addresses (XRPL and Algorand with their checksums); a payTo that is a placeholder nobody holds a key for (the zero address, which USDC refuses, 0x…dEaD, or Solana's System Program or incinerator), also as an MPP recipient; asset that is not USDC, or USDC of another network (e.g. Base Sepolia USDC on Base); on Algorand an asset that is not an ASA id (USDC is ASA 31566704); on XRPL an XRP amount that isn't in drops, or "RLUSD" from another issuer than Ripple's |
accepts[i]-amount | Decimal dollar amounts ("0.02") instead of atomic units, zero or non-integer amounts |
| Stellar options | x402 exact on Stellar (stellar:pubnet, @x402/stellar): payTo a G…, C… or M… address with a valid StrKey checksum, asset a Soroban token contract (USDC is CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75; "USDC" or a testnet contract fails), a positive integer amount (USDC has 7 decimals: 100000 is $0.01) and extra.areFeesSponsored: true, without which the client refuses |
aws-test-mode | A 402 served through Amazon CloudFront that offers only testnets (info): AWS WAF Monetize left in Test currency mode |
accepts[i]-extra | Solana without extra.feePayer (clients throw "feePayer is required"), EVM without the EIP-712 name/version |
accepts[i]-batch, -batch-delay, -batch-deposit, batch-escrow | The batch-settlement scheme (one deposit into the x402 escrow, then a signed voucher per call, claimed in batches): extra.receiverAuthorizer missing (clients refuse), withdrawDelay missing or outside 900 s to 30 days, no EIP-712 domain for the EIP-3009 deposit, minDeposit not in atomic units, the escrow not deployed on the network; the deposit and withdraw delay buyers commit to |
accepts[i]-auth-capture, -auth-escrow, -auth-deadlines, -auth-fees, -auth-domain, -auth-flow, auth-capture-escrow | The auth-capture scheme on EVM (the buyer signs once; the amount is held in the commerce-payments escrow until the seller captures it, with refunds until a deadline): no operator (extra.captureAuthorizer), an escrow that is neither commerce-payments v1.1 nor v1.0, a capture deadline that has passed or a refund deadline before it, fee bounds outside 0 <= min <= max <= 10000 bps or no fee recipient, no EIP-712 domain for the EIP-3009 authorization, a charge (paymentFlow authorization) without receiverAuthorizer, the escrow not deployed on the network; when the money is captured and until when it can be refunded |
accepts[i]-cloudflare, -payto, -amount, cloudflare-registration | Cloudflare pay per crawl (batch-settlement on network cloudflare:402): payTo must be merchant (Cloudflare settles, as merchant of record), the price is a currency code (USD) and an amount in cents, and the 402 carries the http-message-signatures extension with the URL where agents register their signing key. Paid by crawlers and agents registered with Cloudflare (Web Bot Auth), not by wallets: the preflight says caution (cloudflare_crawler_only), not no-go |
accepts[i]-extra, circle-gateway, accepts-order | Circle Gateway options ("exact" signed for the Gateway Wallet, domain GatewayWalletBatched): extra.version other than "1" or no verifyingContract (Circle's client skips or can't sign it), a contract that isn't Circle's Gateway Wallet; who can pay them (a Gateway balance and Circle's client, not standard x402 clients or browser wallets; their 7-day timeout is right); a Gateway option listed before the standard one on the same network, which x402 clients would pick and fail |
accepts[i]-scheme | A scheme standard x402 clients don't pay (anything but exact, upto, batch-settlement and auth-capture; eip3009 gets the hint to use exact): what it requires is unknown, so a missing or unusual payTo, asset or amount is a warning, not a fail (a burn address stays a fail). The preflight never recommends such an option over a standard one; with only such options it says caution (custom_scheme_only) |
accepts[i]-scheme, -network, -plan | Nevermined plans (nvm:erc4337 on eip155:<id>, nvm:card-delegation on stripe, braintree or visa): no payTo or amount is asked, since the price is in the plan; the network must be the settlement rail (not sandbox or live), planId a plan id and extra.agentId set. The preflight says caution (nevermined_plan): payable only with a Nevermined account and its SDK, not by standard x402 clients |
| Resource | resource-url | http:// resource URL on an https:// endpoint (Express behind a TLS proxy without trust proxy); a path instead of a full URL (judged against the endpoint, a warning) |
resource-metadata | Missing description / mimeType |
| Settlement | solana-payout-account | Solana payout wallet without a token account for the asset: every settlement fails on-chain |
xrpl-payout-account, xrpl-payout-trustline | XRPL payout account that was never activated, has Deposit Authorization on, or requires a destination tag the option doesn't give; for a token like RLUSD, no (or a frozen) trust line to its issuer: every payment fails |
accepts[i]-lightning, lightning-invoice, lightning-request-hash | Bitcoin Lightning (x402 exact on lnbtc, Sept 2026): payTo must be the node key, amount in millisatoshis, paymentFlow: "upfront" and a request binding (http:1/mcp:1); the BOLT11 invoice in extra.invoice is decoded and must match the option exactly (amount, bc/tb, expiry = maxTimeoutSeconds, signed by payTo, not expired) and commit to extra.requestHash, which for http:1 is recomputed for the doctor's own request |
algorand-payout-optin | Algorand payout address that has not opted in to the asset (an account can only receive an ASA after opting in): every payment fails |
stellar-payout-trustline | Stellar payout account (G…) that was never funded, or has no (or an unauthorised) trustline to Circle's USDC: every payment fails. Read from Horizon; a contract (C…) needs none |
solana-wallets | Solana settled by PayAI, which rejects Phantom (Lighthouse instructions before the transfer) |
| Who can pay | wallets | Per wallet (MetaMask, Coinbase Wallet, Rabby, Phantom, Solflare, Backpack, Pera, Defly, Xaman, Crossmark, GemWallet, x402 agents, which also pay Stellar): the networks where payment works and where it fails, and why. Also in the JSON report as wallets |
metamask-site-scan | Only with METAMASK_SCAN=on (off by default: the endpoint has no published API or licence). MetaMask's site scanner (Blockaid) blocks the domain (e.g. "wallet drainer"): MetaMask users can't open the site or pay in the browser. With how to report a false positive. Agents are not affected |
evm-payto-eoa | EVM payout wallet is a regular wallet (EOA): MetaMask's Blockaid check may flag the payment signature as "a deceptive request". Info, with how to get it cleared |
coinbase-agents | Whether Coinbase's agent wallets can pay: Wallet MCP pays x402 in USDC on Base with exact, upto or auth-capture and finds services through the CDP Bazaar (the line says whether this route is listed there, from bazaar-listing); the Coinbase MCP also needs x402 v2 and at most $5 a call, and fetches only its own curated catalog. Info when there is no USDC on Base or only another scheme |
| Discovery | bazaar, bazaar-output | Missing or invalid Bazaar declaration, output example not matching its schema |
market-price | Where your price sits among comparable paid APIs of other sellers in the x402 Trust Index (median and range; comparable as judged by TypeSafe Jev, with TYPESAFE_API_KEY). Always info; the hint speaks up when agents' preflights would point at cheaper services, or when you charge far less than the rest |
onchain-payments | Real payments to your payout address on Base in the last 7 days (or on Solana or the XRP Ledger when you have no Base option): how many different wallets paid it, how often and how much, counted from the chain every hour (on Base USDC signed with EIP-3009, the way x402's exact scheme settles; on Solana USDC transfers whose fee an x402 facilitator paid; on the XRP Ledger RLUSD and USDC payments an x402 client marked with SourceTag 804681468 or an InvoiceID; Permit2, Circle Gateway and other networks aren't counted). It says how many payers also paid 10 or more other sellers, as checkers and scanners do, and how to get counted when your endpoint isn't in the Trust Index yet. Always info: new sellers start at zero |
bazaar-replay | The declared example request not answering 402, so the Bazaar cannot index it |
payto-stable | The payout address (payTo) changing from one 402 to the next: a buyer asked again sees a new payTo and may pay twice for one call |
get-probe | A POST route whose unpaid GET answers 404 or 410: directories that probe routes with GET (such as the Agent Exchange x402 census) list it as gone |
bazaar-route | A Bazaar routeTemplate that is not a path (e.g. :var1) or has a :param without a pathParams value, so the request built from it is an invalid URL |
openapi-present, openapi-title, openapi-guidance | Missing /openapi.json, info.title, info.x-guidance |
openapi-contact, openapi-ownership, openapi-input-schema, openapi-free-routes | What x402scan and AgentCash read beyond that: info.contact.email (info), an ownership proof under x-agentcash-provenance.ownershipProofs, checked by recovering the signer of the bare origin against the EVM payTo (pass, or a warning for a trailing slash, another signer, a broken signature or a top-level ownershipProofs that x402scan ignores), paid operations without parameters or requestBody (x402scan does not register them) and operations with neither x-payment-info nor "security": [] (free ones are reported as unprotected). Info lines unless a proof is present but wrong |
well-known | /.well-known/x402 missing (info), unreadable (resources as URLs or objects with a url are both read), or listing resources on another host than the one checked (built from the request Host header, it keeps pointing crawlers at an old domain). A platform address such as *.onrender.com pointing at its own domain is fine |
well-known-names | Resources in /.well-known/x402 without a name and description (info): directories such as the XRPL AI Hub list each resource by them. |
agent-instructions | /skill.md on the origin, so a user can tell an agent connect to <host>/skill.md (Coinbase's pattern); /llms.txt counts as the older form, and an HTML page served on every path does not count. Info when there is none |
bazaar-listing | Where the CDP Bazaar lists this route (same path, same payTo): under this origin, not yet, or only under another host. The Bazaar keeps the URL the payer used, so after a domain move a route stays listed under the old host until someone pays through the new one. On the web check and the paid API (not the CLI) |
bazaar-last-payment | When the CDP Bazaar lists the route here: how long ago it counted the last settled payment. From 21 days a warning, since the Bazaar drops a route after 30 days without one (one paid call keeps it listed). |
cdp-validate | Coinbase's own verdict: Doctor asks CDP's free validate API whether the CDP facilitator would index the route in the Bazaar (the index Coinbase's agents search), and shows the failures it names, without the ones it skips after the first. A warning when the endpoint offers Base or Solana, info otherwise; a pass may carry CDP's advice. HTTPS only; nothing when CDP doesn't answer. Answers cached 10 minutes. On the web check, the paid diagnose and fix, and MCP (not the CLI); the daily health check also runs it on every Fizzl paid route. CDP_VALIDATE=off turns it off |
merchant | The x402-merchant extension (name, website, logo, categories) that GoPlausible's catalog shows on the seller: a warning when malformed, an info line when it is missing on an endpoint that offers Algorand |
builder-code | The Base Builder Code (ERC-8021, the x402 builder-code extension): the buyer's client echoes the app code and the facilitator writes it into the settlement, so Base attributes the payment. A warning when the facilitator would drop it (code not under info, an invalid or missing info.a, invalid or too many service codes, an x402 v1 challenge); an info line when a route that settles on Base has none, or when no option settles on an EVM chain |
| Browser | paywall | Browser paywall in testnet mode on a mainnet endpoint (@x402/paywall defaults to testnet; the flag is read from JS or JSON config) |
shared_cacheyesnounspecifiedCache-ControlAuthorizationCache-Control: privateno-storescripts/trust-views.jslib/trust-views.jsviews/meta.jsonviews/atlas.jsonviews/templates.jsonviews/sellers.jsonviews/similar/views/networks/<key>.jsonviews/meta.jsonnetwork_partskeys/lib/trust-reader.jsdata/endpoints.jsonldata/daily.csvdata/spot_buys.jsonlspot-datadata/onchain_payments.csvpayments-datascripts/hf-export.jsHF_TOKENHF_DATASETscripts/hf-bench.jsx402-agent-safety-benchbench-dataleaderboard.csvHF_BENCH_REPOhttps://x402fizzl-x402-doctor.hf.space/gradio_api/mcp/check_x402_endpoint/api/diagnosetrust_record/api/trustadd_to_trust_indexPOST /api/trust/submitx402_market_health/api/trust/summaryHF_MCP_SPACEhf-mcp/https://x402-doctor.fizzl.eu/mcpdelivery-nohumans.jsondelivery-payapi.jsontrust-datasignals.delivered_elsewheresourcesinfodelivered_elsewhereGET /api/trust?url=…delivered_elsewhere/trustlib/delivery-elsewhere.jsscripts/index-payment.jsstaleINDEX_PAYMENT.routesurlpayTo, MPP recipient) is matched against the OFAC SDN list's
digital currency addresses, from presign-guard's free open copy of the
US Treasury list (lib/sanctions.js, refreshed every 6 hours). A listed address makes the pre-payment check no_go
(payto_sanctioned, with the SDN entry), in the paid check and in the daily scan; signals.sanctions says what was
screened and against which publication. summary.json counts the scanned resources that pay out to a listed address
(payto_sanctioned). Without the list nothing is judged (signals.sanctions.unavailable).0x000000000000000000000000000000000000dEaD, and on
Solana the System Program or the incinerator. The pre-payment check says payto_burn: no_go when no other option
is payable, caution otherwise (a client that signs the first option it can would pay the broken one).
summary.json counts them (payto_burn).onchain-payments.yml reads the
new Base blocks (scripts/onchain-payments.js, lib/onchain-payments.js) and counts, per payout address the daily
scan saw (paytos.json on trust-data), the USDC payments the payer signed with EIP-3009
(transferWithAuthorization, the way x402's exact scheme settles USDC) and from how many different wallets. Plain
transfers, Permit2 and Circle Gateway batches don't count, and x402 Doctor's own test wallet is left out. The result,
8 days per address, is on the payments-data branch. The paid pre-payment check adds signals.onchain_payments
(days, payments, payers, usd, coverage: the share of the window's blocks that were read) and an info line
(onchain_payments), e.g. "47 different wallets paid this seller's payout address on Base in the last 7 days". It
never changes the verdict: new sellers start at zero. A wallet that paid 10 or more different payout addresses in the
window is a checker or scanner, not an ordinary buyer: payers_wide counts those, and the info line says when some
or all of the payers are such wallets ("no ordinary buyer seen yet"). GET /api/trust/summary adds the totals over
all payout addresses (onchain_payments, with pay_tos_paid_only_wide: the addresses paid only by such wallets;
393 of 774 on 19–20 Oct), shown on /trust. Read-only, no key: BASE_RPC_URL is asked first when set, then
the public node base-rpc.publicnode.com, which serves only about the last 10,000 blocks, so a gap of more than
~5 hours between runs shows up as lower coverage. GitHub runs the hourly schedule late (every 3.5 to 6.5 hours on
19 Oct), so the Doctor starts the workflow every hour itself, with the DELIVERY_DISPATCH_TOKEN it already has.paytos-solana.json, with the facilitators that pay their fees: each offer's extra.feePayer) and counts, per
address, the USDC transfers into it whose network fee one of those facilitators paid, the way x402's exact scheme
settles on Solana (lib/onchain-payments-solana.js); a plain transfer, where the sender pays its own fee, doesn't
count. Solana has no log filter over many accounts, so each address's USDC accounts are asked for their new
signatures since the last run and each new transaction is read once; addresses not reached within the run's time
budget are read by the next run, so counts come later but nothing is lost. The result, in the same shape as Base's,
is payments-solana.json on payments-data. A seller without a Base option gets the same onchain_payments line
and signal for its Solana payout address, /api/trust/summary adds onchain_payments_solana, and the preflight's
Solana options carry fee_payer. SOLANA_RPC_URL is asked before the public node, which allows a few calls a second.paytos-xrpl.json), and counts, per address, the RLUSD and USDC payments into it that an x402 client marked
(lib/onchain-payments-xrpl.js). On the XRP Ledger the buyer signs a complete Payment and pays its own fee, so no fee
payer tells an x402 payment apart; x402 clients mark it instead: t54's SDK signs with SourceTag 804681468, and
@x402/xrpl binds the invoice in InvoiceID. A payment with neither, or one in XRP (no dollar amount without a price),
doesn't count; RLUSD and USDC are recognised by their issuers. x402's batch-settlement scheme settles on XRPL with a
Check (t54's "xrpl-check-session"): the buyer writes one to the seller and the seller cashes it for many calls at
once, so each cashed Check counts as one payment from the buyer who wrote it. Each address's transactions since the last ledger read
are asked with account_tx, oldest first, and a run cut short keeps its last ledger whole for the next one. The
result is payments-xrpl.json on payments-data; a seller without a Base or Solana option gets the same
onchain_payments line for its XRPL payout address, and /api/trust/summary adds onchain_payments_xrpl.
XRPL_RPC_URL is asked before the public nodes (xrplcluster.com, xrpl.ws).paytos-stellar.json; a Soroban transfer the payer authorized and a facilitator sent,
lib/onchain-payments-stellar.js) and the Algorand payout addresses (paytos-algorand.json; a USDC transfer in an
atomic group whose fee another transaction paid, the facilitator's fee pooling, lib/onchain-payments-algorand.js).
On both, the addresses the x402 facilitators paid for are watched too, registry listed or not: each run reads what is
new at their fee accounts (OpenZeppelin and agent402.tools on Stellar, GoPlausible's fee payer on Algorand) and keeps
an address found that way for 14 days after its last such payment (found in the file). The results are
payments-stellar.json and payments-algorand.json on payments-data; /api/trust/summary adds
onchain_payments_stellar and onchain_payments_algorand. STELLAR_HORIZON_URL and ALGORAND_INDEXER_URL are asked
before horizon.stellar.org and AlgoNode's public indexer.paytos-xlayer.json), exactly as on Base, 100 blocks per call (the most rpc.xlayer.tech serves;
lib/onchain-payments-xlayer.js). The result is payments-xlayer.json on payments-data; /api/trust/summary adds
onchain_payments_xlayer, GET /api/trust/payments?network=xlayer lists who got paid, and /xlayer shows it.
XLAYER_RPC_URL is asked before rpc.xlayer.tech.trust-scan.yml with
only_if_missing, which stops at once if today's scan was published meanwhile and waits behind one still running
(lib/scan-watch.js, at most one start per two hours, the same DELIVERY_DISPATCH_TOKEN).GET /api/trust?url=<resource> (free, 30/min per IP): the track record of one resource.
POST /api/trust/batch with {"urls": [...]} (free, up to 100 URLs, 10 calls/min per IP): the same for many at once,
in the order sent (found: false for a URL the index doesn't have), with the index's days and updated once at the
top. For directories and indexers that re-test their own lists; the whole index is on the trust-data branch.
POST /api/trust/submit with {"url": "https://…"} (free, 10 an hour per IP): a seller adds an endpoint no registry
lists yet. Doctor checks that it answers 402 with a payment challenge (x402, MPP or L402) and keeps the submission in
the usage log repo (events/trust-submit/); the next scan reads GET /api/trust/submitted and scans it into
submitted.json, apart from the index, so lookups and badges find it (marked submitted: true) while the totals,
pages and datasets keep counting registry listings only. At most 25 per host; one that a registry lists later moves to
the index, and one not payable on any of its last 14 scanned days drops out.
GET /api/trust/summary: totals of the latest scan. /trust: the public page, with lookup and the scan's rules.GET /api/trust/network?network=xrpl (or an id like xrpl:0, free): every scanned x402 endpoint that offers a network,
payable there today or not and why (the scan records unpayable networks with their first problem), grouped per
seller site. /xrpl: the public status page of x402 on the XRP Ledger, with the ledger checks of each payout account
and who gets paid there: GET /api/trust/payments?network=xrpl (free) lists every watched XRPL payout address paid in
the last 7 complete days, most money first, with its XRPL AI Hub merchant name or its endpoints' hosts, payments,
dollars, wallets and how many of them pay ten or more sellers (the names come from the daily scan, in
paytos-xrpl.json). /algorand: the same for x402 on Algorand, with the USDC opt-in of each payout account. /stellar: the same for x402 on Stellar, with the USDC trustline of each payout account. Both list who gets paid there too (?network=algorand, ?network=stellar), naming the facilitator for an address no registry lists.lib/services.js) count as one service; on 9 Oct 2026 the 40,451 payable endpoints were
18,475 services, and one seller alone listed a third of them. Each service gets a category (crypto, security,
on-chain, AI, search, finance, media, places, social, developer tools, other), chosen by TypeSafe Jev after the
daily scan (scripts/categorize.js, cached per description in categories.json on the trust-data branch, at most
1,500 new ones a day) and guessed from keywords until then. In /api/trust/summary (services) and
/api/trust/network (services, categories, per seller services, per endpoint category and
same_service_endpoints), and on /trust, /algorand and /xrpl.categories.json.
Services Jev is sure about (≥ 0.85) are listed on /trust and in /api/trust/summary (services.bait: checked,
flagged and the surest 100), and marked on their endpoints in /api/trust/network and on /algorand and /xrpl.node scripts/trust-scan.js --out trust-data --limit 200, then node scripts/trust-views.js --dir trust-data.FACILITATOR_URLx402 facilitator (default PayAI, https://facilitator.payai.network) |
CDP_API_KEY_ID, CDP_API_KEY_SECRET | Use Coinbase's CDP facilitator for Base (PayAI stays the fallback). Payments settled through CDP get the route listed in the CDP Bazaar |
CDP_VALIDATE | off stops the cdp-validate check, which sends the checked URL to Coinbase's public validate API (api.cdp.coinbase.com/platform/v2/x402/validate, no key) |
DOCTOR_SOLANA_FACILITATOR | With CDP keys set, Solana is settled by PayAI (CDP takes over if PayAI is down). Set to cdp to settle Solana through CDP too |
MPP_SECRET | Turns on MPP payment (method evm, USDC on Base, via the same facilitator) next to x402 on the paid routes, for agents that speak MPP (mppx). Any long random string; it signs the challenge ids. Without it only x402 is offered |
OUTREACH_URL / OUTREACH_KEY | Optional, both needed: when the free web check finds a broken endpoint (a fail) whose /openapi.json publishes info.contact.email, Doctor hands a draft e-mail to the Fizzl wallet server at OUTREACH_URL (https://wallet.fizzl.eu), authenticated with OUTREACH_KEY (the same long random string as there). The wallet server only makes a draft; the owner decides on Telegram or the dashboard whether it is sent. Fizzl's own hosts are skipped, and each host at most once a day. The same key lets the wallet server ask POST /api/outreach/recheck ({url, ids}) right before it mails a draft: which of its findings (web check ids or preflight reasons) are still there |
TYPESAFE_API_KEY | Optional: TypeSafe's Jev judges a broken endpoint before an outreach draft is made (lib/jev-triage.js): no draft for a test, preview or tunnel service, or for findings that would not hurt a paying agent. Off or failing: drafts as before. Plainly test addresses (staging., sandbox., testnet, tunnels, Vercel previews) and clear real problems (http resource.url on https, an internal or raw cloud-function resource.url, a price ≥ 1.5x the advertised one, no payable option) are decided by code without asking. With ANTHROPIC_API_KEY too, Claude decides the cases Jev is unsure about. The same keys as repository secrets let .github/workflows/triage.yml rank all Trust Index candidates by hand |
MPP_TEMPO_RECIPIENT | With MPP_SECRET: also accepts MPP method tempo (push mode: the agent sends USDC.e on Tempo with the MPP memo and answers with the transaction hash, which the Doctor checks on a Tempo RPC). The address that receives it on Tempo; make sure you hold its key in a wallet that supports Tempo. Without it only evm is offered |
MPP_TEMPO_CHAIN | 4217 (Tempo, default) or 42431 (Moderato testnet, pays in pathUSD) |
MPP_TEMPO_RPC | Overrides the Tempo RPC (default https://rpc.tempo.xyz, or the Moderato one) |