# `X402.Extensions.Bazaar`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/extensions/bazaar.ex#L1)

Bazaar discovery extension for x402 v2: extension builder and discovery client.

## Discovery extension builder

Resource servers advertise their endpoint specification by placing the
extension under `extensions.bazaar` in a `PAYMENT-REQUIRED` response. The
extension carries the actual discovery data (`info`) together with a JSON
Schema (Draft 2020-12) that validates it (`schema`).

`info.input` is a discriminated union selected by the `type` field:

  * `"http"` — an HTTP endpoint. Query methods (`GET`, `HEAD`, `DELETE`)
    describe example query parameters; body methods (`POST`, `PUT`, `PATCH`)
    add a `bodyType` and an example `body`.
  * `"mcp"` — a Model Context Protocol tool, identified by `toolName` and
    described by a JSON Schema `inputSchema` for its arguments.

`info.output` describes the expected response format. It is always present
(defaulting to a `"json"` content type). The schema's `output` property
declares `type` (and optionally `format`) as strings and infers the JSON
type of `example`, optionally refined by the `:schema` option.

The factory returns a plain, string-keyed map ready for JSON encoding:

    extensions = %{"bazaar" => X402.Extensions.Bazaar.build_extension(method: :get)}

Dynamic routes add the canonical `routeTemplate` catalog key with the
`:route_template` option. Provider-level service metadata (`serviceName`,
`tags`, `iconUrl`) lives on the `PaymentRequired.resource` object instead
and is validated by `X402.Extensions.Bazaar.Metadata`, which also carries
the facilitator-side soft-drop sanitizers.

## Discovery client

`list_resources/2` queries a facilitator's `GET /discovery/resources`
through `X402.Facilitator.list_resources/2` and parses each discovered
entry into a well-typed map (see `t:resource/0`). Parsing is fail-closed:
a structurally invalid entry returns
`{:error, %X402.Facilitator.Error{type: :malformed_facilitator_response}}`
identifying the offending entry, rather than partial data. Use
`X402.Facilitator.list_resources/2` directly and `parse_resource/1`
per-entry to build a lenient listing instead. `search/2` does the same
for the natural-language `GET /discovery/search` endpoint
(`X402.Facilitator.search_resources/2`), which pages with an opaque
cursor.

The pure filter helpers `filter_by_network/2`, `filter_by_scheme/2`, and
`filter_by_max_price/2` narrow a parsed listing client-side:

    {:ok, %{items: items}} = X402.Extensions.Bazaar.list_resources(MyFacilitator)

    items
    |> X402.Extensions.Bazaar.filter_by_network("eip155:8453")
    |> X402.Extensions.Bazaar.filter_by_max_price("100000")

See the
[bazaar extension spec](https://github.com/x402-foundation/x402/blob/main/specs/extensions/bazaar.md).

# `filter_by_max_price`
*since 0.6.0* 

```elixir
@spec filter_by_max_price([resource()], String.t() | non_neg_integer()) :: [
  resource()
]
```

Keeps the resources with at least one payment option at or below a price.

The price is compared in atomic token units against each accepted
`PaymentRequirements` entry's `amount` (falling back to the legacy
`maxAmountRequired`). Entries without a parsable amount never match.
Raises `ArgumentError` when `max_price` itself is not a non-negative
integer or decimal string, since that is a programmer error.

## Examples

    iex> resources = [
    ...>   %{resource: "https://a.example", accepts: [%{"scheme" => "exact", "amount" => "10000"}]},
    ...>   %{resource: "https://b.example", accepts: [%{"scheme" => "exact", "amount" => "250000"}]}
    ...> ]
    iex> resources
    ...> |> X402.Extensions.Bazaar.filter_by_max_price("100000")
    ...> |> Enum.map(& &1.resource)
    ["https://a.example"]

# `filter_by_network`
*since 0.6.0* 

```elixir
@spec filter_by_network([resource()], String.t()) :: [resource()]
```

Keeps the resources that accept payment on the given CAIP-2 network.

## Examples

    iex> resources = [
    ...>   %{resource: "https://a.example", accepts: [%{"scheme" => "exact", "network" => "eip155:8453"}]},
    ...>   %{resource: "https://b.example", accepts: [%{"scheme" => "exact", "network" => "solana:mainnet"}]}
    ...> ]
    iex> resources
    ...> |> X402.Extensions.Bazaar.filter_by_network("eip155:8453")
    ...> |> Enum.map(& &1.resource)
    ["https://a.example"]

