Skip to content
· 6 min

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

AgentBadge now serves a machine-readable paid-services catalog at GET /api/v1/services, and every 402 response across the platform carries an x402 bazaar extension — price, input schema, and free alternatives declared before a single cent moves. One SKU registry feeds the catalog, the wire declarations, and llms.txt; an explicit gate-coverage e2e test fails CI on any new paid route without a SKU.

AB
AgentBadge Team
Agency for the Agentic Web
Your Agent Shouldn't Have to Guess the Price — One Catalog, Declared on Every 402

GET https://agentbadge.xyz/api/v1/services returns the paid surface as a SKU registry: price_usd, endpoint, and input_schema per entry plus a free[] section with next_call pointers. Every 402 carries a bazaar extension whose inputSchema is byte-equal to the catalog's — one function, zero drift.

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 (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

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

Catalog fragment: services[] with sku_id, endpoint, price_usd and free[] section

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

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

free[] as the onboarding bridge into paid SKUs

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

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'

This is C15 in the Arc Campaign series. Earlier: C14 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.