AgentGrade
EnglishEspañol日本語中文
← Knowledge Base

Bazaar — Service Discovery

What is Bazaar?

Bazaar is the discovery layer for x402. It answers: "What can I buy here, and how much does it cost?" Agents browse a structured catalog of services, prices, and input/output schemas at /.well-known/x402.json.

How it works

Publish /.well-known/x402.json:

{
  "x402Version": 2,
  "name": "Your Service",
  "description": "What your service does",
  "network": "base",
  "facilitator": "coinbase",
  "payTo": "0xYourWallet",
  "services": [
    {
      "method": "POST",
      "path": "/api/generate",
      "description": "Generate content",
      "amount": "100000",
      "discoverable": true,
      "outputSchema": {
        "input": {
          "type": "http",
          "method": "POST",
          "bodyFields": {
            "prompt": { "type": "string", "required": true }
          }
        },
        "output": {
          "type": "json",
          "schema": { "result": { "type": "string" } }
        }
      }
    }
  ]
}

Required fields

v1 vs v2

x402 v2 launched in December 2025 and is the current spec, but v1 servers are still common in the wild. AgentGrade accepts both. Every protocol-level difference:

If you're building new, build v2. If you're auditing an existing v1 server, the scanner won't penalize you for being on v1 — but the v2 differences above are real efficiency wins.

How to return a 402

The discovery catalog above declares what you sell. The 402 response is what actually triggers payment when an agent calls a paid endpoint without one.

v2 form (current spec)

The payment challenge goes in the PAYMENT-REQUIRED response header as a base64-encoded JSON object. The response body is yours to use however you want — typically a human-readable paywall page. The retry request arrives with the signed payment in the PAYMENT-SIGNATURE request header (also base64-encoded JSON). On success, the server may set a PAYMENT-RESPONSE header with settlement details.

app.post('/api/generate', async (req, res) => {
  if (!req.get('PAYMENT-SIGNATURE')) {
    const challenge = {
      x402Version: 2,
      resource: {
        url: 'https://yourdomain.com/api/generate',
        description: 'Generate content from a prompt',
        mimeType: 'application/json'
      },
      accepts: [{
        scheme: 'exact',
        network: 'base',                 // or CAIP form: 'eip155:8453'
        amount: '100000',                // smallest units; 6-decimal USDC → $0.10
        asset: '0x833589fCD6EDb6E08f4c7C32D4f71b54bdA02913',
        payTo: '0xYourWalletAddress',
        maxTimeoutSeconds: 60
      }],
      extensions: { bazaar: { discoverable: true } }
    };
    res.status(402);
    res.set('PAYMENT-REQUIRED', Buffer.from(JSON.stringify(challenge)).toString('base64'));
    return res.send('<h1>$0.10 to generate content</h1>');
  }

  // Verify the signature with your facilitator, then run the work.
  // The @coinbase/x402 SDK handles the verification call for you.
  res.json({ result: '...' });
});

v1 form (legacy, body-based)

v1 servers put the challenge in the JSON body of the 402 response. The retry request used the X-PAYMENT header, and a successful settlement was returned in X-PAYMENT-RESPONSE. v1 also uses maxAmountRequired instead of v2's amount.

app.post('/api/generate', async (req, res) => {
  if (!req.get('X-PAYMENT')) {
    return res.status(402).json({
      x402Version: 1,
      error: 'Payment required to access this resource',
      accepts: [{
        scheme: 'exact',
        network: 'base',
        maxAmountRequired: '100000',
        asset: '0x833589fCD6EDb6E08f4c7C32D4f71b54bdA02913',
        payTo: '0xYourWalletAddress',
        resource: 'https://yourdomain.com/api/generate',
        maxTimeoutSeconds: 60
      }]
    });
  }
  // ...verify the X-PAYMENT signature, then serve the work
});

Heads up — AgentGrade's live-402 check is v2-only. Our scanner reads the PAYMENT-REQUIRED response header to confirm a working paywall. A v1 server that puts its challenge in the body will pass the discovery-catalog check (/.well-known/x402.json) but won't score on the live-402 check. If that matters to you, build v2.

Use the SDK

You almost certainly don't want to hand-roll signature verification — call the @coinbase/x402 middleware instead. It wraps both response shapes and the facilitator round-trip in one line of Express/Fastify/Hono setup.

extensions.bazaar in the live 402 header

When an agent calls a paid endpoint without payment, your server returns HTTP 402 with a base64-encoded Payment-Required header. That header's JSON payload should declare extensions.bazaar so agents know this endpoint is part of a discoverable catalog. The JSON payload before base64 encoding (v2 form):

{
  "x402Version": 2,
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "amount": "100000",
      "asset": "0xUSDC...",
      "payTo": "0xYourWallet",
      "maxTimeoutSeconds": 60
    }
  ],
  "extensions": {
    "bazaar": { "discoverable": true }
  }
}

Without extensions.bazaar, agents that hit a 402 have no signal that the endpoint is also catalogued at /.well-known/x402.json — they may treat it as a one-off paid endpoint instead of part of a browseable service.

Spec maturity

Part of x402 v2. Bazaar discovery is defined within the x402 specification.

Learn more

Related