> ## 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.

# get_products

> get_products task — discover advertising inventory in AdCP using natural language campaign briefs or structured filters. Returns matched products with pricing and formats.

Discover available advertising products based on campaign requirements using natural language briefs or structured filters.

**Authentication**: Optional (returns limited results without credentials)

**Response Time**: \~60 seconds (AI inference with back-end systems)

**Request Schema**: [`/schemas/v2/media-buy/get-products-request.json`](https://adcontextprotocol.org/schemas/v2/media-buy/get-products-request.json)
**Response Schema**: [`/schemas/v2/media-buy/get-products-response.json`](https://adcontextprotocol.org/schemas/v2/media-buy/get-products-response.json)

## Quick Start

Discover products with a natural language brief:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';
  import { GetProductsResponseSchema } from '@adcp/client';

  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Premium athletic footwear with innovative cushioning',
    brand: {
      domain: 'acmecorp.com'
    }
  });

  if (!result.success) {
    throw new Error(`Request failed: ${result.error}`);
  }

  // Validate response against schema
  const validated = GetProductsResponseSchema.parse(result.data);
  console.log(`Found ${validated.products.length} products`);

  // Access validated product fields
  for (const product of validated.products) {
    console.log(`- ${product.name} (${product.delivery_type})`);
    console.log(`  Formats: ${product.format_ids.map(f => f.id).join(', ')}`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_products():
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Premium athletic footwear with innovative cushioning',
          brand={
              'domain': 'acmecorp.com'
          }
      )
      print(f"Found {len(result.products)} products")

  asyncio.run(discover_products())
  ```

  ```bash CLI theme={null}
  uvx adcp \
    https://test-agent.adcontextprotocol.org/mcp \
    get_products \
    '{"buying_mode":"brief","brief":"Premium athletic footwear with innovative cushioning","brand":{"domain":"acmecorp.com"}}' \
    --auth 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ
  ```
</CodeGroup>

### Using Structured Filters

You can also use structured filters instead of (or in addition to) a brief. In `brief` mode, filters act as hard constraints on top of the publisher's curation — the brief describes intent, filters enforce requirements:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  const result = await testAgent.getProducts({
    buying_mode: 'wholesale',
    brand: {
      domain: 'acmecorp.com'
    },
    filters: {
      format_types: ['video'],
      delivery_type: 'guaranteed',
      standard_formats_only: true
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} guaranteed video products`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_with_filters():
      result = await test_agent.simple.get_products(
          buying_mode='wholesale',
          brand={
              'domain': 'acmecorp.com'
          },
          filters={
              'format_types': ['video'],
              'delivery_type': 'guaranteed',
              'standard_formats_only': True
          }
      )
      print(f"Found {len(result.products)} guaranteed video products")

  asyncio.run(discover_with_filters())
  ```
</CodeGroup>

## Request Parameters

| Parameter            | Type                                               | Required    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| -------------------- | -------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `buying_mode`        | string                                             | Yes         | `"brief"`, `"wholesale"`, or `"refine"`. `"brief"`: publisher curates products from the brief. `"wholesale"`: raw inventory for buyer-directed targeting, `brief` must not be provided. `"refine"`: iterate on products and proposals from a previous response using the `refine` array of change requests. v3 clients MUST include `buying_mode`. Sellers receiving requests from pre-v3 clients without `buying_mode` SHOULD default to `"brief"`. |
| `brief`              | string                                             | Conditional | Natural language description of campaign requirements. Required when `buying_mode` is `"brief"`. Must not be provided when `buying_mode` is `"wholesale"` or `"refine"`.                                                                                                                                                                                                                                                                             |
| `refine`             | [Refine\[\]](#refine-array)                        | Conditional | Array of change requests for iterating on products and proposals. Required when `buying_mode` is `"refine"`. Must not be provided when `buying_mode` is `"brief"` or `"wholesale"`. See [Refine array](#refine-array) below.                                                                                                                                                                                                                         |
| `brand`              | BrandRef                                           | No          | Brand reference (domain + optional brand\_id). Resolved to full identity at execution time.                                                                                                                                                                                                                                                                                                                                                          |
| `account`            | AccountRef                                         | No          | Account reference for account-specific pricing. Returns products with pricing from this account's rate card.                                                                                                                                                                                                                                                                                                                                         |
| `buyer_campaign_ref` | string                                             | No          | Buyer's campaign label for CRM and ad server correlation (e.g., `"NovaDrink_Meals_Q2"`). Groups related discovery and buy operations.                                                                                                                                                                                                                                                                                                                |
| `catalog`            | [Catalog](/dist/docs/3.0.0-rc.2/creative/catalogs) | No          | Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Requires `brand`. See [Catalog discovery](#catalog-discovery) below.                                                                                                                                                                                                                                   |
| `filters`            | Filters                                            | No          | Structured filters (see below)                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `property_list`      | PropertyListRef                                    | No          | \[AdCP 3.0] Reference to a property list for filtering. See [Property Lists](/dist/docs/3.0.0-rc.2/governance/property/tasks/property_lists)                                                                                                                                                                                                                                                                                                         |
| `pagination`         | PaginationRequest                                  | No          | Cursor-based pagination for large product catalogs (see below)                                                                                                                                                                                                                                                                                                                                                                                       |
| `time_budget`        | Duration                                           | No          | Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing. Example: `{"interval": 30, "unit": "seconds"}`.                                                                                                                              |

### Filters Object

| Parameter               | Type        | Description                                                                                                                                                                    |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `delivery_type`         | string      | Filter by `"guaranteed"` or `"non_guaranteed"`                                                                                                                                 |
| `is_fixed_price`        | boolean     | Filter for fixed price vs auction products                                                                                                                                     |
| `format_types`          | string\[]   | *(uses deprecated FormatCategory)* Filter by format types (e.g., `["video", "display"]`). Consider filtering by `format_ids` instead for precise matching.                     |
| `format_ids`            | FormatID\[] | Filter by specific format IDs. **Recommended over `format_types`.**                                                                                                            |
| `standard_formats_only` | boolean     | Only return products accepting IAB standard formats                                                                                                                            |
| `min_exposures`         | integer     | Minimum exposures needed for measurement validity                                                                                                                              |
| `start_date`            | string      | Campaign start date in ISO 8601 format (YYYY-MM-DD) for availability checks                                                                                                    |
| `end_date`              | string      | Campaign end date in ISO 8601 format (YYYY-MM-DD) for availability checks                                                                                                      |
| `budget_range`          | object      | Budget range to filter appropriate products (see Budget Range Object below)                                                                                                    |
| `countries`             | string\[]   | Filter by target countries using ISO 3166-1 alpha-2 codes (case-insensitive, e.g., `["US", "CA", "GB"]`)                                                                       |
| `channels`              | string\[]   | Filter by advertising channels (e.g., `["display", "ctv", "social", "streaming_audio"]`). See [Media Channel Taxonomy](/dist/docs/3.0.0-rc.2/reference/media-channel-taxonomy) |

### Budget Range Object

| Parameter  | Type   | Required | Description                                              |
| ---------- | ------ | -------- | -------------------------------------------------------- |
| `currency` | string | Yes      | ISO 4217 currency code (e.g., `"USD"`, `"EUR"`, `"GBP"`) |
| `min`      | number | No\*     | Minimum budget amount                                    |
| `max`      | number | No\*     | Maximum budget amount                                    |

\*At least one of `min` or `max` must be specified.

### Refine array

The `refine` array is a list of change requests. Each entry declares a `scope` and what the buyer is asking for. At least one entry is required. The seller considers all entries together when composing the response, and replies to each via `refinement_applied`.

Each entry is a discriminated union on `scope`:

#### scope: "request"

| Field   | Type   | Required | Description                                                                                                       |
| ------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `scope` | string | Yes      | `"request"`                                                                                                       |
| `ask`   | string | Yes      | Direction for the selection as a whole (e.g., `"more video options"`, `"suggest how to combine these products"`). |

#### scope: "product"

| Field    | Type   | Required | Description                                                                                                                                                                                                              |
| -------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `scope`  | string | Yes      | `"product"`                                                                                                                                                                                                              |
| `id`     | string | Yes      | Product ID from a previous `get_products` response                                                                                                                                                                       |
| `action` | string | Yes      | `"include"`: return this product with updated pricing and data. `"omit"`: exclude from the response. `"more_like_this"`: find similar products (the original is also returned).                                          |
| `ask`    | string | No       | What the buyer is asking for. For `"include"`: specific changes (e.g., `"add 16:9 format"`). For `"more_like_this"`: what "similar" means (e.g., `"same audience but video format"`). Ignored when `action` is `"omit"`. |

#### scope: "proposal"

| Field    | Type   | Required | Description                                                                                                                          |
| -------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `scope`  | string | Yes      | `"proposal"`                                                                                                                         |
| `id`     | string | Yes      | Proposal ID from a previous `get_products` response                                                                                  |
| `action` | string | Yes      | `"include"`: return with updated allocations and pricing. `"omit"`: exclude from the response.                                       |
| `ask`    | string | No       | What the buyer is asking for (e.g., `"shift more budget toward video"`, `"reduce total by 10%"`). Ignored when `action` is `"omit"`. |

### refinement\_applied (response)

When the seller receives a `refine` array, the response includes `refinement_applied` — an array matched by position. Each entry reports whether the ask was fulfilled:

| Field    | Type   | Required | Description                                                                                  |
| -------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `scope`  | string | No       | Echoes the scope from the corresponding `refine` entry for cross-validation.                 |
| `id`     | string | No       | Echoes the id from the corresponding `refine` entry (for product and proposal scopes).       |
| `status` | string | Yes      | `"applied"`: ask fulfilled. `"partial"`: partially fulfilled. `"unable"`: could not fulfill. |
| `notes`  | string | No       | Seller explanation. Recommended when status is `"partial"` or `"unable"`.                    |

### Catalog discovery

Pass a `catalog` to find advertising products that can promote your catalog items. The seller matches your catalog items against its inventory and returns products where matches exist. Supports all catalog types — a product catalog finds sponsored product slots, a job catalog finds job ad products, a flight catalog finds dynamic travel ads.

The `catalog` field uses the same [Catalog](/dist/docs/3.0.0-rc.2/creative/catalogs) object used throughout AdCP. You can reference a synced catalog by `catalog_id`, provide inline items, or use selectors to filter:

| Field        | Type        | Description                                                                     |
| ------------ | ----------- | ------------------------------------------------------------------------------- |
| `type`       | CatalogType | Catalog type (required) — `product`, `job`, `hotel`, `flight`, `offering`, etc. |
| `catalog_id` | string      | Reference a synced catalog by ID                                                |
| `ids`        | string\[]   | Filter to specific item IDs                                                     |
| `gtins`      | string\[]   | Filter by GTIN for cross-retailer matching (product type only)                  |
| `tags`       | string\[]   | Filter by tags (OR logic)                                                       |
| `category`   | string      | Filter by category                                                              |
| `query`      | string      | Natural language filter                                                         |

Products in the response include `catalog_types` (what catalog types they support) and `catalog_match` (which items matched).

## Response

Returns an array of `products` and optionally `proposals` and `shows`.

### Products Array

| Field                  | Type                 | Description                                                                                                                                                                                                              |
| ---------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `product_id`           | string               | Unique product identifier                                                                                                                                                                                                |
| `name`                 | string               | Human-readable product name                                                                                                                                                                                              |
| `description`          | string               | Detailed product description                                                                                                                                                                                             |
| `publisher_properties` | PublisherProperty\[] | Array of publisher entries, each with `publisher_domain` and either `property_ids` or `property_tags`                                                                                                                    |
| `format_ids`           | FormatID\[]          | Supported creative format IDs                                                                                                                                                                                            |
| `delivery_type`        | string               | `"guaranteed"` or `"non_guaranteed"`                                                                                                                                                                                     |
| `delivery_measurement` | DeliveryMeasurement  | (Optional) How delivery is measured (impressions, views, etc.)                                                                                                                                                           |
| `pricing_options`      | PricingOption\[]     | Available pricing models (CPM, CPCV, etc.). Auction options may include `floor_price` and optional `price_guidance`. Bid-based auction models (CPM, vCPM, CPC, CPCV, CPV) may also include optional `max_bid` (boolean). |
| `brief_relevance`      | string               | Why this product matches the brief (when brief provided)                                                                                                                                                                 |

### Shows Array (Optional)

When products reference shows via `show_ids`, the response includes a top-level `shows` array with full show objects. Only shows referenced by the returned products are included. Under pagination, each page includes all shows needed for that page's products.

| Field            | Type                | Description                                                                |
| ---------------- | ------------------- | -------------------------------------------------------------------------- |
| `show_id`        | string              | Seller-scoped identifier for the show                                      |
| `name`           | string              | Human-readable show name                                                   |
| `genre`          | string\[]           | Genre tags (IAB Content Taxonomy 3.0 IDs when `genre_taxonomy` is present) |
| `content_rating` | ContentRating       | Baseline content rating (system + rating value)                            |
| `talent`         | Talent\[]           | Hosts, recurring cast, creators with optional `brand_url` links            |
| `distribution`   | ShowDistribution\[] | Where the show is distributed, with platform-specific identifiers          |

See [Shows and episodes](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/shows-and-episodes) for full documentation.

### Proposals Array (Optional)

Publishers may return proposals alongside products - structured media plans with budget allocations. See [Proposals](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/media-products#proposals) for details.

| Field                   | Type                 | Description                                                                                                                                                                                              |
| ----------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `proposal_id`           | string               | Unique identifier for executing this proposal via `create_media_buy`                                                                                                                                     |
| `name`                  | string               | Human-readable name for the media plan                                                                                                                                                                   |
| `allocations`           | ProductAllocation\[] | Budget allocations across products (percentages must sum to 100). Each allocation may include optional `start_time` and `end_time` for per-flight scheduling.                                            |
| `forecast`              | DeliveryForecast     | Aggregate delivery forecast for the proposal. Contains forecast points with metric ranges. See [Delivery Forecasts](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/media-products#delivery-forecasts) |
| `total_budget_guidance` | object               | Optional min/recommended/max budget guidance                                                                                                                                                             |
| `brief_alignment`       | string               | How this proposal addresses the campaign brief                                                                                                                                                           |
| `expires_at`            | string               | ISO 8601 timestamp when this proposal expires                                                                                                                                                            |

### Pagination

For large product catalogs, use cursor-based pagination:

| Request Parameter        | Type    | Description                                    |
| ------------------------ | ------- | ---------------------------------------------- |
| `pagination.max_results` | integer | Maximum products per page (1-100, default: 50) |
| `pagination.cursor`      | string  | Cursor from previous response for next page    |

| Response Field           | Type    | Description                                                       |
| ------------------------ | ------- | ----------------------------------------------------------------- |
| `pagination.has_more`    | boolean | Whether more products are available                               |
| `pagination.cursor`      | string  | Cursor to pass for the next page                                  |
| `pagination.total_count` | integer | Total matching products (optional, not all backends support this) |

Pagination is optional. When omitted, the server returns all results (or a server-chosen default page). When the response includes `pagination.has_more: true`, pass `pagination.cursor` in the next request to get the next page.

### Response Metadata

| Field                   | Type                                                 | Description                                                                                                                                                                                                                                                 |
| ----------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `property_list_applied` | boolean                                              | \[AdCP 3.0] `true` if the agent filtered products based on the provided `property_list`. Absent or `false` if not provided or not supported.                                                                                                                |
| `catalog_applied`       | boolean                                              | `true` if the seller filtered results based on the provided `catalog`. Absent or `false` if no catalog was provided or the seller does not support catalog matching.                                                                                        |
| `refinement_applied`    | [RefinementResult\[\]](#refinement_applied-response) | Seller acknowledgment of each `refine` entry, matched by position. Only present when `buying_mode` is `"refine"`. See [refinement\_applied](#refinement_applied-response) above.                                                                            |
| `incomplete`            | [IncompleteEntry\[\]](#incomplete-array)             | Declares what the seller could not finish within the `time_budget` or due to internal limits. Each entry identifies a scope with a human-readable explanation. Absent when the response is fully complete. See [incomplete array](#incomplete-array) below. |

### incomplete array

When the seller cannot complete all work within the `time_budget` (or due to its own internal limits), the response includes `incomplete` — an array declaring what is missing. Buyers can use `estimated_wait` to decide whether to retry with a larger budget.

| Field            | Type     | Required | Description                                                                                                                                                                                                                                             |
| ---------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scope`          | string   | Yes      | `"products"`: not all inventory sources were searched. `"pricing"`: products returned but pricing is absent or unconfirmed. `"forecast"`: products returned but forecast data is absent. `"proposals"`: proposals were not generated or are incomplete. |
| `description`    | string   | Yes      | Human-readable explanation of what is missing and why.                                                                                                                                                                                                  |
| `estimated_wait` | Duration | No       | How much additional time would resolve this scope.                                                                                                                                                                                                      |

**See schema for complete field list**: [`get-products-response.json`](https://adcontextprotocol.org/schemas/v2/media-buy/get-products-response.json)

## Common Scenarios

### Time-budgeted discovery

Declare a time budget when you need fast results and can accept partial data. The seller returns what it can within the budget and declares what is incomplete:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'CTV and display for brand awareness',
    brand: {
      domain: 'acmecorp.com'
    },
    time_budget: {
      interval: 10,
      unit: 'seconds'
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} products`);

    if (result.data.incomplete) {
      for (const entry of result.data.incomplete) {
        console.log(`Incomplete: ${entry.scope} — ${entry.description}`);
        if (entry.estimated_wait) {
          console.log(`  Would resolve in ${entry.estimated_wait.interval} ${entry.estimated_wait.unit}`);
        }
      }
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_with_time_budget():
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='CTV and display for brand awareness',
          brand={
              'domain': 'acmecorp.com'
          },
          time_budget={
              'interval': 10,
              'unit': 'seconds'
          }
      )
      print(f"Found {len(result.products)} products")

      for entry in result.get('incomplete', []):
          print(f"Incomplete: {entry['scope']} — {entry['description']}")
          if 'estimated_wait' in entry:
              wait = entry['estimated_wait']
              print(f"  Would resolve in {wait['interval']} {wait['unit']}")

  asyncio.run(discover_with_time_budget())
  ```
</CodeGroup>

A response with incomplete data — products are returned but some scopes are missing:

```json test=false theme={null}
{
  "products": [
    {
      "product_id": "prog-display-ros",
      "name": "Programmatic Display — Run of Site",
      "delivery_type": "non_guaranteed",
      "pricing_options": [{ "pricing_option_id": "cpm-ros", "pricing_model": "cpm", "currency": "USD", "fixed_price": 12.00 }]
    }
  ],
  "incomplete": [
    {
      "scope": "products",
      "description": "Premium inventory not searched — requires publisher approval",
      "estimated_wait": { "interval": 60, "unit": "minutes" }
    },
    {
      "scope": "forecast",
      "description": "Forecast model did not complete within budget",
      "estimated_wait": { "interval": 45, "unit": "seconds" }
    }
  ]
}
```

### Standard Catalog Discovery

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // wholesale mode: buyer applies their own audiences, no publisher curation
  const result = await testAgent.getProducts({
    buying_mode: 'wholesale',
    brand: {
      domain: 'acmecorp.com'
    },
    filters: {
      delivery_type: 'non_guaranteed'
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} standard catalog products`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_standard_catalog():
      # wholesale mode: buyer applies their own audiences, no publisher curation
      result = await test_agent.simple.get_products(
          buying_mode='wholesale',
          brand={
              'domain': 'acmecorp.com'
          },
          filters={
              'delivery_type': 'non_guaranteed'
          }
      )
      print(f"Found {len(result.products)} standard catalog products")

  asyncio.run(discover_standard_catalog())
  ```
</CodeGroup>

### Multi-Format Discovery

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Find products supporting both video and display
  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Brand awareness campaign with video and display',
    brand: {
      domain: 'acmecorp.com'
    },
    filters: {
      format_types: ['video', 'display']
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} products supporting video and display`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_multi_format():
      # Find products supporting both video and display
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Brand awareness campaign with video and display',
          brand={
              'domain': 'acmecorp.com'
          },
          filters={
              'format_types': ['video', 'display']
          }
      )
      print(f"Found {len(result.products)} products supporting video and display")

  asyncio.run(discover_multi_format())
  ```
</CodeGroup>

### Budget and Date Filtering

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Find products within budget and date range for specific countries and channels
  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Q2 campaign for athletic footwear in North America',
    brand: {
      domain: 'acmecorp.com'
    },
    filters: {
      start_date: '2025-04-01',
      end_date: '2025-06-30',
      budget_range: {
        min: 50000,
        max: 100000,
        currency: 'USD'
      },
      countries: ['US', 'CA'],
      channels: ['display', 'ctv', 'podcast'],
      delivery_type: 'guaranteed'
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} products for Q2 within budget`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_with_budget_and_dates():
      # Find products within budget and date range for specific countries and channels
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Q2 campaign for athletic footwear in North America',
          brand={
              'domain': 'acmecorp.com'
          },
          filters={
              'start_date': '2025-04-01',
              'end_date': '2025-06-30',
              'budget_range': {
                  'min': 50000,
                  'max': 100000,
                  'currency': 'USD'
              },
              'countries': ['US', 'CA'],
              'channels': ['display', 'ctv', 'podcast'],
              'delivery_type': 'guaranteed'
          }
      )
      print(f"Found {len(result.products)} products for Q2 within budget")

  asyncio.run(discover_with_budget_and_dates())
  ```
</CodeGroup>

### Property Tag Resolution

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Get products with property tags
  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Sports content',
    brand: {
      domain: 'acmecorp.com'
    }
  });

  if (result.success && result.data) {
    // Products with property_tags in publisher_properties represent large networks
    // Use get_adcp_capabilities to discover the agent's portfolio
    const productsWithTags = result.data.products.filter(p =>
      p.publisher_properties?.some(pub => pub.property_tags && pub.property_tags.length > 0)
    );
    console.log(`${productsWithTags.length} products use property tags (large networks)`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_property_tags():
      # Get products with property tags
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Sports content',
          brand={
              'domain': 'acmecorp.com'
          }
      )

      # Products with property_tags in publisher_properties represent large networks
      # Use get_adcp_capabilities to discover the agent's portfolio
      products_with_tags = [p for p in result.products
          if any(pub.get('property_tags') for pub in p.get('publisher_properties', []))]
      print(f"{len(products_with_tags)} products use property tags (large networks)")

  asyncio.run(discover_property_tags())
  ```
</CodeGroup>

### Guaranteed Delivery Products

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Find guaranteed delivery products for measurement
  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Guaranteed delivery for lift study',
    brand: {
      domain: 'acmecorp.com'
    },
    filters: {
      delivery_type: 'guaranteed',
      min_exposures: 100000
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} guaranteed products with 100k+ exposures`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_guaranteed():
      # Find guaranteed delivery products for measurement
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Guaranteed delivery for lift study',
          brand={
              'domain': 'acmecorp.com'
          },
          filters={
              'delivery_type': 'guaranteed',
              'min_exposures': 100000
          }
      )
      print(f"Found {len(result.products)} guaranteed products with 100k+ exposures")

  asyncio.run(discover_guaranteed())
  ```
</CodeGroup>

### Standard Formats Only

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Find products that only accept IAB standard formats
  const result = await testAgent.getProducts({
    buying_mode: 'wholesale',
    brand: {
      domain: 'acmecorp.com'
    },
    filters: {
      standard_formats_only: true
    }
  });

  if (result.success && result.data) {
    console.log(`Found ${result.data.products.length} products with standard formats only`);
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_standard_formats():
      # Find products that only accept IAB standard formats
      result = await test_agent.simple.get_products(
          buying_mode='wholesale',
          brand={
              'domain': 'acmecorp.com'
          },
          filters={
              'standard_formats_only': True
          }
      )
      print(f"Found {len(result.products)} products with standard formats only")

  asyncio.run(discover_standard_formats())
  ```
</CodeGroup>

### Catalog-driven discovery

Use `catalog` with a brand to discover advertising products that can promote your catalog items. The seller matches your items against its inventory and returns products where matches exist:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Discover retail media products for specific catalog items
  const result = await testAgent.getProducts({
    buying_mode: 'wholesale',
    brand: {
      domain: 'acmecorp.com'
    },
    catalog: {
      type: 'product',
      tags: ['ketchup', 'organic'],
      category: 'food/condiments'
    },
    filters: {
      channels: ['retail_media']
    }
  });

  if (result.success && result.data) {
    if (result.data.catalog_applied) {
      console.log(`Found ${result.data.products.length} products with catalog matches`);
    } else {
      console.log('Seller does not support catalog matching');
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_commerce_products():
      # Discover retail media products for specific catalog items
      result = await test_agent.simple.get_products(
          buying_mode='wholesale',
          brand={
              'domain': 'acmecorp.com'
          },
          catalog={
              'type': 'product',
              'tags': ['ketchup', 'organic'],
              'category': 'food/condiments'
          },
          filters={
              'channels': ['retail_media']
          }
      )
      if result.get('catalog_applied'):
          print(f"Found {len(result.products)} products with catalog matches")
      else:
          print("Seller does not support catalog matching")

  asyncio.run(discover_commerce_products())
  ```

  ```bash CLI theme={null}
  uvx adcp \
    https://test-agent.adcontextprotocol.org/mcp \
    get_products \
    '{"buying_mode":"wholesale","brand":{"domain":"acmecorp.com"},"catalog":{"type":"product","tags":["ketchup","organic"],"category":"food/condiments"},"filters":{"channels":["retail_media"]}}' \
    --auth 1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ
  ```
</CodeGroup>

You can also use GTIN matching, reference a synced catalog, or discover products for other catalog types:

```json theme={null}
{
  "catalog": {
    "type": "product",
    "gtins": ["00013000006040", "00013000006057"]
  }
}
```

```json theme={null}
{
  "catalog": {
    "catalog_id": "gmc-primary",
    "type": "product"
  }
}
```

```json theme={null}
{
  "catalog": {
    "type": "job",
    "catalog_id": "chef-vacancies"
  }
}
```

### Property List Filtering

<Info>
  **AdCP 3.0** - Property list filtering requires governance agent support.
</Info>

Filter products to only those available on properties in your approved list:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/client/testing';

  // Filter products by property list from governance agent
  const result = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Brand-safe inventory for family brand',
    brand: {
      domain: 'acmecorp.com'
    },
    property_list: {
      agent_url: 'https://governance.example.com',
      list_id: 'pl_brand_safe_2024'
    }
  });

  if (result.success && result.data) {
    // Check if filtering was actually applied
    if (result.data.property_list_applied) {
      console.log(`Found ${result.data.products.length} products on approved properties`);
    } else {
      console.log('Agent does not support property list filtering');
      console.log(`Found ${result.data.products.length} products (unfiltered)`);
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent

  async def discover_with_property_list():
      # Filter products by property list from governance agent
      result = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Brand-safe inventory for family brand',
          brand={
              'domain': 'acmecorp.com'
          },
          property_list={
              'agent_url': 'https://governance.example.com',
              'list_id': 'pl_brand_safe_2024'
          }
      )

      # Check if filtering was actually applied
      if result.get('property_list_applied'):
          print(f"Found {len(result['products'])} products on approved properties")
      else:
          print("Agent does not support property list filtering")
          print(f"Found {len(result['products'])} products (unfiltered)")

  asyncio.run(discover_with_property_list())
  ```
</CodeGroup>

**Note**: If `property_list_applied` is absent or `false`, the sales agent did not filter products. This can happen if:

* The agent doesn't support property governance features
* The agent couldn't access the property list
* The property list had no effect on the available inventory

#### Property Targeting Behavior

Products have a `property_targeting_allowed` flag that affects filtering:

* **`property_targeting_allowed: false` (default)**: Product is "all or nothing" - excluded unless your list contains all of its properties
* **`property_targeting_allowed: true`**: Product is included if there's any intersection between its properties and your list

This allows publishers to offer run-of-network products that can't be cherry-picked alongside flexible inventory that buyers can filter.

See [Property Targeting](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/media-products#property-targeting) for more details and [Property Governance](/dist/docs/3.0.0-rc.2/governance/property/specification) for more on property lists.

## Refinement

After initial discovery, use `buying_mode: "refine"` to iterate on specific products and proposals. The `refine` array is a list of change requests — each entry declares a scope and what the buyer is asking for. The seller returns updated products with revised pricing and configurations, plus `refinement_applied` acknowledging each ask. See [Refinement](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/refinement) for the full guide.

### Refine products

Each product entry declares an explicit action. Use `scope: "request"` for set-level direction. Filters contain absolute values (not deltas), so `budget_range` is the new target budget:

```json test=false theme={null}
{
  "buying_mode": "refine",
  "refine": [
    { "scope": "request", "ask": "good selection but I want more video options and less display" },
    { "scope": "product", "id": "prod_premium_video", "action": "include", "ask": "add 16:9 format option" },
    { "scope": "product", "id": "prod_display_run_of_site", "action": "omit" },
    { "scope": "product", "id": "prod_native_feed", "action": "more_like_this", "ask": "same audience but video format" }
  ],
  "filters": {
    "start_date": "2026-04-01",
    "end_date": "2026-04-30",
    "budget_range": { "min": 200000, "max": 200000, "currency": "USD" }
  }
}
```

### Refine a proposal

Reference proposals by ID to request adjustments:

```json test=false theme={null}
{
  "buying_mode": "refine",
  "refine": [
    { "scope": "product", "id": "prod_premium_video", "action": "include" },
    { "scope": "product", "id": "prod_display_run_of_site", "action": "include" },
    { "scope": "proposal", "id": "prop_awareness_q2", "action": "include", "ask": "shift more budget toward video, reduce display allocation" }
  ],
  "filters": {
    "start_date": "2026-04-01",
    "end_date": "2026-05-31",
    "budget_range": { "min": 300000, "max": 300000, "currency": "USD" }
  }
}
```

### Find similar products

Use `more_like_this` to discover products similar to ones you like. The seller returns the original product plus additional options:

```json test=false theme={null}
{
  "buying_mode": "refine",
  "refine": [
    { "scope": "product", "id": "prod_premium_video", "action": "more_like_this", "ask": "same premium audience but different formats" }
  ]
}
```

### Proposals in refine mode

Sellers MAY return proposals alongside refined products, even when the buyer did not include proposal entries. For example, a buyer refining three products may receive those products back with updated pricing *and* a proposal suggesting how to combine them.

Key points:

* **Proposals are not guaranteed.** Sellers are not required to generate proposals in refine mode. Allocation and campaign optimization are primarily orchestrator (buyer-side agent) responsibilities.
* **Signal interest via a request-level ask.** Include `{ "scope": "request", "ask": "suggest how to combine these products" }` to indicate you'd welcome a proposal.
* **Unsolicited proposals can be refined or ignored.** If a seller returns a proposal you didn't request, you can refine it in a follow-up call, or simply ignore it and build packages manually via `create_media_buy`.

### Notes

* **`refine` is only valid in `refine` mode.** Requests that include this field in `brief` or `wholesale` mode are rejected with `INVALID_REQUEST`.
* **Proposals are ephemeral.** Proposals typically include an `expires_at` timestamp. After expiration, the seller returns `PROPOSAL_EXPIRED`. Re-discover with a new `brief` request to get a fresh proposal. Proposals without `expires_at` remain valid until the seller invalidates them.
* **Product IDs are stable catalog identifiers.** They reference the seller's inventory and are validated at request time. You can refine products from a previous call — even from a different brief — and the seller will confirm eligibility against the current request context. Custom products (`is_custom: true`) may have an `expires_at` timestamp, after which refinement returns `PRODUCT_NOT_FOUND`.

The response uses the same format as discovery, plus `refinement_applied` acknowledging each ask. Each product reflects the updated parameters — revised pricing options and any configuration changes the seller recommends based on the `refine` entries and filters. Call refine as many times as needed before committing to a `create_media_buy`.

### Client validation

Orchestrators building refinement workflows should validate requests before sending:

* **Non-empty refine**: The `refine` array requires at least one entry. An empty `[]` is rejected by schema validation.
* **Valid entries**: Each product entry requires `scope`, `id`, and `action` (`include`, `omit`, or `more_like_this`). Each proposal entry requires `scope`, `id`, and `action` (`include` or `omit`). Request-level entries require `scope` and `ask`.
* **Filters are absolute**: When refining, `filters` represent the complete target state, not a delta from the previous request. Always send the full filter set you want applied.

Client implementations should validate refinement requests against the [request schema](/dist/docs/3.0.0-rc.2/building/schemas-and-sdks) before sending.

## Error Handling

| Error Code          | Description                                                    | Resolution                                                          |
| ------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- |
| `AUTH_REQUIRED`     | Authentication needed for full catalog                         | Provide credentials via auth header                                 |
| `INVALID_REQUEST`   | Brief too long or malformed filters                            | Check request parameters                                            |
| `PRODUCT_NOT_FOUND` | One or more referenced product IDs are unknown or expired      | Remove invalid IDs and retry, or re-discover with a `brief` request |
| `PROPOSAL_EXPIRED`  | A referenced proposal ID has passed its `expires_at` timestamp | Re-discover with a new `brief` or `wholesale` request               |
| `POLICY_VIOLATION`  | Category blocked for advertiser                                | See policy response message for details                             |

### Authentication Comparison

See the difference between authenticated and unauthenticated access:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent, testAgentNoAuth } from '@adcp/client/testing';

  // WITH authentication - full catalog with pricing
  const fullCatalog = await testAgent.getProducts({
    buying_mode: 'brief',
    brief: 'Premium CTV inventory for brand awareness',
    brand: {
      domain: 'acmecorp.com'
    }
  });

  if (!fullCatalog.success) {
    throw new Error(`Failed to get products: ${fullCatalog.error}`);
  }

  console.log(`With auth: ${fullCatalog.data.products.length} products`);
  console.log(`First product pricing: ${fullCatalog.data.products[0].pricing_options.length} options`);

  // WITHOUT authentication - limited public catalog
  const publicCatalog = await testAgentNoAuth.getProducts({
    buying_mode: 'brief',
    brief: 'Premium CTV inventory for brand awareness',
    brand: {
      domain: 'acmecorp.com'
    }
  });

  if (!publicCatalog.success) {
    throw new Error(`Failed to get products: ${publicCatalog.error}`);
  }

  console.log(`Without auth: ${publicCatalog.data.products.length} products`);
  console.log(`First product pricing: ${publicCatalog.data.products[0].pricing_options?.length || 0} options`);
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent, test_agent_no_auth

  async def compare_auth():
      # WITH authentication - full catalog with pricing
      full_catalog = await test_agent.simple.get_products(
          buying_mode='brief',
          brief='Premium CTV inventory for brand awareness',
          brand={
              'domain': 'acmecorp.com'
          }
      )

      print(f"With auth: {len(full_catalog['products'])} products")
      print(f"First product pricing: {len(full_catalog['products'][0]['pricing_options'])} options")

      # WITHOUT authentication - limited public catalog
      public_catalog = await test_agent_no_auth.simple.get_products(
          buying_mode='brief',
          brief='Premium CTV inventory for brand awareness',
          brand={
              'domain': 'acmecorp.com'
          }
      )

      print(f"Without auth: {len(public_catalog['products'])} products")
      print(f"First product pricing: {len(public_catalog['products'][0].get('pricing_options', []))} options")

  asyncio.run(compare_auth())
  ```
</CodeGroup>

**Key Differences:**

* **Product Count**: Authenticated access returns more products, including private/custom offerings
* **Pricing Information**: Only authenticated requests receive detailed pricing options (CPM, CPCV, etc.)
* **Targeting Details**: Custom targeting capabilities may be restricted to authenticated users
* **Rate Limits**: Unauthenticated requests have lower rate limits

## Authentication Behavior

* **Without credentials**: Returns limited catalog (standard catalog products), no pricing, no custom offerings
* **With credentials**: Returns complete catalog with pricing and custom products

See [Authentication Guide](/dist/docs/3.0.0-rc.2/building/integration/authentication) for details.

## Asynchronous Operations

Most product searches complete immediately, but some scenarios require asynchronous processing. When this happens, you'll receive a status other than `completed` and can track progress through webhooks or polling.

### When Search Runs Asynchronously

Product search may require async processing in these situations:

* **Complex searches**: Searching across multiple inventory sources or custom curation
* **Needs clarification**: Your brief is vague and the system needs more information
* **Custom products**: Bespoke product packages that require human review

### Async Status Flow

<Tabs>
  <Tab title="MCP">
    #### Immediate Completion (Most Common)

    ```json theme={null}
    POST /api/mcp/call_tool

    {
      "name": "get_products",
      "arguments": {
        "buying_mode": "brief",
        "brief": "CTV inventory for sports audience",
        "brand": { "domain": "acmecorp.com" }
      }
    }

    Response (200 OK):
    {
      "status": "completed",
      "message": "Found 3 products matching your requirements",
      "products": [...]
    }
    ```

    #### Needs Clarification

    When the brief is unclear, the system asks for more details:

    ```json theme={null}
    Response (200 OK):
    {
      "status": "input-required",
      "message": "I need a bit more information. What's your budget range and campaign duration?",
      "task_id": "task_789",
      "context_id": "ctx_123",
      "reason": "CLARIFICATION_NEEDED",
      "partial_results": [],
      "suggestions": ["$50K-$100K", "1 month", "Q1 2024"]
    }
    ```

    Continue the conversation with the same `context_id`:

    ```json theme={null}
    POST /api/mcp/continue

    {
      "context_id": "ctx_123",
      "message": "Budget is $75K for a 3-week campaign in March"
    }

    Response (200 OK):
    {
      "status": "completed",
      "message": "Perfect! Found 5 products within your budget",
      "products": [...]
    }
    ```

    #### Complex Search (With Webhook)

    For searches requiring deep inventory analysis, configure a webhook:

    ```json theme={null}
    POST /api/mcp/call_tool

    {
      "name": "get_products",
      "arguments": {
        "buying_mode": "brief",
        "brief": "Premium inventory across all formats for luxury automotive brand",
        "brand": { "domain": "acmecorp.com" },
        "pushNotificationConfig": {
          "url": "https://buyer.com/webhooks/adcp/get_products",
          "authentication": {
            "schemes": ["Bearer"],
            "credentials": "secret_token_32_chars"
          }
        }
      }
    }

    Response (200 OK):
    {
      "status": "working",
      "message": "Searching premium inventory across display, video, and audio",
      "task_id": "task_456",
      "context_id": "ctx_123",
      "percentage": 10,
      "current_step": "searching_inventory"
    }

    // Later, webhook POST to https://buyer.com/webhooks/adcp/get_products
    {
      "task_id": "task_456",
      "task_type": "get_products",
      "status": "completed",
      "timestamp": "2025-01-22T10:30:00Z",
      "message": "Found 12 premium products across all formats",
      "result": {
        "products": [...]
      }
    }
    ```
  </Tab>

  <Tab title="A2A">
    #### Immediate Completion (Most Common)

    ```json theme={null}
    POST /api/a2a

    {
      "message": {
        "role": "user",
        "parts": [{
          "kind": "data",
          "data": {
            "skill": "get_products",
            "parameters": {
              "buying_mode": "brief",
              "brief": "CTV inventory for sports audience",
              "brand": { "domain": "acmecorp.com" }
            }
          }
        }]
      }
    }

    Response (200 OK):
    {
      "id": "task_123",
      "contextId": "ctx_456",
      "artifact": {
        "kind": "data",
        "data": {
          "products": [...]
        }
      },
      "status": {
        "state": "completed",
        "message": {
          "role": "agent",
          "parts": [{ "text": "Found 3 products matching your requirements" }]
        }
      }
    }
    ```

    #### Needs Clarification

    Real-time updates via SSE when clarification is needed:

    ```json theme={null}
    // Initial response
    {
      "id": "task_789",
      "contextId": "ctx_123",
      "status": {
        "state": "input-required",
        "message": {
          "role": "agent",
          "parts": [
            { "text": "I need a bit more information. What's your budget range and campaign duration?" },
            {
              "data": {
                "reason": "CLARIFICATION_NEEDED",
                "suggestions": ["$50K-$100K", "1 month", "Q1 2024"]
              }
            }
          ]
        }
      }
    }

    // Send follow-up
    POST /api/a2a

    {
      "contextId": "ctx_123",
      "message": {
        "role": "user",
        "parts": [{ "text": "Budget is $75K for a 3-week campaign in March" }]
      }
    }

    // SSE update: task completed
    {
      "id": "task_789",
      "contextId": "ctx_123",
      "artifact": {
        "kind": "data",
        "data": { "products": [...] }
      },
      "status": {
        "state": "completed",
        "message": {
          "role": "agent",
          "parts": [{ "text": "Perfect! Found 5 products within your budget" }]
        }
      }
    }
    ```

    #### Complex Search (With Webhook)

    Configure push notifications for long searches:

    ```json theme={null}
    POST /api/a2a

    {
      "message": {
        "role": "user",
        "parts": [{
          "kind": "data",
          "data": {
            "skill": "get_products",
            "parameters": {
              "buying_mode": "brief",
              "brief": "Premium inventory across all formats for luxury automotive brand",
              "brand": { "domain": "acmecorp.com" }
            }
          }
        }]
      },
      "pushNotificationConfig": {
        "url": "https://buyer.com/webhooks/a2a/get_products",
        "authentication": {
          "schemes": ["bearer"],
          "credentials": "secret_token_32_chars"
        }
      }
    }

    Response (200 OK):
    {
      "id": "task_456",
      "contextId": "ctx_789",
      "status": {
        "state": "working",
        "message": {
          "role": "agent",
          "parts": [
            { "text": "Searching premium inventory across display, video, and audio" },
            {
              "data": {
                "percentage": 10,
                "current_step": "searching_inventory"
              }
            }
          ]
        }
      }
    }

    // Later, webhook POST to https://buyer.com/webhooks/a2a/get_products
    {
      "id": "task_456",
      "contextId": "ctx_789",
      "artifact": {
        "kind": "data",
        "data": {
          "products": [...]
        }
      },
      "status": {
        "state": "completed",
        "message": {
          "role": "agent",
          "parts": [
            { "text": "Found 12 premium products across all formats" },
            {
              "data": {
                "products": [...]
              }
            }
          ]
        },
        "timestamp": "2025-01-22T10:30:00Z"
      }
    }
    ```
  </Tab>
</Tabs>

### Status Overview

| Status           | When It Happens                   | What You Do                          |
| ---------------- | --------------------------------- | ------------------------------------ |
| `completed`      | Search finished successfully      | Process the product results          |
| `input-required` | Need clarification on the brief   | Answer the question and continue     |
| `working`        | Searching across multiple sources | Wait for webhook or poll for updates |
| `submitted`      | Custom curation queued            | Wait for webhook notification        |
| `failed`         | Search couldn't complete          | Check error message, adjust brief    |

**Note:** For the complete status list see [Task Lifecycle](/dist/docs/3.0.0-rc.2/building/implementation/task-lifecycle).

**Most searches complete immediately.** Async processing is only needed for complex scenarios or when the system needs your input.

## Next Steps

After discovering products:

1. **Review Options**: Compare products, pricing, and targeting capabilities
2. **Create Media Buy**: Use [`create_media_buy`](/dist/docs/3.0.0-rc.2/media-buy/task-reference/create_media_buy) to execute campaign
3. **Prepare Creatives**: Use [`list_creative_formats`](/dist/docs/3.0.0-rc.2/creative/task-reference/list_creative_formats) to see format requirements
4. **Upload Assets**: Use [`sync_creatives`](/dist/docs/3.0.0-rc.2/creative/task-reference/sync_creatives) to provide creative assets

## Learn More

* [Product Discovery Guide](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/) - Understanding briefs and products
* [Pricing Models](/dist/docs/3.0.0-rc.2/media-buy/advanced-topics/pricing-models) - CPM, CPCV, CPP explained
* [Brief Expectations](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/brief-expectations) - How to write effective briefs
* [Media Products](/dist/docs/3.0.0-rc.2/media-buy/product-discovery/media-products) - Product structure and fields