# `filter_by_scheme`
*since 0.6.0* 

```elixir
@spec filter_by_scheme([resource()], String.t()) :: [resource()]
```

Keeps the resources that accept payment with the given scheme.

## Examples

    iex> resources = [
    ...>   %{resource: "https://a.example", accepts: [%{"scheme" => "exact", "network" => "eip155:8453"}]},
    ...>   %{resource: "https://b.example", accepts: [%{"scheme" => "upto", "network" => "eip155:8453"}]}
    ...> ]
    iex> resources
    ...> |> X402.Extensions.Bazaar.filter_by_scheme("upto")
    ...> |> Enum.map(& &1.resource)
    ["https://b.example"]

# `list_resources`
*since 0.6.0* 

```elixir
@spec list_resources(
  X402.Facilitator.server() | keyword(),
  keyword()
) ::
  {:ok, discovery_response()}
  | {:error,
     X402.Facilitator.Error.t() | NimbleOptions.ValidationError.t() | term()}
```

Lists discoverable x402 resources from a facilitator's bazaar as typed maps.

Queries `GET /discovery/resources` through
`X402.Facilitator.list_resources/2` (accepting the same filter and
pagination parameters) and parses every discovered entry with
`parse_resource/1`. Parsing is fail-closed: a structurally invalid entry
returns `{:error, %X402.Facilitator.Error{type:
:malformed_facilitator_response, reason: {:invalid_resource, index,
reason}}}` instead of partial data.

When called with just a keyword list — `list_resources(limit: 20)` — the
parameters apply to the default `X402.Facilitator` process name.

## Examples

    {:ok, %{items: items, pagination: pagination}} =
      X402.Extensions.Bazaar.list_resources(MyFacilitator,
        network: "eip155:8453",
        limit: 20
      )

    Enum.map(items, & &1.resource)

# `parse_resource`
*since 0.6.0* 

```elixir
@spec parse_resource(term()) :: {:ok, resource()} | {:error, term()}
```

Parses one raw discovered-resource map into a typed map.

Validates the required fields from the v2 specification (§8.3): `resource`,
`type`, `x402Version`, `accepts` (a list of `PaymentRequirements` maps),
and `lastUpdated` (Unix timestamp or ISO 8601 string). Optional
`description`, `mimeType`, `metadata`, and `extensions` fields default to
`nil` when absent and are rejected when mistyped.

## Examples

    iex> {:ok, parsed} = X402.Extensions.Bazaar.parse_resource(%{
    ...>   "resource" => "https://api.example.com/premium-data",
    ...>   "type" => "http",
    ...>   "x402Version" => 2,
    ...>   "accepts" => [%{"scheme" => "exact", "network" => "eip155:8453", "amount" => "10000"}],
    ...>   "lastUpdated" => 1_703_123_456
    ...> })
    iex> {parsed.resource, parsed.x402_version, parsed.last_updated}
    {"https://api.example.com/premium-data", 2, 1703123456}

    iex> X402.Extensions.Bazaar.parse_resource(%{"type" => "http"})
    {:error, {:missing_field, "resource"}}

    iex> X402.Extensions.Bazaar.parse_resource(%{
    ...>   "resource" => "https://api.example.com",
    ...>   "type" => "http",
    ...>   "x402Version" => 2,
    ...>   "accepts" => "exact",
    ...>   "lastUpdated" => 1
    ...> })
    {:error, {:invalid_field, "accepts"}}

# `search`
*since 0.9.0* 

```elixir
@spec search(
  X402.Facilitator.server() | keyword(),
  keyword()
) ::
  {:ok, search_response()}
  | {:error,
     X402.Facilitator.Error.t() | NimbleOptions.ValidationError.t() | term()}
```

Searches a facilitator's bazaar with a natural-language query, as typed maps.

Queries `GET /discovery/search` through
`X402.Facilitator.search_resources/2` (accepting the same parameters —
`:query` is required) and parses every matching entry with
`parse_resource/1`, fail-closed exactly like `list_resources/2`. Pass
`pagination.cursor` back as `:cursor` to fetch the next page.

When called with just a keyword list — `search(query: "weather")` — the
parameters apply to the default `X402.Facilitator` process name.

## Examples

    {:ok, %{resources: resources, partial_results: partial, pagination: pagination}} =
      X402.Extensions.Bazaar.search(MyFacilitator, query: "weather forecast", limit: 10)

    Enum.map(resources, & &1.resource)

