B402 Bazaar
B402 Bazaar is the discovery layer for B402-registered paid endpoints. AI agents (and any other client) can search the Bazaar to find HTTP APIs that accept x402 payments — without anyone pre-configuring a list. As a merchant, you opt your endpoint in by attaching a metadata blob to your normal V2 settle call. Bazaar indexes you within ~30 seconds of the first confirmed settle carrying that blob.
Status: opt-in and free. No separate registration, no contract, no back-office signup. You declare metadata on your V2 settle, Bazaar picks it up.
Coinbase CDP-compatible blob shape. The bazaar blob you attach to settle matches CDP's x402 Bazaar extension spec field-for-field. If your code already produces a CDP-compatible bazaar blob (e.g. via
@coinbase/x402-fetchor the Python SDK), the same blob works against B402 unchanged. Discovery responses are wrapped in Binance's standard envelope (see "Verifying your listing" below) — that wrapping is on the roadmap to remove for full wire-shape parity with CDP.
How It Works
Your endpoint ─▶ V2 settle (with bazaar blob) ─▶ B402 ─▶ on-chain confirm
│
▼
Bazaar indexer (30s tick)
│
▼
/v1/b402/discovery/* API
- A buyer (usually an AI agent) calls your paid endpoint with an x402 V2 payment header.
- You forward the payment to B402's
/papi/v2/b402/settlewithpaymentPayload.extensions.bazaarpopulated. - B402 confirms the on-chain settle and stores the blob.
- Bazaar's indexer picks up the row on its next 30-second tick and upserts your resource into the catalog.
- Agents searching the Bazaar see your endpoint in the results.
First appearance: typically within 30–60 seconds of the first confirmed V2 settle. Failed or timed-out settles do not count toward indexing.
Updating metadata: subsequent settles with a different blob overwrite the stored row's metadata (keyed on (merchantId, resourceUrl)). You can update your description or schema just by shipping an updated blob on your next settle.
Multi-chain: if you settle the same URL on multiple chains, B402 appends each chain to your resource's accepts[] array rather than overwriting. Agents see all your payment options.
TL;DR — attach extensions.bazaar on every V2 settle
Minimum HTTP-GET example. The extensions.bazaar object goes inside the paymentPayload you send to /papi/v2/b402/settle:
{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"resource": {
"url": "https://api.example.com/btc-price",
"description": "Real-time BTC spot price aggregated from 20 exchanges.",
"mimeType": "application/json"
},
"accepted": { /* your PaymentRequirements */ },
"payload": { /* signed authorization */ },
"extensions": {
"bazaar": {
"info": {
"input": {
"type": "http",
"method": "GET",
"queryParams": { "symbol": "BTC" }
},
"output": {
"type": "json",
"example": { "symbol": "BTC", "price": 67000, "ts": 1700000000 }
}
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"type": { "const": "http" },
"method": { "enum": ["GET"] }
},
"required": ["type", "method"]
}
},
"required": ["input"]
},
"description": "Real-time BTC spot price aggregated from 20 exchanges."
}
}
},
"paymentRequirements": { /* same as /verify */ }
}
That's the whole integration. No other endpoints to call, no API keys to rotate.
The bazaar blob — field reference
| Field | Required | Type | Purpose |
|---|---|---|---|
info | yes | object | Discovery payload — describes the resource's input / output. Shape depends on variant (see below). |
schema | yes | JSON Schema (Draft 2020-12) object | Validates the shape of info. Must define input (required) and optionally output. Invalid blobs are skipped by the indexer. |
routeTemplate | no | string like /users/:userId | For parameterized HTTP routes. Lets B402 collapse /users/1 and /users/2 into a single catalog entry. Omit for static routes. |
description | no (but recommended) | string | Human-readable resource description. Surfaced on the API's resource-level description field. If omitted, B402 derives one from the URL. |
info variants
The shape of info.input depends on which HTTP method your endpoint uses. The discriminator is info.input.method.
Variant A — HTTP query method (GET, HEAD, DELETE)
{
"input": {
"type": "http",
"method": "GET",
"queryParams": { "symbol": "BTC" },
"headers": { "X-Client-Version": "1" }
},
"output": {
"type": "json",
"example": { "price": 67000 }
}
}
| Sub-field | Required | Notes |
|---|---|---|
input.type | yes | Always "http" |
input.method | yes | "GET", "HEAD", or "DELETE" |
input.queryParams | no | Example query params |
input.headers | no | Example headers (agents may use these to shape calls) |
output.type | yes (if output present) | e.g. "json", "text" |
output.example | no | An example payload the endpoint returns |
Variant B — HTTP body method (POST, PUT, PATCH)
Same as Variant A, plus input.bodyType ("json" / "form-data" / "text") and input.body (example request body — required for body methods).
{
"input": {
"type": "http",
"method": "POST",
"bodyType": "json",
"body": { "ticker": "AAPL", "depth": "full" }
},
"output": { "type": "json", "example": { "result": "..." } }
}
routeTemplate — parameterized routes
If your endpoint has path parameters like /users/123 and /users/456, set routeTemplate so Bazaar collapses them into a single catalog entry:
{
"info": { "input": { "type": "http", "method": "GET", "pathParams": { "userId": "123" } } },
"schema": { /* ... */ },
"routeTemplate": "/users/:userId"
}
CDP spec recommends: starts with /, matches ^/[a-zA-Z0-9_/:.\-~%]+$, no .., no ://. Use these for cross-ecosystem compatibility. Bazaar stores the value as-is — non-conforming templates are accepted, but agents using strict CDP clients may reject them. Static routes should omit routeTemplate.
Example: TypeScript
const PRICE_FEED_BLOB = {
info: {
input: {
type: "http" as const,
method: "GET" as const,
queryParams: { symbol: "BTC" },
},
output: {
type: "json" as const,
example: { symbol: "BTC", price: 67000 },
},
},
schema: {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
input: {
type: "object",
properties: {
type: { const: "http" },
method: { enum: ["GET"] },
},
required: ["type", "method"],
},
},
required: ["input"],
},
description: "Real-time BTC spot price aggregated from 20 exchanges.",
};
// When building the V2 payment payload for /papi/v2/b402/settle:
const paymentPayload = {
x402Version: 2,
resource: {
url: "https://api.example.com/btc-price",
description: PRICE_FEED_BLOB.description,
mimeType: "application/json",
},
accepted: paymentRequirements,
payload: { signature, authorization },
extensions: { bazaar: PRICE_FEED_BLOB },
};
Example: Python
PRICE_FEED_BLOB = {
"info": {
"input": {
"type": "http",
"method": "GET",
"queryParams": {"symbol": "BTC"},
},
"output": {"type": "json", "example": {"symbol": "BTC", "price": 67000}},
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"type": {"const": "http"},
"method": {"enum": ["GET"]},
},
"required": ["type", "method"],
}
},
"required": ["input"],
},
"description": "Real-time BTC spot price aggregated from 20 exchanges.",
}
payment_payload = {
"x402Version": 2,
"resource": {
"url": "https://api.example.com/btc-price",
"description": PRICE_FEED_BLOB["description"],
"mimeType": "application/json",
},
"accepted": payment_requirements,
"payload": {"signature": signature, "authorization": authorization},
"extensions": {"bazaar": PRICE_FEED_BLOB},
}
Verifying your listing
After your first confirmed V2 settle with the blob, hit any of the discovery endpoints (no auth required). Substitute {DISCOVERY_BASE_URL} with the value provided to you during onboarding — see Environments and API base URLs.
# Paginated catalog (response key: items[])
curl {DISCOVERY_BASE_URL}/v1/b402/discovery/resources
# Your listings only (response key: resources[])
curl "{DISCOVERY_BASE_URL}/v1/b402/discovery/merchant?payTo=0xYourPayToAddress"
# Keyword search (response key: resources[])
curl "{DISCOVERY_BASE_URL}/v1/b402/discovery/search?query=BTC+price"
Discovery responses are wrapped in Binance's standard {code, message, data, success} envelope. Your listing lives inside the data array — schemas below show the shape of each element under data.items[]:
{
"code": "000000",
"message": "success",
"data": {
"items": [
{
"resource": "https://api.example.com/btc-price",
"type": "http",
"x402Version": 2,
"description": "Real-time BTC spot price aggregated from 20 exchanges.",
"accepts": [
{
"scheme": "permit2-exact",
"network": "eip155:56",
"asset": "0x55d398326f99059fF775485246999027B3197955",
"maxAmountRequired": "1000",
"payTo": "0xabc..."
}
],
"lastUpdated": 1780476191702,
"quality": {
"l30DaysTotalCalls": 1543,
"l30DaysUniquePayers": 87,
"lastCalledAt": 1780476131000
}
}
],
"pagination": { "limit": 10, "offset": 0, "total": 2 },
"x402Version": 2
},
"success": true
}
How agents find you
AI agents query /v1/b402/discovery/search directly and pick resources from the result, then invoke them through their existing x402 client stack. Bazaar doesn't proxy the call — it's pure discovery.
Ranking & visibility
Discovery today orders results by:
- Text relevance — for
/search, yourdescriptionand synthesized internal name are matched against the agent's query (MySQL FULLTEXT). For/resourcesand/merchant, listings are returned most-recently-updated first. - Fail-rate demotion — resources with
fail_rate_24h > 50%are filtered from results entirely. Healthy resources are unaffected.
Two more signals are tracked per resource and surfaced on the response (quality.l30DaysTotalCalls / l30DaysUniquePayers) but don't yet feed the result order: recent-activity decay and buyer diversity. They roll into a composite score in a later release. A minimum-amount threshold (USD $0.01-equivalent per settle) filters dust out of the activity counters, so micro-settles can't pump apparent traffic.
FAQ
Do I have to opt in on every settle, or once is enough? Once is enough to create the listing — your endpoint is indexed on the first successful V2 settle that carries the blob, and stays indexed indefinitely. Include the blob on later settles only when you want to update metadata (description, schema, route template). Bazaar treats each blob-bearing settle as "re-advertise this listing with this metadata."
What if I have more than one endpoint?
Send a different blob for each endpoint's settles. Each (merchantId, resourceUrl) pair is a separate listing.
Can I support multiple chains on the same URL?
Yes — settle on each chain separately with the same resource.url. B402 merges the accepts[] array so agents see all your chains on the same resource. Dedup key is (scheme, network, asset) — the latest settle on a given tuple overwrites that entry.
What if the same endpoint has multiple pricing tiers (e.g. basic / pro)?
Bazaar treats them as one listing per (merchantId, resourceUrl) — the last settle's maxAmountRequired wins for that (network, asset, scheme) entry. Multi-tier listings are on the roadmap.
Can I submit a listing without settling? Not in v0 — listing is strictly opt-in via successful V2 settle. Marketing-only listings are deferred to a future version.
How do I get removed? Stop including the blob on new settles and your listing's recency signal will decay until it sinks below the visible results. For an active takedown (e.g. leaked API key, compromised endpoint), reach out via the application form and we'll hide the listing.
Next Steps
- Settle Payment (V2) — the wire shape that carries the bazaar blob.
- Environments and API base URLs — where to call settle, and (when published) where to call discovery.
- Integration Guideline — prerequisites, checklist, and best practices for B402 generally.