{
  "markdown": "# cryptomonnaie — pay-per-call API over x402\n\nAn Express server that sells paid API endpoints over the **x402** protocol\n(**USDC** payments on **Base**), built to be consumed by AI agents.\n\nA client (human or agent) calls a paid endpoint → the server replies\n`402 Payment Required` with the payment requirements → the client signs a\nUSDC payment and replays the request with the `PAYMENT` header → a\n**facilitator** verifies and settles the payment on-chain → the server\nserves the response. No blockchain key management server-side: it only\nholds the receiving address.\n\n## Available endpoints\n\nAll `/api/*` routes are paid (x402 payment required), except `/health`,\n`/stats`, and `/.well-known/x402.json`, which are free. Every response is\nclean JSON — never a raw 500, always `{error: \"...\"}` with the right HTTP\nstatus code on any problem (validation, upstream source down, etc.).\n\nReplace `$URL` with the server's URL (`http://localhost:4021` locally, the\nRender URL in production) in the examples below.\n\n### Crypto prices & gas (dedicated routes, optimized for agent search)\n\n| Endpoint | Price | Example |\n|---|---|---|\n| `GET /api/price/eth-usd` | $0.005 | `curl \"$URL/api/price/eth-usd\"` |\n| `GET /api/price/btc-usd` | $0.005 | `curl \"$URL/api/price/btc-usd\"` |\n| `GET /api/price/sol-usd` | $0.005 | `curl \"$URL/api/price/sol-usd\"` |\n| `GET /api/price/usdc-supply` | $0.005 | `curl \"$URL/api/price/usdc-supply\"` |\n| `GET /api/gas/base` | $0.005 | `curl \"$URL/api/gas/base\"` |\n| `GET /api/gas/ethereum` | $0.005 | `curl \"$URL/api/gas/ethereum\"` |\n\nThese are thin, single-purpose wrappers around the same sources as\n`/api/defi/price` and `/api/chain/gas` below — kept as separate routes (with\nnarrow, intent-matching descriptions) so an agent searching for e.g. \"ETH\nprice USD\" or \"gas price Base\" finds and calls them directly, instead of\nhaving to first discover the generic parameterized endpoint.\n\n### Crypto / DeFi data (source [DefiLlama](https://defillama.com), free and open)\n\n> ⚠️ **License note**: DefiLlama's terms of service restrict their free API\n> to personal, non-commercial use and prohibit commercial exploitation of\n> the data without prior written agreement (defillama.com/terms, clauses 7\n> and 8.10). These endpoints (plus the 6 `/api/price/*` and `/api/gas/*`\n> ones above, and the 4 `/api/defi/yields/*` sub-routes below, all of\n> which reuse the same DefiLlama sources) are built on it anyway, on the\n> explicit and informed decision of this service's operator (compliance\n> risk accepted) — to be revisited if DefiLlama raises the issue, or by\n> moving to their paid Pro API (pro-api.llama.fi) if needed.\n\n| Endpoint | Price | Example |\n|---|---|---|\n| `GET /api/defi/price` | $0.005 | `curl \"$URL/api/defi/price?coins=ethereum,bitcoin\"` |\n| `GET /api/defi/tvl` | $0.005 | `curl \"$URL/api/defi/tvl?protocol=aave\"` |\n| `GET /api/defi/tvl-chain` | $0.005 | `curl \"$URL/api/defi/tvl-chain?chain=base\"` |\n| `GET /api/defi/protocols` | $0.005 | `curl \"$URL/api/defi/protocols?limit=20\"` |\n| `GET /api/defi/yields` | $0.005 | `curl \"$URL/api/defi/yields?chain=base&min_tvl=1000000\"` |\n| `GET /api/defi/yields/top` | $0.005 | `curl \"$URL/api/defi/yields/top?limit=10&min_tvl=10000000\"` |\n| `GET /api/defi/yields/by-token` | $0.005 | `curl \"$URL/api/defi/yields/by-token?symbol=USDC&limit=10\"` |\n| `GET /api/defi/yields/by-chain` | $0.005 | `curl \"$URL/api/defi/yields/by-chain?chain=base&limit=10\"` |\n| `GET /api/defi/yields/pool` | $0.005 | `curl \"$URL/api/defi/yields/pool?pool=<pool id>\"` |\n| `GET /api/defi/stablecoins` | $0.005 | `curl \"$URL/api/defi/stablecoins?limit=20\"` |\n\n`/api/defi/yields/top`, `/by-token`, `/by-chain`, and `/pool` are dedicated, intent-matching\nroutes alongside the generic `/api/defi/yields` — for an agent searching \"best yield for\nUSDC\" or \"best yields on Base\" rather than discovering the generic parameterized endpoint\nfirst (same rationale as the dedicated `/api/price/*` and `/api/gas/*` routes above).\n`/pool` returns one pool's detail plus its last 30 recorded APY/TVL data points, via\nDefiLlama's `yields.llama.fi/chart/{pool}` (verified live against the current DefiLlama\ndocs before use — see `endpoints/defi-yields-pool.js` for a note on a doc/reality\nmismatch found in the process: the docs list `/chart/{pool}`'s base URL as `api.llama.fi`,\nbut only `yields.llama.fi` actually serves it; `api.llama.fi/chart/{pool}` 404s).\n\n### On-chain data (public RPC reads via `viem`, no third-party API)\n\n| Endpoint | Price | Example |\n|---|---|---|\n| `GET /api/chain/gas` | $0.005 | `curl \"$URL/api/chain/gas?chain=base\"` (or `chain=ethereum`) |\n| `GET /api/chain/block` | $0.005 | `curl \"$URL/api/chain/block?chain=base\"` |\n\n### Web reading & extraction (fetch, readability, and — for extract — Claude Haiku 4.5)\n\n| Endpoint | Price | Example |\n|---|---|---|\n| `POST /api/web/read` | $0.005 | `curl -X POST \"$URL/api/web/read\" -H \"Content-Type: application/json\" -d '{\"url\":\"https://en.wikipedia.org/wiki/HTTP_402\"}'` |\n| `POST /api/web/extract` | $0.02 | `curl -X POST \"$URL/api/web/extract\" -H \"Content-Type: application/json\" -d '{\"url\":\"...\",\"schema\":{\"type\":\"object\",\"properties\":{\"title\":{\"type\":\"string\"}}}}'` |\n\n`POST /api/web/read` downloads a page and returns its main content as clean\nMarkdown (readability extraction — boilerplate/nav/ads stripped), so an\nagent never has to parse raw HTML. `POST /api/web/extract` does the same\nfetch, then extracts structured JSON from the page according to a\ncaller-supplied JSON Schema, via Claude Haiku 4.5 — one call instead of\nread-then-extract. Both are guarded against SSRF (see `lib/web.js`): the\ntarget URL must be public http(s), private/loopback/link-local/reserved IP\nranges are refused (checked both on the initial host and on every redirect\nhop), the download is capped at 2 MB within a 10 s budget, and the site's\n`robots.txt` is honored (fails open — i.e. allows the fetch — only when\n`robots.txt` itself is unreachable, the same convention real crawlers use).\n\n### Open public data\n\n| Endpoint | Price | Source / license | Example |\n|---|---|---|---|\n| `GET /api/fx/rates` | $0.005 | Frankfurter (MIT, open ECB data) | `curl \"$URL/api/fx/rates?base=EUR\"` |\n| `GET /api/github/repo` | $0.005 | GitHub REST API | `curl \"$URL/api/github/repo?full_name=expressjs/express\"` |\n| `GET /api/npm/package` | $0.005 | registry.npmjs.org + api.npmjs.org | `curl \"$URL/api/npm/package?name=express\"` |\n| `GET /api/hn/top` | $0.005 | Hacker News Firebase API (MIT) | `curl \"$URL/api/hn/top?limit=20\"` |\n| `GET /api/wiki/summary` | $0.005 | Wikimedia REST API (CC BY-SA 4.0, attribution included in the response) | `curl \"$URL/api/wiki/summary?title=Bitcoin&lang=en\"` |\n| `GET /api/dns/lookup` | $0.005 | Direct DNS resolution (Node's `dns` module) | `curl \"$URL/api/dns/lookup?domain=example.com\"` |\n| `GET /api/rdap/domain` | $0.005 | rdap.org (open protocol, WHOIS's successor) | `curl \"$URL/api/rdap/domain?domain=example.com\"` |\n\n### AI tasks (Claude Haiku 4.5, ANTHROPIC_API_KEY required)\n\n| Endpoint | Price | Example |\n|---|---|---|\n| `POST /api/ai/summarize` | $0.01 | `curl -X POST \"$URL/api/ai/summarize\" -H \"Content-Type: application/json\" -d '{\"text\":\"...\",\"max_sentences\":3}'` |\n| `POST /api/ai/classify` | $0.01 | `curl -X POST \"$URL/api/ai/classify\" -H \"Content-Type: application/json\" -d '{\"text\":\"...\",\"labels\":[\"positive\",\"negative\",\"neutral\"]}'` |\n| `POST /api/ai/translate` | $0.01 | `curl -X POST \"$URL/api/ai/translate\" -H \"Content-Type: application/json\" -d '{\"text\":\"...\",\"target_lang\":\"French\"}'` |\n| `POST /api/ai/extract` | $0.02 | `curl -X POST \"$URL/api/ai/extract\" -H \"Content-Type: application/json\" -d '{\"text\":\"...\",\"schema\":{\"type\":\"object\",\"properties\":{\"total\":{\"type\":\"number\"}}}}'` |\n\n### Premium reseller (Tavily, Serper — real third-party providers, real margin)\n\n| Endpoint | Price | Example |\n|---|---|---|\n| `POST /api/search/web` | $0.01 | `curl -X POST \"$URL/api/search/web\" -H \"Content-Type: application/json\" -d '{\"query\":\"latest developments in the x402 protocol\",\"num_results\":5}'` |\n| `POST /api/search/serp` | $0.005 | `curl -X POST \"$URL/api/search/serp\" -H \"Content-Type: application/json\" -d '{\"query\":\"best crypto payment protocols 2026\",\"country\":\"us\"}'` |\n\nUnlike the rest of this server (free/public sources, or a flat-rate AI call), this\nfamily resells a paid upstream provider's API per call — so margin, compliance, and\nupstream outages are real, ongoing concerns, tracked deliberately rather than assumed\naway.\n\n**A third endpoint, `POST /api/web/scrape` (Tavily Extract), was built, shipped, then\nretired on 2026-09-03.** It was removed after a real 6-page comparative test (3\nJavaScript-rendered pages, a heavy documentation page, a product page, and an article\nbehind a cookie-consent banner — all confirmed `robots.txt`-compliant before testing)\nagainst this server's own free `/api/web/read`: the in-house extractor matched or beat\nTavily Extract on 5 of the 6 pages, usually because Tavily returned a full page dump\n(navigation and boilerplate mixed in) where Readability went straight to the actual\ncontent. Tavily's only reproducible advantage was bypassing a bot-detection block that\nrefused this server's own honestly-identified crawler outright — real, but too narrow to\njustify a dedicated $0.02 endpoint. Full test data: `docs/RAPPORT-P1-PREMIUM.md`.\n\n**Compliance basis (verified before writing any code, not assumed).** The brief named\nExa, Serper, and Firecrawl as candidates. Both Exa and Firecrawl were **rejected**: their\nTerms of Service explicitly forbid reselling API output in a commercial product without\nprior written consent (Exa ToS §4.2(a)(e)(f): no distributing/publishing/offering-for-sale\nof anything obtained via the Services, no reselling, no building a competitive product;\nFirecrawl ToS: \"Use the Services for any commercial purposes except as expressly\nauthorized by Firecrawl\" plus a separate \"sell, distribute... based on the Services\"\nprohibition). Two replacements were researched and picked instead:\n\n- **Tavily** (`api.tavily.com`) — replaces Exa for `/api/search/web`. Its ToS\n  (tavily.com/terms) contains an explicit carve-out for exactly this architecture: §3.2\n  bans reselling/sublicensing the Services *except* \"integration of the Services in\n  Customer Applications\", and a Customer Application is defined (§1.2) to include serving\n  your own third-party end users — provided (§3.5, Acceptable Use Policy §4) those end\n  users never receive the Tavily API key or call Tavily directly (they only ever talk to\n  this server). That's exactly how `endpoints/search-web.js` is built.\n- **Serper** (`serper.dev`) — used for `/api/search/serp`. SerpApi was checked as an\n  alternative and rejected (subscription-only, no true prepaid credits, and is currently\n  the defendant in active litigation brought by Google over its scraping methods).\n  Serper's own ToS is **silent** on resale — neither an explicit permission nor a\n  prohibition. The one clause that matters bans mirroring \"the materials on any other\n  server as-is with no-value-added\" — so `endpoints/search-serp.js` deliberately\n  restructures Serper's raw JSON (renamed/trimmed fields, 3 separate response sections\n  merged into one shape) rather than passing it through verbatim, to stay clearly on the\n  value-added side of that clause. This is a documented risk decision, not a clean bill of\n  health — revisit if Serper ever adds an explicit resale clause either way.\n\n**Margin, at the cheapest prepaid tier of each provider (real numbers, verified against\neach provider's own current pricing docs, cited — not estimates):**\n\n| Endpoint | Sale price | Upstream cost | Margin | Upstream unit |\n|---|---|---|---|---|\n| `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](https://docs.tavily.com/documentation/api-credits)) |\n| `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) |\n\n`/api/search/web`'s margin is thinner than the \"cost × ~2\" target set out in the brief —\nTavily's real floor ($0.008/credit) is higher than assumed, and $0.01 was kept as the sale\nprice anyway (rather than raising to $0.02) to stay priced like the rest of this server's\ncheap data endpoints; the price is one constant to change in `endpoints/search-web.js` if\nthicker margin matters more than that. Every successful premium-reseller call appends its\nreal upstream cost to `logs/couts.jsonl` (`lib/couts-log.js` — same `DATA_DIR`/gitignore\ndiscipline as `paiements.jsonl`/`sondages.jsonl`), so actual margin (sale price is already\nknown and fixed; only the cost side needs tracking) can be checked against these estimates\nover time rather than assumed to hold forever.\n\n**⚠️ Operational gotcha found shipping the now-retired `/api/web/scrape` (2026-09-02):\nCDP's mainnet facilitator silently rejects payments for endpoints with a long\n`description`.** Its first description (557 chars) failed real mainnet payment 5/5 times —\nthe facilitator's `/verify` call returned `\"'paymentPayload' is invalid: must match one of\n[x402V2Pay...\"`, which surfaces to the buyer as a bare, unhelpful `402` (looks identical to\n\"insufficient funds\" or \"didn't pay at all\" — nothing in the response says \"description too\nlong\"). Reproduced locally by running this server with `NETWORK=base` against the real CDP\nfacilitator (no deploy needed per iteration) and bisecting: every other endpoint's shorter\ndescription settled fine in the same session (the 334-char `/api/search/web` included), and\ntrimming to 301 chars fixed it, confirmed with 3/3 real settled mainnet transactions. Root\ncause and exact limit not confirmed (CDP's schema isn't public) — the testnet facilitator\ndid **not** reproduce this at 557 chars, so **always verify a new/lengthened endpoint\ndescription with a real mainnet payment**, not just testnet, before trusting it. This\nlesson outlives the endpoint that surfaced it — rule of thumb for any future endpoint:\nkeep `description` well under ~350 chars.\n\n**Failure handling**: `lib/tavily.js` and `lib/serper.js` collapse every upstream failure\nmode — missing API key, network error, any non-2xx response (including an exhausted\ncredit balance) — to the same clean `503 {\"error\":\"This endpoint is temporarily\nunavailable (...).\"}`, never a raw `500` and never a leaked provider error message. Both\nendpoints cache identical repeated requests for 60s (same convention as the rest of this\nserver, see `lib/cache.js`) — a cache hit costs nothing upstream, so real margin on\nrepeated queries is better than the table above.\n\nAll the requests above return a `402 Payment Required` first — replay them\nwith an x402 client (see `scripts/buyer-test.js` for a full example, or\n`@x402/fetch` on the agent side).\n\n## Stack\n\n- Node 20+, ESM, Express — no TypeScript.\n- x402 v2 packages (current ecosystem, scoped `@x402/*`):\n  - `@x402/express` — Express middleware (`paymentMiddleware`, `x402ResourceServer`)\n  - `@x402/core` — HTTP facilitator client (`HTTPFacilitatorClient`)\n  - `@x402/evm` — `exact` payment scheme on EVM (server and client)\n  - `@x402/fetch` — buyer side: a `fetch` wrapper that auto-pays 402s\n  - `@x402/extensions` — the **Bazaar** extension (discovery metadata for agents)\n  - `@coinbase/x402` — CDP facilitator config (mainnet)\n  - `viem` — key generation / EVM signing, RPC reads (`/api/chain/*`, `/api/gas/*`)\n  - `express-rate-limit` — per-IP rate limiting on `/api/*` routes\n  - `@anthropic-ai/sdk` — Claude Haiku 4.5 for the `/api/ai/*` and `/api/web/extract` endpoints\n  - `jsdom` + `@mozilla/readability` — safe HTML parsing and article extraction (the same engine behind Firefox Reader View) for `/api/web/*`\n  - `turndown` — HTML-to-Markdown conversion for `/api/web/*`\n  - `robots-parser` — robots.txt compliance for `/api/web/*`\n  - `ipaddr.js` — private/reserved IP classification for the `/api/web/*` SSRF guard\n\n> The older `x402-express` / `x402-fetch` packages (v1, unscoped) are\n> deprecated — don't mix them with `@x402/*`.\n\n## Structure\n\n```\nserver.js                  # starts Express, loads endpoints/, mounts the x402 middleware\nconfig.js                  # reads .env, validates it, maps base-sepolia/base -> CAIP-2\ndiscovery.js                # builds the GET /.well-known/x402.json document\npayment-log.js              # logs every successful payment to logs/paiements.jsonl\nsondage-log.js              # logs every 402 response served (\"probes\") to logs/sondages.jsonl\nechecs-log.js                # logs settlement/upstream failures to logs/echecs.jsonl (see \"Observability\")\nlib/\n  http.js                   # fetchJson/fetchText (10s timeout, User-Agent), safeHandler (never a raw 500, logs UpstreamError)\n  cache.js                  # 60s in-memory cache for market/network data\n  anthropic.js               # shared Claude Haiku 4.5 client for /api/ai/* and /api/web/extract\n  chains.js                  # resolves ?chain=base|ethereum -> viem client, shared gas-price helper\n  defi.js                    # shared DefiLlama helpers for /api/price/*\n  web.js                      # SSRF-guarded page fetch + readability-to-Markdown extraction for /api/web/*\n  stats.js                    # computes GET /stats from the two jsonl logs\n  stats-daily.js               # computes GET /stats/daily (protected) — revenue, top-10 UA\n  stats-probes.js               # computes GET /stats/probes (protected) — full UA/IP long tail, scanner/cible\n  stats-echecs.js                # computes GET /stats/echecs (protected) — last 100 failures + counters\n  tavily.js                   # shared Tavily client for /api/search/web (see \"Premium reseller\")\n  serper.js                   # shared Serper.dev client for /api/search/serp (see \"Premium reseller\")\n  couts-log.js                # logs our own upstream cost per premium-reseller call to logs/couts.jsonl\nendpoints/                 # one file = one endpoint, auto-loaded\n  health.js                 # GET /health (free)\n  stats.js                   # GET /stats (free)\n  defi-tvl.js                # GET /api/defi/tvl (paid, $0.005)\n  defi-price.js               # GET /api/defi/price\n  defi-tvl-chain.js           # GET /api/defi/tvl-chain\n  defi-protocols.js           # GET /api/defi/protocols\n  defi-yields.js               # GET /api/defi/yields\n  defi-yields-top.js           # GET /api/defi/yields/top\n  defi-yields-by-token.js      # GET /api/defi/yields/by-token\n  defi-yields-by-chain.js      # GET /api/defi/yields/by-chain\n  defi-yields-pool.js          # GET /api/defi/yields/pool\n  defi-stablecoins.js          # GET /api/defi/stablecoins\n  price-eth-usd.js              # GET /api/price/eth-usd\n  price-btc-usd.js               # GET /api/price/btc-usd\n  price-sol-usd.js                # GET /api/price/sol-usd\n  price-usdc-supply.js             # GET /api/price/usdc-supply\n  chain-gas.js               # GET /api/chain/gas\n  chain-block.js              # GET /api/chain/block\n  gas-base.js                  # GET /api/gas/base\n  gas-ethereum.js                # GET /api/gas/ethereum\n  web-read.js                     # POST /api/web/read\n  web-extract.js                   # POST /api/web/extract\n  fx-rates.js                 # GET /api/fx/rates\n  github-repo.js               # GET /api/github/repo\n  npm-package.js                # GET /api/npm/package\n  hn-top.js                      # GET /api/hn/top\n  wiki-summary.js                 # GET /api/wiki/summary\n  dns-lookup.js                    # GET /api/dns/lookup\n  rdap-domain.js                    # GET /api/rdap/domain\n  ai-summarize.js                    # POST /api/ai/summarize\n  ai-extract.js                       # POST /api/ai/extract\n  ai-classify.js                       # POST /api/ai/classify\n  ai-translate.js                       # POST /api/ai/translate\n  search-web.js                          # POST /api/search/web (paid, $0.01 — premium reseller, Tavily)\n  search-serp.js                          # POST /api/search/serp (paid, $0.005 — premium reseller, Serper)\nscripts/\n  generate-buyer-wallet.js # generates BUYER_PRIVATE_KEY (viem) + prints the address\n  buyer-test.js            # buyer client: receives the 402, pays, prints the response (path/method/body configurable)\n  check-bazaar.js          # npm run bazaar — queries the CDP facilitator's Bazaar discovery\n  seed-bazaar.js           # npm run seed [-- --only=...] — pays real endpoints so the Bazaar indexes them\n  seed-hebdo.js            # npm run seed-hebdo — weekly seed of a configurable subset (SEED_PATHS), balance guard + retry (see below)\n  lib/seed-core.js         # shared dynamic-discovery + payment loop behind seed-bazaar.js and seed-hebdo.js\n  importer-cle-cdp.js      # npm run cle — imports the CDP key into .env without ever printing it\nrender.yaml                 # Render deployment blueprint (Node web service)\nlogs/paiements.jsonl        # successful-payment log (gitignored, created on the first payment)\nlogs/sondages.jsonl         # 402-response log (gitignored, created on the first probe)\nlogs/seeds.jsonl            # weekly seed run summaries (gitignored, LOCAL only — see below)\nlogs/couts.jsonl            # our own upstream cost per premium-reseller call (gitignored, see \"Premium reseller\")\nlogs/echecs.jsonl           # settlement/upstream failure log (gitignored, created on the first failure)\n.env / .env.example        # configuration (.env is never committed)\n```\n\n### Adding an endpoint\n\nCreate `endpoints/my-endpoint.js`:\n\n```js\nexport const path = \"/api/my-endpoint\";\nexport const method = \"GET\";            // optional, defaults to GET\nexport const price = \"$0.01\";           // null => free\nexport const description = \"What this endpoint does.\";\nexport async function handler(req, res) {\n  res.json({ hello: \"world\" });\n}\n```\n\nIt is loaded automatically at startup. An optional `discovery` export (via\n`declareDiscoveryExtension` from `@x402/extensions/bazaar`) describes the\ninput parameters and an example output — see `endpoints/defi-tvl.js`.\nWrite `description` and `discovery` in English, phrased around the search\nterms an agent would actually type (e.g. \"ETH price USD\", \"summarize\ntext\") — that's what buyer agents match against in the Bazaar and in\n`/.well-known/x402.json`.\n\n## Configuration (.env)\n\n| Variable | Role |\n|---|---|\n| `NETWORK` | `base-sepolia` (test, default) or `base` (production) |\n| `BASE_URL` | This 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` |\n| `PAY_TO_ADDRESS` | EVM address that receives the USDC |\n| `CDP_API_KEY_ID` / `CDP_API_KEY_SECRET` | CDP keys — required **only** if `NETWORK=base` |\n| `BUYER_PRIVATE_KEY` | Test buyer wallet's private key — **never** set server-side in production (see `render.yaml`) |\n| `ANTHROPIC_API_KEY` | Required for `/api/ai/*` and `/api/web/extract` (Claude Haiku 4.5) — without it, these endpoints return a clean 500 error explaining the missing key |\n| `GITHUB_TOKEN` | Optional — raises the GitHub rate limit (60/h → 5000/h) for `/api/github/repo`. No scope required (public repo data) |\n| `TAVILY_API_KEY` | Required for `/api/search/web` (see \"Premium reseller\") — without it, it returns a clean `503`, never a `500` |\n| `SERPER_API_KEY` | Required for `/api/search/serp` (see \"Premium reseller\") — without it, returns a clean `503`, never a `500` |\n| `PORT` | Server port — provided automatically by Render in production, 4021 locally |\n\n### Importing the CDP key (`npm run cle`)\n\nTo go to production without copy-pasting `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET`\ninto `.env` by hand:\n\n```bash\nnpm run cle\n```\n\n1. **1st run**: creates `CLE_API_CDP.txt` at the repo root (a template with 2\n   fields to fill in) and opens it in TextEdit. Paste the Key ID (one line)\n   and the Secret (can be a multi-line PEM block), save.\n2. **2nd run** (`npm run cle` again): reads the file, writes\n   `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET` into `.env` (the multi-line secret\n   is stored quoted with literal `\\n`s — `dotenv` converts them back to real\n   newlines on load), switches `NETWORK=base`, deletes `CLE_API_CDP.txt`,\n   and adds it to `.gitignore`. The secret is **never printed**, only its\n   size (number of lines) is confirmed.\n\nFacilitators:\n\n- **base-sepolia** → public test facilitator `https://x402.org/facilitator`, no key.\n- **base** → the **CDP** facilitator (Coinbase Developer Platform), authenticated with\n  `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET` (create keys at https://portal.cdp.coinbase.com).\n\n## Quickstart (testnet)\n\n```bash\nnpm install\nnpm start                        # starts the server on port 4021\n\n# In another terminal:\nnpm run generate-buyer-wallet    # generates BUYER_PRIVATE_KEY + prints the address\n# Fund the address with test USDC: https://faucet.circle.com (Base Sepolia)\nnpm run buyer-test               # pays $0.005 on /api/defi/tvl and prints the response + tx hash\n```\n\nTest another endpoint (path, method, and body configurable):\n\n```bash\nENDPOINT_PATH=\"/api/defi/price?coins=bitcoin\" npm run buyer-test\nENDPOINT_PATH=\"/api/ai/summarize\" METHOD=POST \\\n  BODY='{\"text\":\"Long article...\",\"max_sentences\":1}' npm run buyer-test\nENDPOINT_PATH=\"/api/web/read\" METHOD=POST \\\n  BODY='{\"url\":\"https://en.wikipedia.org/wiki/HTTP_402\"}' npm run buyer-test\n```\n\nCheck manually:\n\n```bash\ncurl http://localhost:4021/health                        # {\"ok\":true}\ncurl http://localhost:4021/stats                          # usage stats, free\ncurl -i \"http://localhost:4021/api/defi/tvl?protocol=aave\"   # 402 Payment Required\n```\n\n## Discovery for agents (Bazaar + `.well-known/x402.json`)\n\nThe **Bazaar** is the official x402 discovery index (docs.x402.org): it\nlives on the **facilitator** side (`GET {facilitator}/discovery/resources`),\nfed by each route's metadata via `@x402/extensions/bazaar`. This server's\nroutes declare that metadata (input schema + example output); on mainnet,\nbehind the CDP facilitator, they can be indexed and discovered by third-party\nagents through that endpoint (no key required to read it).\n\nIn addition, **`GET /.well-known/x402.json`** lists, server-side, every paid\nendpoint directly (absolute URL via `BASE_URL`, method, description, price,\nnetwork, `payTo`, input/output schema). There is no single official schema\nfor this file: this document follows the envelope from the IETF draft\n*\"Discovering x402 Payment Capability via DNS and a Well-Known URI\"*\n(`x402Version`, `kind: \"resource-server\"`, `resources[]`, `docs`, `updated`)\nand enriches each resource with the same `accepts`/`extensions.bazaar`\nfields already used in this server's real `402` responses — see\n`discovery.js` for the detail and its sources.\n\n```bash\ncurl https://x402-seller.onrender.com/.well-known/x402.json\n```\n\nTo check what the CDP facilitator has indexed from this server (mainnet\nonly):\n\n```bash\nnpm run bazaar\n```\n\n### Weekly automated seed (staying indexed in the Bazaar)\n\nThe CDP facilitator de-lists a resource from the Bazaar **per endpoint**\nafter 30 days without a settled payment on that specific URL (verified\ndirectly against docs.cdp.coinbase.com/x402/seller/get-discovered, not\nassumed — a separate, payment-independent health-probe mechanism also\ndown-ranks/removes an endpoint that fails consecutive availability checks).\nReal agent traffic alone can't be relied on to keep every endpoint fresh.\n\nA dedicated **Render Cron Job** (`x402-seed-hebdo`, created via the Render\nAPI, not in `render.yaml` — a separate resource on purpose, so it can never\ntouch the web service's deploys) runs `node scripts/seed-hebdo.js` every\nMonday against a **configurable subset** (`SEED_PATHS`, at the top of the\nfile — the only place to edit it), one retry per endpoint on failure, 3s\npause between calls.\n\n**Reseeding every endpoint stopped making sense once the catalog grew and\none price changed** (2026-09-03): at $0.440/run (33 endpoints, ~$1.90/month)\nagainst ~$0.005 of real third-party revenue since launch, the cost was\ndisproportionate — and, per the 30-day-per-endpoint rule above, a *weekly*\nrun was already 4x more frequent than the minimum needed anyway, so the\nreal lever is the endpoint *list*, not the frequency. `SEED_PATHS` now\nseeds only the 5 `/api/defi/yields*` routes ($0.05 each, since a pricing\ntest) plus `gas/base` and `defi/price` ($0.005 each) — **$0.26/run,\n~$1.13/month**. The endpoints left out of `SEED_PATHS` will drop out of the\nBazaar catalog after 30 days without a real payment — an accepted risk (see\n`docs/` for the dated note); they stay fully served and still listed in this\nserver's own discovery documents (`.well-known/x402.json`, `openapi.json`)\nregardless, only the CDP facilitator's own catalog is affected. Before\nspending anything the script reads the buyer wallet's real USDC balance on\nBase mainnet and refuses to run if it's under $0.30 (~1.15x one run's cost,\nsame margin ratio as the old $0.50/$0.440 guard, rescaled) — recharge the\nwallet and it resumes on its own next week, no code change needed.\n\n`scripts/lib/seed-core.js` holds the shared discovery+payment logic used by\nboth this script and `scripts/seed-bazaar.js` (the on-demand/`--only`\nvariant) — one `EXAMPLES` table, never two lists that can drift apart.\n`seed-hebdo.js` deliberately does **not** import `config.js`: it only ever\nneeds `BUYER_PRIVATE_KEY` (read from `.env` locally via `dotenv`, or from a\nreal Render env var on the cron job) and `TARGET_URL` — none of the\nseller-side fields (`PAY_TO_ADDRESS`, CDP keys), which stay out of the cron\njob's environment entirely.\n\nEach run appends one JSON summary line to `logs/seeds.jsonl` (gitignored,\n**local disk only** — Render cron jobs have no persistent disk, so that\nwrite silently no-ops there; the real record of a Render run is its own\nlogs in the Render dashboard). Run it yourself anytime:\n\n```bash\nnpm run seed-hebdo\n```\n\n## Rate limiting and logging\n\n- **Rate limit**: 60 requests/minute per IP on all `/api/*` routes\n  (`express-rate-limit`). Beyond that, a `429` response with a clear\n  message. `.well-known`, `/health`, and `/stats` are not rate-limited.\n- **Payment log**: every successfully settled payment writes a JSON line to\n  `logs/paiements.jsonl` (`date`, `endpoint`, `payer`, `montant`, `hash` —\n  only data that's already public on-chain, never a secret or signed\n  payment payload). Directory gitignored, created on the first payment.\n- **Probe log**: every `402 Payment Required` response actually served\n  writes a JSON line to `logs/sondages.jsonl` (`date`, `endpoint`, a\n  **truncated** IP — last octet/group zeroed, never the exact client\n  address — and `user_agent`). Same append-only jsonl discipline as the\n  payment log; see `sondage-log.js`.\n- **`GET /stats`** (free): aggregates both logs into 402-probe and\n  successful-payment counts per endpoint, over the last 24h and 7d.\n  Contains no sensitive data (no IPs, payer addresses, or transaction\n  hashes) — see `lib/stats.js`.\n- **Failure log**: two cases that used to be silently swallowed now write a\n  JSON line each to `logs/echecs.jsonl` (`type: \"settlement_failed\"` from\n  `server.js`'s `onAfterSettle` when a verified payment's settle call\n  itself fails — motif/payer/User-Agent, never a key or full signature;\n  `type: \"upstream_error\"` from `lib/http.js`'s `safeHandler` whenever a\n  paid endpoint's handler throws an `UpstreamError` — endpoint, mapped\n  provider, the real upstream HTTP status when one was received, a short\n  message). Since x402 only settles after a successful handler response, an\n  `upstream_error` never means a buyer was charged for a failed request —\n  see `echecs-log.js`.\n- **`GET /stats/probes?key=<STATS_KEY>`** (protected, same key as\n  `/stats/daily` below) : the full, untruncated long tail of who's probing\n  — by User-Agent and by truncated IP, 24h/7d, each User-Agent tagged\n  `profil: \"scanner\"` (≥10 distinct endpoints touched) or `\"cible\"` (fewer\n  — a genuine low-volume prospect wouldn't make `/stats/daily`'s top-10\n  cut) — see `lib/stats-probes.js`.\n- **`GET /stats/echecs?key=<STATS_KEY>`** (protected): the last 100 lines\n  of `logs/echecs.jsonl` plus 24h/7d counters by `type` — see\n  `lib/stats-echecs.js`.\n\n## Deploying to Render\n\nThe provided `render.yaml` describes a Node web service (free plan):\n\n1. On https://dashboard.render.com → **New** → **Blueprint** → connect this\n   GitHub repo. Render reads `render.yaml` automatically.\n2. Fill in the requested environment variables (`sync: false` in the\n   blueprint = entered by hand, never committed): `NETWORK`,\n   `PAY_TO_ADDRESS`, `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET`, `BASE_URL`,\n   `ANTHROPIC_API_KEY`.\n3. `BASE_URL` must be the service's Render URL (e.g.\n   `https://x402-seller.onrender.com`) — **never** localhost.\n4. `BUYER_PRIVATE_KEY` is **never** set server-side: it's a test buyer key,\n   unrelated to the service that sells endpoints.\n5. Render provides `PORT` automatically; the server already listens on\n   `process.env.PORT` and `0.0.0.0` (`server.js`), and\n   `healthCheckPath: /health` is already configured in `render.yaml`.\n\n## Going to production (Base mainnet)\n\n1. Create a secret API key at https://portal.cdp.coinbase.com and fill in\n   `CDP_API_KEY_ID` / `CDP_API_KEY_SECRET` in `.env` (or via `npm run cle`).\n2. Set `NETWORK=base` in `.env`, `BASE_URL` to the real public domain, then\n   restart.\n3. Payments arrive as real USDC at `PAY_TO_ADDRESS`.\n\n## Reference docs\n\n- Protocol and quickstarts: https://x402.gitbook.io/x402\n- CDP facilitator and Bazaar: https://docs.cdp.coinbase.com/x402\n- Bazaar (discovery layer): https://docs.x402.org/extensions/bazaar\n- IETF `.well-known` draft: https://datatracker.ietf.org/doc/html/draft-hawkins-x402-dns-discovery-01\n- Render blueprint: https://render.com/docs/blueprint-spec\n",
  "bytes": 34125,
  "sha": "8425ee5144c0c7f2535f8d2e77f8067780764aa0d7f0d932ad1cba48150a13e3",
  "repo_slug": "entreprisedaney33-rgb/x402-seller",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_entreprisedaney33_rgb_x402_sel_0c251224/readme"
}