# Your Agent Shouldn't Have to Guess the Price — One Catalog, Declared on Every 402

> Published: 2026-10-08 | Author: AgentBadge Team | Canonical: https://agentbadge.xyz/blog/arc-c15-x402-bazaar

An agent that wants to buy from an API answers three questions before it pays: *what does it cost, what do I send, and is there a free way to try it first?* Most payment-gated APIs make the agent guess all three — the price lives in a marketing page, the input schema lives in a wiki, and "free tier" is a sales call. This week AgentBadge closed that loop: GET /api/v1/services returns the full machine-readable catalog, and every HTTP 402 response across the platform declares itself for x402 indexers — price, input schema, and free alternatives, before a single cent moves.

This is article 15 in the Arc Campaign series. It builds directly on [C14](https://agentbadge.xyz/blog/arc-c14-agent-discovery) (the .well-known discovery surface that helps agents *find* us) — C15 is what an agent reads once it found us and wants to *buy* something.

![Catalog JSON and a 402 coin — price before payment](/images/blog/arc-c15-x402-bazaar-hero.png)

## One registry, three consumers

The interesting decision wasn't the endpoint — it was what feeds it. We didn't want another hand-maintained list that rots. So the catalog is a declarative **SKU registry** (lib/service-catalog) that reads from the same config sources the payment middleware reads from — the scan-bundle price you see in the catalog is the price the gate will actually charge, because both derive from one source of truth.

That one registry now feeds three consumers: GET /api/v1/services — the JSON catalog for agents and indexers; the **bazaar extension** on every 402 — x402 v2 discovery metadata; and llms.txt — the "Paid Services" section links anchor to /api/v1/services#<sku_id> (from our EPIC-178 discovery work).

Fourteen SKUs across eight paid surfaces today: readiness scans, passport mints, marketplace buys, keeperhub premium scans, eval-as-a-service verdicts/jobs/subscriptions, bstock service passes, and venue instance subscriptions. A SKU looks like this:

{
  "sku_id": "eaas:verdict",
  "surface": "eaas",
  "name": "Eval verdict",
  "price_usd": "0.10",
  "endpoint": { "method": "POST", "path": "/api/eaas/verdicts" },
  "auth": "x402",
  "input_schema": {
    "type": "object",
    "properties": { "url": { "type": "string" } },
    "required": ["url"]
  }
}
sku_id is surface:slug and immutable once published — it is the public contract agents bookmark and indexers key on. Our coverage test keeps a golden list of all fourteen ids; renaming one fails CI.

![Diagram: live config sources feed one SKU registry, which feeds the catalog endpoint, the bazaar extension on every 402, and llms.txt — with the coverage e2e watching for drift](/images/blog/arc-c15-x402-bazaar-d1.png)

![Catalog fragment: services[] with sku_id, endpoint, price_usd and free[] section](/images/blog/arc-c15-x402-bazaar-1.png)

## Every 402 declares itself (bazaar)

An x402 "bazaar" extension is metadata inside the 402 response that tells indexers — and paying clients — what the call costs and what the input should look like. The rule we shipped: **every 402 in the platform carries it**, not just the happy-path REST endpoints.

Every payment gate — x402 Hedera, MPP/Stripe, bstock freemium, the manual settle seam behind venue subscriptions, all three EaaS middlewares — now attaches bazaarExtensionFor("<sku_id>") to its payment options. The extension object looks like:

{
  "bazaar": {
    "info": {
      "input": { "type": "http", "bodyType": "json",
                 "body": { "url": "https://example.com" } },
      "output": { "type": "json", "example": { "score": 72 } }
    },
    "schema": {
      "properties": { "input": { "properties": {
        "body": { "type": "object",
                  "properties": { "url": { "type": "string" } },
                  "required": ["url"] } } } }
    }
  }
}
The important invariant: schema.properties.input.properties.body is inputSchemaOf(sku) — *byte-equal* to the schema in the catalog. Nobody hand-copies a schema into a 402; there is exactly one function that produces it, so catalog and wire declaration cannot drift (we wrote that down as decision D-179-4).

Even our L402 (Lightning-style macaroon) gate is accounted for — its challenge is WWW-Authenticate-based with no JSON slot, so it lives on an explicit exception list in the coverage test rather than pretending to carry metadata it can't hold.

![402 anatomy: PAYMENT-REQUIRED header decoded to extensions.bazaar](/images/blog/arc-c15-x402-bazaar-2.png)

## Free as the front door

A catalog that only lists prices is half a catalog. Agents evaluating a new provider want to *try before they trust*, so /api/v1/services carries a free[] section alongside services[] — health checks, the scan-packs catalog (GET /api/scan-packs), marketplace browsing — each with a next_call pointer to the natural paid follow-up.

That made the free section an onboarding bridge rather than a footnote: an agent can hit /api/health and next_call points it at the free scan-packs listing; the scan-packs listing explains which paid bundles exist; the paid bundle declares its bazaar schema on the 402. Discovery → free trial → paid call, with zero documentation reading.

![Diagram: the full agent journey — discovery surface to catalog to free endpoint to paid SKU, where the 402 itself carries the bazaar declaration](/images/blog/arc-c15-x402-bazaar-d2.png)

![free[] as the onboarding bridge into paid SKUs](/images/blog/arc-c15-x402-bazaar-4.png)

## No paid endpoint without a declaration

A registry only stays honest if adding a route without registering it *breaks the build*. tests/e2e/catalog-coverage.test.ts keeps an explicit GATE_TABLE — every payment gate in the codebase, mapped to the SKU ids that cover its endpoint. The test walks both directions: every gate row must resolve to real SKUs on that endpoint, and every SKU's endpoint must be claimed by a gate row. Then it mounts the real route modules and asserts each SKU endpoint resolves (a Hono bare-404 fails; a handler-level "unknown id" 404 doesn't — we learned that one the hard way with fixture :param values).

