Building Self-Describing Payment APIs with x402 Discovery
๐ The Problem
AI agents need to discover what tools and APIs are available before they can call them. OpenAI's function calling and MCP solved this for free tools โ but what happens when the tool costs money?
The x402 protocol adds pay-per-use to HTTP via 402 Payment Required challenges. But the protocol doesn't define how clients discover what endpoints exist or what they do before paying.
We built a solution using three discovery layers, each targeting a different consumer.
๐ช Layer 1: x402 Bazaar โ Official Protocol Discovery
x402 ships with a built-in discovery extension called Bazaar (@x402/extensions/bazaar). It decorates route configs with self-description metadata that becomes available in the 402 challenge response headers.
x402 Bazaar docs ยท Source: @x402/extensions
The key API is declareDiscoveryExtension โ it accepts three patterns:
- Query endpoints (GET):
method,queryParams,pathParams,output - Body endpoints (POST/PUT):
method,bodyType,body(input schema),pathParams,output - MCP endpoints:
toolName,description,transport,inputSchema,example,output
How it flows:
Route config โ extensions.bazaar = { info: {...}, schema: {...} }
โ
402 challenge response includes extension metadata in headers
โ
Client reads metadata to understand endpoint capabilities
We attached Bazaar extensions to all 3 endpoints in our API โ stealth_dom, airgap_scrub, rag_shrink โ with full input schemas and example outputs.
๐ค Layer 2: MCP-Style /api/v1/tools โ Machine Discovery
AI agents (Claude, OpenAI, etc.) expect OpenAI-compatible function definitions. We added a /api/v1/tools endpoint that returns exactly that:
{
"tools": [
{
"type": "function",
"function": {
"name": "stealth_dom",
"description": "Fetch any URL from Cloudflare edge...",
"parameters": {
"type": "object",
"properties": {
"url": { "type": "string", "description": "URL to fetch" }
},
"required": ["url"]
}
}
}
],
"metadata": {
"server": "x402-api",
"base_url": "https://x402-gateway-prod.look-a3a.workers.dev",
"payment": { "network": "Base", "currency": "USDC", "price": "$0.01" }
}
}
This matches the format the x402 MCP integration guide recommends. AI agents fetch this once, understand every available tool, and know the payment requirements upfront.
๐ค Layer 3: Enhanced /health โ Human Discovery
Developers need something readable. We enhanced the /health endpoint with:
- Full tool descriptions with input/output schemas
- Live example payloads
- Payment configuration (network, currency, wallet)
- Discovery metadata pointing to Bazaar and the tools endpoint
No Swagger UI needed โ just a structured JSON response a developer can curl and understand.
๐ How The Rest of the Industry Does It
Three coexisting patterns emerged from researching the x402 ecosystem:
Bazaar (official) โ declareDiscoveryExtension built into @x402/extensions. Used by GoPlausible and growing. Protocol-aware clients read extension metadata from challenge headers.
MCP tools (OpenAI format) โ /api/v1/tools or similar endpoints returning function definitions. Highest adoption among AI agent integrations. Recommended by x402.
OpenAPI (Swagger) โ Standard OpenAPI 3.0/3.1 specs with Swagger UI. Used by Zuplo for human developer docs. Niche but universally understood.
No universal standard exists yet. Our approach: implement Bazaar + MCP tools now, add OpenAPI later if needed.
๐ ๏ธ What We Built
API: x402 API Gateway โ 3 endpoints on Cloudflare Workers with pay-per-use via x402.
Source: /opt/data/projects/x402-api/src/index.ts โ Hono + @x402/hono middleware + Bazaar extensions.
Tests: 6/6 passing โ stealth_dom (~168ms DOM fetch), airgap_scrub (4 PII types redacted), rag_shrink (paragraph extraction), /health, /api/v1/tools, and 402 challenge flow.
MCP integration: Memory-melon proxies all 3 tools via Go handlers in internal/mcp/endpoints.go.
โ๏ธ Implementation Details
Resilient Payment Middleware
The default x402 facilitator (https://x402.org/facilitator) is unreachable. Our middleware wraps facilitator calls in try/catch so failed payments return proper 402 challenges instead of 500 crashes:
try {
const payment = await createX402Headers({...});
res.setHeader('X-402-Payment-Required', payment.challenge);
} catch {
// Return 402 with inline challenge on facilitator failure
res.status(402).json({ error: 'Payment required', ... });
}
Dev Mode
X-Dev-Bypass: true skips payment verification entirely โ useful for local development and testing. Production deploys remove the header.
Per-Endpoint Pricing
wrangler.toml has separate price vars (PRICE_STEALTH_DOM=0.05, etc.) ready to wire into the discovery layer so pricing is self-describing.
๐ Sources
- x402 docs โ Official protocol documentation
- x402 Bazaar extension โ Discovery layer spec
- x402 MCP integration guide โ Recommended patterns for AI agents
- x402 core library โ
@x402/hono,@x402/extensions - GoPlausible x402 implementation โ Real-world Bazaar usage
- Zuplo x402 gateway โ OpenAPI + x402 pattern
- Awesome x402 โ Community resource list (50+ projects)
- Cloudflare Workers docs โ Deployment target
- Hono framework โ API runtime