Cataloging is automatic. A seller does not register a listing after a payment;
the facilitator extracts the Bazaar declaration from the PaymentPayload it
already receives. Seller metadata remains the source of truth. The facilitator
does not ask an LLM to invent descriptions, parameters, prices, or capabilities.
Payment-to-catalog flow
Cataloging and analytics are soft-failure side effects. Invalid metadata never
turns a valid payment into a payment rejection, and a catalog database failure
must not change the payment response.
The configured index_on boundary is either verified or settled. The
checked-in hosted profile uses verified, so a valid declared payment can be
indexed before final settlement; operators that require only completed payments
can choose settled.
Validation and trust boundary
The facilitator delegates Bazaar validation to the official x402 extension
builders and validators, including JSON Schema validation. It then applies local
bounds and security checks:
- metadata byte, depth, description, service-name, tag, icon, schema, example,
and route-template limits;
- Unicode normalization and control/bidi stripping;
- URL normalization and HTTPS-origin policy;
- percent-decoding before route-template traversal checks;
- valid HTTP method/path or MCP transport/tool identity;
- no unsafe route traversal or malformed echoed resource block;
- no silent replacement of an existing resource’s owner
payTo.
Invalid declarations are recorded as rejected observations with a stable public
reason and precise internal reason. The response uses the official
EXTENSION-RESPONSES header; no private status field is added to Bazaar JSON.
payment_observed means this facilitator itself verified or settled a real
payment whose declared asset, network, amount, and recipient matched. It does
not prove that the seller controls the origin, that the description is truthful,
or that the endpoint is high quality. The dashboard may show provenance and
status; public Bazaar responses do not.
Identity and ownership
HTTP resources are keyed by normalized origin, route template or path, and
uppercase method. MCP resources are keyed by the tuple:
Two MCP tools at one endpoint therefore remain separate catalog entries. A
resource is owned by the first observed payTo; a later declaration with a
different recipient cannot silently take over that identity. A changed price,
asset, timeout, schema, or description creates a new append-only version and
payment-option history.
Versioning and cursor snapshots
catalog_resources is the stable identity row. Each declaration is stored in
catalog_resource_versions, and each accepted option is stored in
catalog_payment_options. The rows are append-only for seller declarations;
historical records keep the terms that were actually paid.
Every visible version receives a monotonic catalog watermark under a PostgreSQL
row lock. A discovery cursor contains:
- the snapshot watermark;
- the result offset;
- a fingerprint of query and filters;
- an expiry timestamp; and
- a search-session ID for ranked result attribution.
The cursor is HMAC-signed. Reusing it with different filters, a modified body,
or an expired timestamp returns invalid_cursor. Pages therefore remain stable
while new resources are written by another replica.
Liveness and staleness
The catalog records first_seen, last_seen, last_seen_paid, active/stale/
quarantined status, and observation outcomes. A resource that stops being seen
is demoted after stale_after_hours and is excluded by the default discovery
profile. Operators can explicitly include stale entries for investigation.
Nothing is silently deleted merely because an endpoint stopped responding:
retention and tombstoning are operational decisions, while the audit trail
preserves why a listing was rejected, superseded, quarantined, or stale.
Search handoff
Every active version gets a deterministic canonical search document compiled from
seller fields. The lexical document is written synchronously; embedding work is
queued in PostgreSQL and is never awaited by /verify or /settle. A worker
claims jobs with FOR UPDATE SKIP LOCKED, leases and fencing tokens. Failed
jobs back off and eventually become dead-lettered, where npm run index -- revive
can requeue them.
This separation keeps payment latency independent from model availability. With
no pgvector or embedding provider, the same catalog remains browseable and
searchable through PostgreSQL full-text search.
Inspecting outcomes
Use the public discovery routes for the official wire shape:
Use authenticated analytics to inspect rejected observations, versions,
verification, payment options, stale state, search generations, and resource
liveness. See Analytics API and
storage. Last modified on August 10, 2026