x402sandbox by realandworks.comStoreSalesDocs

products.json

The catalog is data: one JSON file, one entry per product, validated at load — an invalid catalog is rejected as a whole and the previous one keeps serving.

{
  "products": [
    {
      "sku": "library",                 // stable key, unique
      "title": "Whole Library",
      "description": "optional — shown on the store and in discovery",
      "price": "$0.01",                 // fixed pricing … OR:
      // "pricing": { "mode": "demand", "floor": "$0.001", "ceiling": "$0.10",
      //              "step": 0.1, "windowMinutes": 15 },
      "route": "GET /library/*",        // "METHOD /path" — wildcard for folders
      "contentDir": "./content/library",// exactly ONE of contentDir | contentPath | bundlePath | proxyUrl
      "network": "eip155:84532",        // optional, inherits .env NETWORK
      "mimeType": "text/markdown",      // optional; folders infer per file
      "preview": true,                  // optional: excerpt md/txt on the store
      "discoverable": true              // optional: list in x402 registries (Bazaar)
    }
  ]
}

Rules the validator enforces

  • Exactly one of price / pricing, and exactly one of contentPath / bundlePath / contentDir / proxyUrl
  • contentDir products must use a wildcard route (GET /x/*); file products must not
  • network matches eip155:<chainId>; prices are "$0.05" strings or positive numbers; skus unique
  • demand pricing: floor < ceiling, step in (0,1), window ≥ 1 minute

Directory products

One folder = one price. Every file dropped in is instantly listed and purchasable; removing it delists it — no reload. The store page lists directory contents live (dotfiles and secret extensions are never listed or served).

Proxy products — paywall any endpoint

proxyUrl makes a live upstream service sellable — the founding idea taken literally:

{ "sku": "ask", "title": "Ask the corpus (per query)", "price": "$0.02",
  "route": "POST /ask", "proxyUrl": "http://127.0.0.1:8403/soil/search",
  "discoverable": true }

After payment, the gateway forwards the request — method, JSON body, query string, and (for wildcard routes) the subpath — and streams the upstream response back. An unreachable upstream answers 502. The upstream should bind localhost and skip its own payment handling: the gateway sells, it serves. This is how a retrieval "front desk" (e.g. the x402-packager) plugs in: the gateway owns payments, logging, redelivery and discovery; the upstream owns the brains.

Demand pricing

Each window, the repricer counts settled sales: none → price decays by step toward floor; some → it rises toward ceiling. The floor/ceiling bounds are the safety property (quote-spam can manipulate the signal; bounds cap the damage). For one window after a change both old and new price verify, so in-flight quotes never fail. Current price persists in the DB across restarts.

Where products get listed — and how to confirm it

Three independent listing surfaces:

  • Your own /catalog.json — always lists everything, instantly. Agents that know your URL need nothing else.
  • Bazaar (the facilitator's discovery index) — products with discoverable enabled carry a machine-readable discovery declaration; the index learns about them from their x402 traffic through the facilitator, so a new product typically appears after its first real payment challenges, not instantly. Set PUBLIC_ORIGIN in .env (e.g. https://store.example.com) so announced URLs are public, not localhost.
  • Chain explorers (x402scan and similar) — discover sellers from on-chain settlements; nothing to configure.

Confirming listings: the admin Discovery page queries the facilitator's Bazaar index live and shows listed / not-yet-listed / discovery-off per product. Programmatically, a bazaar-extended facilitator client does the same: withBazaar(client).extensions.bazaar.listResources() from @x402/extensions/bazaar.

payTo is never in the catalog

The receiving wallet and payment scheme come from .env — the catalog is safe to commit and share.