cryptomonnaie — pay-per-call API over x402

MCP serverCommerce & finance

Pay-per-call MCP tools (crypto/DeFi data, web reading, AI tasks) in USDC over x402 on Base.

Unavailable. This server has no hosted endpoint yet, so ahel can't serve it.

Connect ahel once, and every AI you use reads what you have installed.

From the project's README

As published by entreprisedaney33-rgb/x402-seller in README.md.

An Express server that sells paid API endpoints over the x402 protocol (USDC payments on Base), built to be consumed by AI agents.

A client (human or agent) calls a paid endpoint → the server replies 402 Payment Required with the payment requirements → the client signs a USDC payment and replays the request with the PAYMENT header → a facilitator verifies and settles the payment on-chain → the server serves the response. No blockchain key management server-side: it only holds the receiving address.

Available endpoints

All /api/* routes are paid (x402 payment required), except /health, /stats, and /.well-known/x402.json, which are free. Every response is clean JSON — never a raw 500, always {error: "..."} with the right HTTP status code on any problem (validation, upstream source down, etc.).

Replace $URL with the server's URL (http://localhost:4021 locally, the Render URL in production) in the examples below.

Crypto prices & gas (dedicated routes, optimized for agent search)

EndpointPriceExample
GET /api/price/eth-usd$0.005curl "$URL/api/price/eth-usd"
GET /api/price/btc-usd$0.005curl "$URL/api/price/btc-usd"
GET /api/price/sol-usd$0.005curl "$URL/api/price/sol-usd"
GET /api/price/usdc-supply$0.005curl "$URL/api/price/usdc-supply"
GET /api/gas/base$0.005curl "$URL/api/gas/base"
GET /api/gas/ethereum$0.005curl "$URL/api/gas/ethereum"

These are thin, single-purpose wrappers around the same sources as /api/defi/price and /api/chain/gas below — kept as separate routes (with narrow, intent-matching descriptions) so an agent searching for e.g. "ETH price USD" or "gas price Base" finds and calls them directly, instead of having to first discover the generic parameterized endpoint.

Crypto / DeFi data (source DefiLlama, free and open)

⚠️ License note: DefiLlama's terms of service restrict their free API to personal, non-commercial use and prohibit commercial exploitation of the data without prior written agreement (defillama.com/terms, clauses 7 and 8.10). These endpoints (plus the 6 /api/price/* and /api/gas/* ones above, and the 4 /api/defi/yields/* sub-routes below, all of which reuse the same DefiLlama sources) are built on it anyway, on the explicit and informed decision of this service's operator (compliance risk accepted) — to be revisited if DefiLlama raises the issue, or by moving to their paid Pro API (pro-api.llama.fi) if needed.

EndpointPriceExample
GET /api/defi/price$0.005curl "$URL/api/defi/price?coins=ethereum,bitcoin"
GET /api/defi/tvl$0.005curl "$URL/api/defi/tvl?protocol=aave"
GET /api/defi/tvl-chain$0.005curl "$URL/api/defi/tvl-chain?chain=base"
GET /api/defi/protocols$0.005curl "$URL/api/defi/protocols?limit=20"
GET /api/defi/yields$0.005curl "$URL/api/defi/yields?chain=base&min_tvl=1000000"
GET /api/defi/yields/top$0.005curl "$URL/api/defi/yields/top?limit=10&min_tvl=10000000"
GET /api/defi/yields/by-token$0.005curl "$URL/api/defi/yields/by-token?symbol=USDC&limit=10"
GET /api/defi/yields/by-chain$0.005curl "$URL/api/defi/yields/by-chain?chain=base&limit=10"
GET /api/defi/yields/pool$0.005curl "$URL/api/defi/yields/pool?pool=<pool id>"
GET /api/defi/stablecoins$0.005curl "$URL/api/defi/stablecoins?limit=20"

/api/defi/yields/top, /by-token, /by-chain, and /pool are dedicated, intent-matching routes alongside the generic /api/defi/yields — for an agent searching "best yield for USDC" or "best yields on Base" rather than discovering the generic parameterized endpoint first (same rationale as the dedicated /api/price/* and /api/gas/* routes above). /pool returns one pool's detail plus its last 30 recorded APY/TVL data points, via DefiLlama's yields.llama.fi/chart/{pool} (verified live against the current DefiLlama docs before use — see endpoints/defi-yields-pool.js for a note on a doc/reality mismatch found in the process: the docs list /chart/{pool}'s base URL as api.llama.fi, but only yields.llama.fi actually serves it; api.llama.fi/chart/{pool} 404s).

On-chain data (public RPC reads via viem, no third-party API)

EndpointPriceExample
GET /api/chain/gas$0.005curl "$URL/api/chain/gas?chain=base" (or chain=ethereum)
GET /api/chain/block$0.005curl "$URL/api/chain/block?chain=base"

Web reading & extraction (fetch, readability, and — for extract — Claude Haiku 4.5)

EndpointPriceExample
POST /api/web/read$0.005curl -X POST "$URL/api/web/read" -H "Content-Type: application/json" -d '{"url":"https://en.wikipedia.org/wiki/HTTP_402"}'
POST /api/web/extract$0.02curl -X POST "$URL/api/web/extract" -H "Content-Type: application/json" -d '{"url":"...","schema":{"type":"object","properties":{"title":{"type":"string"}}}}'

POST /api/web/read downloads a page and returns its main content as clean Markdown (readability extraction — boilerplate/nav/ads stripped), so an agent never has to parse raw HTML. POST /api/web/extract does the same fetch, then extracts structured JSON from the page according to a caller-supplied JSON Schema, via Claude Haiku 4.5 — one call instead of read-then-extract. Both are guarded against SSRF (see lib/web.js): the target URL must be public http(s), private/loopback/link-local/reserved IP ranges are refused (checked both on the initial host and on every redirect hop), the download is capped at 2 MB within a 10 s budget, and the site's robots.txt is honored (fails open — i.e. allows the fetch — only when robots.txt itself is unreachable, the same convention real crawlers use).

Open public data

EndpointPriceSource / licenseExample
GET /api/fx/rates$0.005Frankfurter (MIT, open ECB data)curl "$URL/api/fx/rates?base=EUR"
GET /api/github/repo$0.005GitHub REST APIcurl "$URL/api/github/repo?full_name=expressjs/express"
GET /api/npm/package$0.005registry.npmjs.org + api.npmjs.orgcurl "$URL/api/npm/package?name=express"
GET /api/hn/top$0.005Hacker News Firebase API (MIT)curl "$URL/api/hn/top?limit=20"
GET /api/wiki/summary$0.005Wikimedia REST API (CC BY-SA 4.0, attribution included in the response)curl "$URL/api/wiki/summary?title=Bitcoin&lang=en"
GET /api/dns/lookup$0.005Direct DNS resolution (Node's dns module)curl "$URL/api/dns/lookup?domain=example.com"
GET /api/rdap/domain$0.005rdap.org (open protocol, WHOIS's successor)curl "$URL/api/rdap/domain?domain=example.com"

AI tasks (Claude Haiku 4.5, ANTHROPIC_API_KEY required)

EndpointPriceExample
POST /api/ai/summarize$0.01curl -X POST "$URL/api/ai/summarize" -H "Content-Type: application/json" -d '{"text":"...","max_sentences":3}'
POST /api/ai/classify$0.01curl -X POST "$URL/api/ai/classify" -H "Content-Type: application/json" -d '{"text":"...","labels":["positive","negative","neutral"]}'
POST /api/ai/translate$0.01curl -X POST "$URL/api/ai/translate" -H "Content-Type: application/json" -d '{"text":"...","target_lang":"French"}'
POST /api/ai/extract$0.02curl -X POST "$URL/api/ai/extract" -H "Content-Type: application/json" -d '{"text":"...","schema":{"type":"object","properties":{"total":{"type":"number"}}}}'

Premium reseller (Tavily, Serper — real third-party providers, real margin)

EndpointPriceExample
POST /api/search/web$0.01curl -X POST "$URL/api/search/web" -H "Content-Type: application/json" -d '{"query":"latest developments in the x402 protocol","num_results":5}'
POST /api/search/serp$0.005curl -X POST "$URL/api/search/serp" -H "Content-Type: application/json" -d '{"query":"best crypto payment protocols 2026","country":"us"}'

Unlike the rest of this server (free/public sources, or a flat-rate AI call), this family resells a paid upstream provider's API per call — so margin, compliance, and upstream outages are real, ongoing concerns, tracked deliberately rather than assumed away.

A third endpoint, POST /api/web/scrape (Tavily Extract), was built, shipped, then retired on 2026-09-03. It was removed after a real 6-page comparative test (3 JavaScript-rendered pages, a heavy documentation page, a product page, and an article behind a cookie-consent banner — all confirmed robots.txt-compliant before testing) against this server's own free /api/web/read: the in-house extractor matched or beat Tavily Extract on 5 of the 6 pages, usually because Tavily returned a full page dump (navigation and boilerplate mixed in) where Readability went straight to the actual content. Tavily's only reproducible advantage was bypassing a bot-detection block that refused this server's own honestly-identified crawler outright — real, but too narrow to justify a dedicated $0.02 endpoint. Full test data: docs/RAPPORT-P1-PREMIUM.md.

Compliance basis (verified before writing any code, not assumed). The brief named Exa, Serper, and Firecrawl as candidates. Both Exa and Firecrawl were rejected: their Terms of Service explicitly forbid reselling API output in a commercial product without prior written consent (Exa ToS §4.2(a)(e)(f): no distributing/publishing/offering-for-sale of anything obtained via the Services, no reselling, no building a competitive product; Firecrawl ToS: "Use the Services for any commercial purposes except as expressly authorized by Firecrawl" plus a separate "sell, distribute... based on the Services" prohibition). Two replacements were researched and picked instead:

  • Tavily (api.tavily.com) — replaces Exa for /api/search/web. Its ToS (tavily.com/terms) contains an explicit carve-out for exactly this architecture: §3.2 bans reselling/sublicensing the Services except "integration of the Services in Customer Applications", and a Customer Application is defined (§1.2) to include serving your own third-party end users — provided (§3.5, Acceptable Use Policy §4) those end users never receive the Tavily API key or call Tavily directly (they only ever talk to this server). That's exactly how endpoints/search-web.js is built.
  • Serper (serper.dev) — used for /api/search/serp. SerpApi was checked as an alternative and rejected (subscription-only, no true prepaid credits, and is currently the defendant in active litigation brought by Google over its scraping methods). Serper's own ToS is silent on resale — neither an explicit permission nor a prohibition. The one clause that matters bans mirroring "the materials on any other server as-is with no-value-added" — so endpoints/search-serp.js deliberately restructures Serper's raw JSON (renamed/trimmed fields, 3 separate response sections merged into one shape) rather than passing it through verbatim, to stay clearly on the value-added side of that clause. This is a documented risk decision, not a clean bill of health — revisit if Serper ever adds an explicit resale clause either way.

Margin, at the cheapest prepaid tier of each provider (real numbers, verified against each provider's own current pricing docs, cited — not estimates):

EndpointSale priceUpstream costMarginUpstream unit
POST /api/search/web$0.01$0.008$0.002 (20%)Tavily pay-as-you-go, $0.008/credit, 1 credit per basic search (docs.tavily.com/documentation/api-credits)
POST /api/search/serp$0.005$0.001$0.004 (80%)Serper Starter pack, $50/50,000 credits, 1 credit per query up to 10 results (serper.dev's own pricing page was returning a 404 when last checked — figure corroborated by third-party sources, not the primary source; our own account balance confirms $0.001/credit is consistent with real usage)

/api/search/web's margin is thinner than the "cost × ~2" target set out in the brief — Tavily's real floor ($0.008/credit) is higher than assumed, and $0.01 was kept as the sale price anyway (rather than raising to $0.02) to stay priced like the rest of this server's cheap data endpoints; the price is one constant to change in endpoints/search-web.js if thicker margin matters more than that. Every successful premium-reseller call appends its real upstream cost to logs/couts.jsonl (lib/couts-log.js — same DATA_DIR/gitignore discipline as paiements.jsonl/sondages.jsonl), so actual margin (sale price is already known and fixed; only the cost side needs tracking) can be checked against these estimates over time rather than assumed to hold forever.

⚠️ Operational gotcha found shipping the now-retired /api/web/scrape (2026-09-02): CDP's mainnet facilitator silently rejects payments for endpoints with a long description. Its first description (557 chars) failed real mainnet payment 5/5 times — the facilitator's /verify call returned "'paymentPayload' is invalid: must match one of [x402V2Pay...", which surfaces to the buyer as a bare, unhelpful 402 (looks identical to "insufficient funds" or "didn't pay at all" — nothing in the response says "description too long"). Reproduced locally by running this server with NETWORK=base against the real CDP facilitator (no deploy needed per iteration) and bisecting: every other endpoint's shorter description settled fine in the same session (the 334-char /api/search/web included), and trimming to 301 chars fixed it, confirmed with 3/3 real settled mainnet transactions. Root cause and exact limit not confirmed (CDP's schema isn't public) — the testnet facilitator did not reproduce this at 557 chars, so always verify a new/lengthened endpoint description with a real mainnet payment, not just testnet, before trusting it. This lesson outlives the endpoint that surfaced it — rule of thumb for any future endpoint: keep description well under ~350 chars.

Failure handling: lib/tavily.js and lib/serper.js collapse every upstream failure mode — missing API key, network error, any non-2xx response (including an exhausted credit balance) — to the same clean 503 {"error":"This endpoint is temporarily unavailable (...)."}, never a raw 500 and never a leaked provider error message. Both endpoints cache identical repeated requests for 60s (same convention as the rest of this server, see lib/cache.js) — a cache hit costs nothing upstream, so real margin on repeated queries is better than the table above.

All the requests above return a 402 Payment Required first — replay them with an x402 client (see scripts/buyer-test.js for a full example, or @x402/fetch on the agent side).

Stack

  • Node 20+, ESM, Express — no TypeScript.
  • x402 v2 packages (current ecosystem, scoped @x402/*):
    • @x402/express — Express middleware (paymentMiddleware, x402ResourceServer)
    • @x402/core — HTTP facilitator client (HTTPFacilitatorClient)
    • @x402/evmexact payment scheme on EVM (server and client)
    • @x402/fetch — buyer side: a fetch wrapper that auto-pays 402s
    • @x402/extensions — the Bazaar extension (discovery metadata for agents)
    • @coinbase/x402 — CDP facilitator config (mainnet)
    • viem — key generation / EVM signing, RPC reads (/api/chain/*, /api/gas/*)
    • express-rate-limit — per-IP rate limiting on /api/* routes
    • @anthropic-ai/sdk — Claude Haiku 4.5 for the /api/ai/* and /api/web/extract endpoints
    • jsdom + @mozilla/readability — safe HTML parsing and article extraction (the same engine behind Firefox Reader View) for /api/web/*
    • turndown — HTML-to-Markdown conversion for /api/web/*
    • robots-parser — robots.txt compliance for /api/web/*
    • ipaddr.js — private/reserved IP classification for the /api/web/* SSRF guard

The older x402-express / x402-fetch packages (v1, unscoped) are deprecated — don't mix them with @x402/*.

Structure

server.js                  # starts Express, loads endpoints/, mounts the x402 middleware
config.js                  # reads .env, validates it, maps base-sepolia/base -> CAIP-2
discovery.js                # builds the GET /.well-known/x402.json document
payment-log.js              # logs every successful payment to logs/paiements.jsonl
sondage-log.js              # logs every 402 response served ("probes") to logs/sondages.jsonl
echecs-log.js                # logs settlement/upstream failures to logs/echecs.jsonl (see "Observability")
lib/
  http.js                   # fetchJson/fetchText (10s timeout, User-Agent), safeHandler (never a raw 500, logs UpstreamError)
  cache.js                  # 60s in-memory cache for market/network data
  anthropic.js               # shared Claude Haiku 4.5 client for /api/ai/* and /api/web/extract
  chains.js                  # resolves ?chain=base|ethereum -> viem client, shared gas-price helper
  defi.js                    # shared DefiLlama helpers for /api/price/*
  web.js                      # SSRF-guarded page fetch + readability-to-Markdown extraction for /api/web/*
  stats.js                    # computes GET /stats from the two jsonl logs
  stats-daily.js               # computes GET /stats/daily (protected) — revenue, top-10 UA
  stats-probes.js               # computes GET /stats/probes (protected) — full UA/IP long tail, scanner/cible
  stats-echecs.js                # computes GET /stats/echecs (protected) — last 100 failures + counters
  tavily.js                   # shared Tavily client for /api/search/web (see "Premium reseller")
  serper.js                   # shared Serper.dev client for /api/search/serp (see "Premium reseller")
  couts-log.js                # logs our own upstream cost per premium-reseller call to logs/couts.jsonl
endpoints/                 # one file = one endpoint, auto-loaded
  health.js                 # GET /health (free)
  stats.js                   # GET /stats (free)
  defi-tvl.js                # GET /api/defi/tvl (paid, $0.005)
  defi-price.js               # GET /api/defi/price
  defi-tvl-chain.js           # GET /api/defi/tvl-chain
  defi-protocols.js           # GET /api/defi/protocols
  defi-yields.js               # GET /api/defi/yields
  defi-yields-top.js           # GET /api/defi/yields/top
  defi-yields-by-token.js      # GET /api/defi/yields/by-token
  defi-yields-by-chain.js      # GET /api/defi/yields/by-chain
  defi-yields-pool.js          # GET /api/defi/yields/pool
  defi-stablecoins.js          # GET /api/defi/stablecoins
  price-eth-usd.js              # GET /api/price/eth-usd
  price-btc-usd.js               # GET /api/price/btc-usd
  price-sol-usd.js                # GET /api/price/sol-usd
  price-usdc-supply.js             # GET /api/price/usdc-supply
  chain-gas.js               # GET /api/chain/gas
  chain-block.js              # GET /api/chain/block
  gas-base.js                  # GET /api/gas/base
  gas-ethereum.js                # GET /api/gas/ethereum
  web-read.js                     # POST /api/web/read
  web-extract.js                   # POST /api/web/extract
  fx-rates.js                 # GET /api/fx/rates
  github-repo.js               # GET /api/github/repo
  npm-package.js                # GET /api/npm/package
  hn-top.js                      # GET /api/hn/top
  wiki-summary.js                 # GET /api/wiki/summary
  dns-lookup.js                    # GET /api/dns/lookup
  rdap-domain.js                    # GET /api/rdap/domain
  ai-summarize.js                    # POST /api/ai/summarize
  ai-extract.js                       # POST /api/ai/extract
  ai-classify.js                       # POST /api/ai/classify
  ai-translate.js                       # POST /api/ai/translate
  search-web.js                          # POST /api/search/web (paid, $0.01 — premium reseller, Tavily)
  search-serp.js                          # POST /api/search/serp (paid, $0.005 — premium reseller, Serper)
scripts/
  generate-buyer-wallet.js # generates BUYER_PRIVATE_KEY (viem) + prints the address
  buyer-test.js            # buyer client: receives the 402, pays, prints the response (path/method/body configurable)
  check-bazaar.js          # npm run bazaar — queries the CDP facilitator's Bazaar discovery
  seed-bazaar.js           # npm run seed [-- --only=...] — pays real endpoints so the Bazaar indexes them
  seed-hebdo.js            # npm run seed-hebdo — weekly seed of a configurable subset (SEED_PATHS), balance guard + retry (see below)
  lib/seed-core.js         # shared dynamic-discovery + payment loop behind seed-bazaar.js and seed-hebdo.js
  importer-cle-cdp.js      # npm run cle — imports the CDP key into .env without ever printing it
render.yaml                 # Render deployment blueprint (Node web service)
logs/paiements.jsonl        # successful-payment log (gitignored, created on the first payment)
logs/sondages.jsonl         # 402-response log (gitignored, created on the first probe)
logs/seeds.jsonl            # weekly seed run summaries (gitignored, LOCAL only — see below)
logs/couts.jsonl            # our own upstream cost per premium-reseller call (gitignored, see "Premium reseller")
logs/echecs.jsonl           # settlement/upstream failure log (gitignored, created on the first failure)
.env / .env.example        # configuration (.env is never committed)

Adding an endpoint

Create endpoints/my-endpoint.js:

export const path = "/api/my-endpoint";
export const method = "GET";            // optional, defaults to GET
export const price = "$0.01";           // null => free
export const description = "What this endpoint does.";
export async function handler(req, res) {
  res.json({ hello: "world" });
}

It is loaded automatically at startup. An optional discovery export (via declareDiscoveryExtension from @x402/extensions/bazaar) describes the input parameters and an example output — see endpoints/defi-tvl.js. Write description and discovery in English, phrased around the search terms an agent would actually type (e.g. "ETH price USD", "summarize text") — that's what buyer agents match against in the Bazaar and in /.well-known/x402.json.

Configuration (.env)

VariableRole
NETWORKbase-sepolia (test, default) or base (production)
BASE_URLThis server's public URL, announced to agents (Bazaar, .well-known/x402.json). Never localhost in production. Empty locally → auto falls back to http://localhost:PORT
PAY_TO_ADDRESSEVM address that receives the USDC
CDP_API_KEY_ID / CDP_API_KEY_SECRETCDP keys — required only if NETWORK=base
BUYER_PRIVATE_KEYTest buyer wallet's private key — never set server-side in production (see render.yaml)
ANTHROPIC_API_KEYRequired for /api/ai/* and /api/web/extract (Claude Haiku 4.5) — without it, these endpoints return a clean 500 error explaining the missing key
GITHUB_TOKENOptional — raises the GitHub rate limit (60/h → 5000/h) for /api/github/repo. No scope required (public repo data)
TAVILY_API_KEYRequired for /api/search/web (see "Premium reseller") — without it, it returns a clean 503, never a 500
SERPER_API_KEYRequired for /api/search/serp (see "Premium reseller") — without it, returns a clean 503, never a 500
PORTServer port — provided automatically by Render in production, 4021 locally

Importing the CDP key (npm run cle)

To go to production without copy-pasting CDP_API_KEY_ID/CDP_API_KEY_SECRET into .env by hand:

npm run cle

Shortened here. Read the whole README on GitHub.

Signals

Last commit
Sep 2026
Weekly downloads
334

ahel review (caution)

  • S2medium
    demands high-sensitivity credentials

Automated review, not a security audit. Ruleset v1.

Advanced
Delivery
x402-seller-mcp MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
io-github-entreprisedaney33-rgb-x402-seller-mcp
Source
github.com/entreprisedaney33-rgb/x402-seller