Copy-paste recipes for two audiences: builders who want to monetize an API, and
buyers — humans or agents — who want to pay for one. No sandbox, no mocked
responses. Every example here works against paywall.wickedapi.com right now.
/for-builders
Five recipes, start to finish: get a key, paywall a real endpoint, edit pricing live, import in bulk, and check what you've earned.
Self-serve signup is public. Pick a name and a slug; you get a working API key back immediately, shown once.
$ curl -X POST https://paywall-admin.wickedapi.com/signup \ -H "Content-Type: application/json" \ -d '{"name":"My API","slug":"myapi"}' { "tenant": { "id": "clx...", "slug": "myapi" }, "apiKey": "x402_live_..." // save this — shown once }
Rate-limited to 5 signups/hour/IP — plenty for a real signup, not much use for spam.
Set upstreamUrl and a paid request gets forwarded to your real backend; the real response comes back to the caller. Omit it and callers just get a generic confirmation payload instead — useful for testing the payment flow before your backend is ready.
$ curl -X POST https://paywall-admin.wickedapi.com/tenants/me/routes \ -H "Authorization: Bearer x402_live_..." \ -H "Content-Type: application/json" \ -d '{ "method": "GET", "path": "/forecast", "price": "$0.01", "network": "eip155:8453", "payTo": "0xYourWalletAddress", "upstreamUrl": "https://your-real-api.com/forecast" }' # Live at https://paywall.wickedapi.com/myapi/forecast — GET/POST query # params and JSON body are forwarded through to your backend as-is.
upstreamUrl must be a public https:// address — localhost, private IPs, and internal hostnames are rejected at write time.
Edits apply within about 20 seconds. The gateway polls Postgres and picks up price and payout-wallet changes live.
$ curl -X PATCH https://paywall-admin.wickedapi.com/tenants/me/routes/<route-id> \ -H "Authorization: Bearer x402_live_..." \ -H "Content-Type: application/json" \ -d '{"price": "$0.005"}'
Have ten endpoints to paywall? Send them all in one call — it upserts, so re-running the same import is safe.
$ curl -X POST https://paywall-admin.wickedapi.com/tenants/me/routes/import \ -H "Authorization: Bearer x402_live_..." \ -H "Content-Type: application/json" \ -d '{ "routes": [ { "method":"GET", "path":"/search", "price":"$0.002", "network":"eip155:8453", "payTo":"0xYourWallet", "upstreamUrl":"https://your-api.com/search" }, { "method":"POST", "path":"/analyze", "price":"$0.05", "network":"eip155:8453", "payTo":"0xYourWallet", "upstreamUrl":"https://your-api.com/analyze" } ] }'
Every settlement is recorded with tx hash, payer address, amount, and route — real on-chain data, queryable per-tenant.
$ curl https://paywall-admin.wickedapi.com/tenants/me/settlements \ -H "Authorization: Bearer x402_live_..." { "settlements": [{ "status": "settled", "amount": "10000", "txHash": "0x0c6d48...", ... }] }
/for-buyers
Three ways to pay, depending on where your code runs: a Node.js backend, a browser, or an autonomous agent discovering endpoints on its own.
The simplest path for a backend or an agent: the @x402/fetch interceptor handles the 402 → sign → retry cycle for you.
$ npm install @x402/fetch @x402/core viem import { withPaymentInterceptor } from "@x402/fetch"; import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; const account = privateKeyToAccount(process.env.PRIVATE_KEY); const wallet = createWalletClient({ account, chain: base, transport: http() }); const fetchWithPay = withPaymentInterceptor(fetch, wallet); const res = await fetchWithPay("https://paywall.wickedapi.com/wickedapi/weather"); const data = await res.json(); // 0.01 USDC paid automatically
The "exact" scheme is just an EIP-712-signed EIP-3009 authorization under the hood, so a browser wallet can sign it directly via eth_signTypedData_v4 — no bundler, no SDK. This is exactly what powers the live demo on our homepage.
// 1. Fetch unpaid, decode the 402 const res = await fetch("/wickedapi/weather"); const required = JSON.parse(atob(res.headers.get("payment-required"))); const accepted = required.accepts[0]; // 2. Sign the EIP-3009 authorization (domain comes from accepted.extra) const signature = await window.ethereum.request({ method: "eth_signTypedData_v4", params: [account, JSON.stringify(typedData)], // see full source below }); // 3. Retry with X-PAYMENT — real weather comes back on success const paid = await fetch("/wickedapi/weather", { headers: { "x-payment": btoa(JSON.stringify(paymentPayload)) }, });
Full working source (typed-data construction, nonce generation, chain switching): /pay-demo.js — it's the exact script running our own live demo, unminified.
Every route here is indexed on the x402 Bazaar the first time it settles a real payment — an autonomous agent can search it instead of being told which URL to call.
import { HTTPFacilitatorClient } from "@x402/core/server"; import { withBazaar } from "@x402/extensions/bazaar"; const client = withBazaar(new HTTPFacilitatorClient()); const resources = await client.extensions.bazaar.listResources({ type: "http" }); // -> paywalled endpoints, their prices, and their input/output shapes
Declare your wallet on the initial request to get a tier-discounted price quote. It's optimistic — we independently re-verify against your real, signature-backed wallet before settling, so declaring someone else's wallet gets you a cheaper quote, never a payment that actually goes through.
$ curl https://paywall.wickedapi.com/wickedapi/weather \ -H "X-Agent-Wallet: 0xYourStakedWalletAddress" # Tiers (live, from the reputation service): # unranked 1x rate · bronze 1.5x, 5% off · silver 2x, 10% off · gold 4x, 25% off
Staking itself happens on the reputation service, not here — see stake.wickedapi.com for its registration and staking endpoints.
Three things worth building into any agent that pays autonomously:
402 is expected, not an error — it's the price list. Parse accepts[] and decide whether to pay.502 after payment means the seller's own backend failed after settlement went through — there's no automatic refund yet, so budget for that like any real payment-then-fulfill system.