Two rows aren't SKUs by design: l402 (macaroon challenges, no JSON slot) and attestation-api (internal trust surface, not a product). They sit in GATE_EXCEPTIONS with written reasons — an allowlist that can only grow in code review, never silently.

![GATE_TABLE coverage matrix — every gate has a SKU, every SKU a gate](/images/blog/arc-c15-x402-bazaar-3.png)

## Honest status

Shipped across five slices: the SKU registry, GET /api/v1/services, bazaar coverage on every 402 (including the settle-seam PAYMENT-REQUIRED header — that slot was empty before), the free[] section with next_call bridges into llms.txt, and the coverage/drift e2e contract. Deprecated /pricing.json and /api/meta/fees still serve but point at /api/v1/services — additive, no breaking change for existing clients.

What's *not* here yet: tenant-published market services don't appear in the catalog (they live under /api/market/services and are dynamic — that's O3 on our decisions doc, deliberately out of scope), and the catalog advertises USD prices while the runtime router decides which chains/assets to accept — separation of declaration and settlement is intentional.

## Try it

# The whole paid surface, one GET:
curl -s https://agentbadge.xyz/api/v1/services | jq '.services[].sku_id'

# Decode a real 402's bazaar declaration:
curl -s -X POST https://agentbadge.xyz/api/total-scan \
  -H 'content-type: application/json' -d '{}' \
  -D - | grep -i payment-required | cut -d' ' -f2- \
  | base64 -d | jq '.extensions.bazaar.info'

- Catalog implementation: src/server/routes/services-catalog.ts and lib/service-catalog/ in the [agentbadge repo](https://github.com/spreadzp/agentbadge)

- Coverage contract: [tests/e2e/catalog-coverage.test.ts](https://github.com/spreadzp/agentbadge/blob/main/hackathon/server/tests/e2e/catalog-coverage.test.ts) — the GATE_TABLE every new paid route must join

*This is C15 in the Arc Campaign series. Earlier: [C14](https://agentbadge.xyz/blog/arc-c14-agent-discovery) built the .well-known surface that gets agents to the door; C15 is what they read at the counter. Next in the pipeline: verdict transparency and keyless signers.*

---

## For AI Agents

- Companion guide: https://agentbadge.xyz/agent-guide/articles/arc-c15-x402-bazaar
- Knowledge Index: https://agentbadge.xyz/agent-guide/
- LLM entry point: https://agentbadge.xyz/llms.txt
- Engineering services: https://agentbadge.xyz/agent-guide/team/services
