> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# list_products

> Read seller product offers with structured discovery criteria.

`list_products` is the side-effect-free entry point to the compact product lifecycle. Each invocation completes synchronously, while account-level wholesale feed webhooks keep long-lived buyer mirrors current without polling. It returns products only—never proposals. Buyers can purchase listed products through [`buy_products`](/dist/docs/3.0.23/media-buy/task-reference/buy_products) or pass returned `product_id` values to [`request_proposals`](/dist/docs/3.0.23/media-buy/task-reference/request_proposals) for seller planning.

**Request schema:** [`/schemas/3.0.23/media-buy/list-products-request.json`](https://adcontextprotocol.org/schemas/3.0.23/media-buy/list-products-request.json)

```json theme={null}
{
  "brand": { "domain": "acmeoutdoor.example" },
  "criteria": {
    "offer_filters": {
      "channels": ["olv"],
      "pricing_currencies": ["USD"]
    },
    "targeting_overlay": {
      "geo_countries": ["US"]
    },
    "required_overlay_support": {
      "geo_metros": { "systems": ["nielsen_dma"] }
    }
  },
  "max_results": 25
}
```

`brand` is an identity-only key. Sellers resolve its canonical manifest from the domain; callers do not send logos, colors, or governance overrides in this request. Catalog ingestion belongs to [`sync_catalogs`](/dist/docs/3.0.23/media-buy/task-reference/sync_catalogs); discovery accepts only a compact catalog selection under `criteria.catalog`.

`offer_filters` select commercial offers. `targeting_overlay` instead constrains the inventory or impressions that may deliver, so every returned price and forecast must account for the effective overlay. `required_overlay_support` asks for targeting dimensions the buyer can select independently on packages later; it does not request one product per metro or guarantee availability for every future value.

When a product can honor the overlay exactly, it does not echo the request. A product that proposes a different executable overlay returns sparse `targeting_resolution.modifications`; selecting that configured product accepts those disclosed changes.

## Response

Every response has an explicit `outcome`:

| Field             | Type       | Description                                                                                                                                |
| ----------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `outcome`         | string     | `listed` when the response contains a product page; `unchanged` when a conditional request matches the current feed.                       |
| `products`        | Product\[] | Product offers for this page. Required for `listed`, even when the array is empty; omitted for `unchanged`.                                |
| `next_cursor`     | string     | Cursor for the next page. Omitted on the final page. Pass it as `cursor` in the next request.                                              |
| `feed_version`    | string     | Opaque version of the selected offer feed. Pass it as `if_feed_version` on a later request to avoid downloading an unchanged feed.         |
| `pricing_version` | string     | Opaque version of the selected pricing layer. Pass it as `if_pricing_version` together with `if_feed_version` when checking both versions. |
| `cache_scope`     | string     | Whether the version applies to the seller's `public` feed or an `account`-specific overlay.                                                |
| `incomplete`      | object\[]  | Optional details about portions of the response the seller could not complete.                                                             |

A product page can include continuation and cache state:

```json theme={null}
{
  "outcome": "listed",
  "products": [],
  "next_cursor": "page_2",
  "feed_version": "feed_2026_08_10_1",
  "pricing_version": "pricing_42",
  "cache_scope": "account"
}
```

To check whether the same selection has changed, repeat the request with `if_feed_version`. Include `if_pricing_version` only when `if_feed_version` is also present:

```json theme={null}
{
  "brand": { "domain": "acmeoutdoor.example" },
  "criteria": {
    "offer_filters": {
      "channels": ["olv"],
      "pricing_currencies": ["USD"]
    },
    "targeting_overlay": {
      "geo_countries": ["US"]
    }
  },
  "if_feed_version": "feed_2026_08_10_1",
  "if_pricing_version": "pricing_42"
}
```

If the selected feed and pricing layer still match, the seller can omit `products`:

```json theme={null}
{
  "outcome": "unchanged",
  "feed_version": "feed_2026_08_10_1",
  "pricing_version": "pricing_42",
  "cache_scope": "account"
}
```

## Wholesale feed webhooks

Buyers maintaining a wholesale product mirror register `product.*` and `wholesale_feed.bulk_change` subscribers through `sync_accounts.accounts[].notification_configs[]`. Registration is account-level and durable; `push_notification_config` on an individual `list_products` request does not create that subscription.

The normal mirror flow is:

1. Call `list_products` to bootstrap the mirror and store `feed_version` plus `cache_scope`.
2. Register the durable account subscriber through [`sync_accounts`](/dist/docs/3.0.23/accounts/tasks/sync_accounts).
3. Apply the webhook's `canonical_product` or `canonical_pricing_options` replacement payload directly.
4. On a gap, call `list_products` with the mirror's last successfully applied `feed_version`. On an uncertain or bulk-change repair, omit the conditional token and replace the mirror from a complete read.

The webhook version describes post-change state; it is not the conditional token for a stale mirror. Conditional reads are a repair path, not a requirement to poll while webhooks are healthy.