# `discovery_response`

```elixir
@type discovery_response() :: %{
  x402_version: integer() | nil,
  items: [resource()],
  pagination: X402.Facilitator.discovery_pagination() | nil
}
```

Parsed response of `list_resources/2`.

# `resource`

```elixir
@type resource() :: %{
  resource: String.t(),
  type: String.t(),
  x402_version: integer(),
  accepts: [map()],
  last_updated: integer() | String.t(),
  description: String.t() | nil,
  mime_type: String.t() | nil,
  metadata: map() | nil,
  extensions: map() | nil
}
```

A discovered x402 resource parsed from `GET /discovery/resources`.

`:accepts` entries are the raw, string-keyed `PaymentRequirements` maps
from the wire. `:last_updated` is either a Unix timestamp (per the v2
specification) or an ISO 8601 string (as emitted by some facilitators).

# `search_response`

```elixir
@type search_response() :: %{
  x402_version: integer() | nil,
  resources: [resource()],
  partial_results: boolean() | nil,
  pagination: X402.Facilitator.discovery_search_pagination() | nil
}
```

Parsed response of `search/2`.

# `t`

```elixir
@type t() :: %{required(binary()) =&gt; map()}
```

A built `extensions.bazaar` discovery extension payload.

# `build_extension`
*since 0.5.0* 

```elixir
@spec build_extension(keyword()) :: t()
```

Builds a `bazaar` discovery extension payload (`info` + `schema`).

Accepts either an HTTP endpoint config or an MCP tool config.

## HTTP options

  * `:method` — (required) HTTP method: `:get`, `:head`, `:delete`,
    `:post`, `:put`, `:patch` (or the uppercase string). Query methods
    produce a read-only signature; body methods add `bodyType` and `body`.
  * `:input` — example input values (a map of query parameters for query
    methods, a map for `"json"` / `"form-data"` bodies, or a string for
    `"text"` bodies).
  * `:input_schema` — JSON Schema merged into the schema's `queryParams`
    or `body` property.
  * `:body_type` — request body content type for body methods: `"json"`
    (default), `"form-data"`, or `"text"`.
  * `:headers` — example custom header values.
  * `:path_params` — concrete path parameter values (dynamic routes).
  * `:path_params_schema` — JSON Schema for path parameters.
  * `:route_template` — canonical `:param` template for dynamic routes
    (for example `"/users/:userId"`), published as the extension's
    top-level `routeTemplate` catalog key. Must satisfy
    `X402.Extensions.Bazaar.Metadata.valid_route_template?/1`.

## MCP options

  * `:tool_name` — (required) MCP tool name.
  * `:input_schema` — (required) JSON Schema for the tool's `arguments`.
  * `:description` — human-readable tool description.
  * `:transport` — MCP transport: `"streamable-http"` (default) or `"sse"`.
  * `:example` — example `arguments` object.

## Output options (`:output`)

A keyword list or map describing the expected response:

  * `:type` — response content type (default `"json"`).
  * `:format` — additional format information.
  * `:example` — example response value.
  * `:schema` — JSON Schema merged into the schema's `example` property.

## Examples

    iex> ext = X402.Extensions.Bazaar.build_extension(method: :get, input: %{"city" => "San Francisco"})
    iex> ext["info"]["input"]["type"]
    "http"
    iex> ext["info"]["input"]["method"]
    "GET"
    iex> ext["info"]["input"]["queryParams"]
    %{"city" => "San Francisco"}

    iex> ext = X402.Extensions.Bazaar.build_extension(method: :post, input: %{"query" => "example"})
    iex> ext["info"]["input"]["bodyType"]
    "json"

    iex> ext = X402.Extensions.Bazaar.build_extension(
    ...>   method: :get,
    ...>   path_params: %{"userId" => "123"},
    ...>   route_template: "/users/:userId"
    ...> )
    iex> {ext["routeTemplate"], ext["info"]["input"]["pathParams"]}
    {"/users/:userId", %{"userId" => "123"}}

    iex> ext = X402.Extensions.Bazaar.build_extension(
    ...>   tool_name: "financial_analysis",
    ...>   input_schema: %{"type" => "object", "properties" => %{"ticker" => %{"type" => "string"}}, "required" => ["ticker"]}
    ...> )
    iex> ext["info"]["input"]["type"]
    "mcp"
    iex> ext["info"]["input"]["toolName"]
    "financial_analysis"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
