Skip to main content
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 Response Schema: /schemas/v2/media-buy/get-products-response.json

Quick Start

Discover products with a natural language brief:
import { testAgent } from '@adcp/client/testing';
import { GetProductsResponseSchema } from '@adcp/client';

const result = await testAgent.getProducts({
  brief: 'Premium athletic footwear with innovative cushioning',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.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(', ')}`);
}

Using Structured Filters

You can also use structured filters instead of (or in addition to) a brief:
import { testAgent } from '@adcp/client/testing';

const result = await testAgent.getProducts({
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.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`);
}

Request Parameters

ParameterTypeRequiredDescription
briefstringNoNatural language description of campaign requirements
brand_manifestBrandManifest | stringNoBrand information (inline object or URL). See Brand Manifest
filtersFiltersNoStructured filters (see below)

Filters Object

ParameterTypeDescription
delivery_typestringFilter by "guaranteed" or "non_guaranteed"
is_fixed_pricebooleanFilter for fixed price vs auction products
format_typesstring[]Filter by format types (e.g., ["video", "display"])
format_idsFormatID[]Filter by specific format IDs
standard_formats_onlybooleanOnly return products accepting IAB standard formats
min_exposuresintegerMinimum exposures needed for measurement validity
start_datestringCampaign start date in ISO 8601 format (YYYY-MM-DD) for availability checks
end_datestringCampaign end date in ISO 8601 format (YYYY-MM-DD) for availability checks
budget_rangeobjectBudget range to filter appropriate products (see Budget Range Object below)
countriesstring[]Filter by target countries using ISO 3166-1 alpha-2 codes (e.g., ["US", "CA", "GB"])
channelsstring[]Filter by advertising channels (e.g., ["display", "video", "dooh", "ctv", "audio", "native"])

Budget Range Object

ParameterTypeRequiredDescription
currencystringYesISO 4217 currency code (e.g., "USD", "EUR", "GBP")
minnumberNo*Minimum budget amount
maxnumberNo*Maximum budget amount
*At least one of min or max must be specified.

Response

Returns an array of products, each containing:
FieldTypeDescription
product_idstringUnique product identifier
namestringHuman-readable product name
descriptionstringDetailed product description
publisher_propertiesPublisherProperty[]Array of publisher entries, each with publisher_domain and either property_ids or property_tags
format_idsFormatID[]Supported creative format IDs
delivery_typestring"guaranteed" or "non_guaranteed"
delivery_measurementDeliveryMeasurementHow delivery is measured (impressions, views, etc.)
pricing_optionsPricingOption[]Available pricing models (CPM, CPCV, etc.)
brief_relevancestringWhy this product matches the brief (when brief provided)
See schema for complete field list: get-products-response.json

Common Scenarios

Standard Catalog Discovery

import { testAgent } from '@adcp/client/testing';

// No brief = standard catalog products for maximum reach
const result = await testAgent.getProducts({
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.com'
  },
  filters: {
    delivery_type: 'non_guaranteed'
  }
});

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

Multi-Format Discovery

import { testAgent } from '@adcp/client/testing';

// Find products supporting both video and display
const result = await testAgent.getProducts({
  brief: 'Brand awareness campaign with video and display',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.com'
  },
  filters: {
    format_types: ['video', 'display']
  }
});

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

Budget and Date Filtering

import { testAgent } from '@adcp/client/testing';

// Find products within budget and date range for specific countries and channels
const result = await testAgent.getProducts({
  brief: 'Q2 campaign for athletic footwear in North America',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.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', 'video', 'ctv'],
    delivery_type: 'guaranteed'
  }
});

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

Property Tag Resolution

import { testAgent } from '@adcp/client/testing';

// Get products with property tags
const result = await testAgent.getProducts({
  brief: 'Sports content',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.com'
  }
});

if (result.success && result.data) {
  // Products with property_tags in publisher_properties need resolution via list_authorized_properties
  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)`);
}

Guaranteed Delivery Products

import { testAgent } from '@adcp/client/testing';

// Find guaranteed delivery products for measurement
const result = await testAgent.getProducts({
  brief: 'Guaranteed delivery for lift study',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.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`);
}

Standard Formats Only

import { testAgent } from '@adcp/client/testing';

// Find products that only accept IAB standard formats
const result = await testAgent.getProducts({
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.com'
  },
  filters: {
    standard_formats_only: true
  }
});

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

Error Handling

Error CodeDescriptionResolution
AUTH_REQUIREDAuthentication needed for full catalogProvide credentials via auth header
INVALID_REQUESTBrief too long or malformed filtersCheck request parameters
POLICY_VIOLATIONCategory blocked for advertiserSee policy response message for details

Authentication Comparison

See the difference between authenticated and unauthenticated access:
import { testAgent, testAgentNoAuth } from '@adcp/client/testing';

// WITH authentication - full catalog with pricing
const fullCatalog = await testAgent.getProducts({
  brief: 'Premium CTV inventory for brand awareness',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.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({
  brief: 'Premium CTV inventory for brand awareness',
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.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`);
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 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

Immediate Completion (Most Common)

POST /api/mcp/call_tool

{
  "name": "get_products",
  "arguments": {
    "brief": "CTV inventory for sports audience",
    "brand_manifest": { "name": "Nike", "url": "https://nike.com" }
  }
}

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

Needs Clarification

When the brief is unclear, the system asks for more details:
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",
  "data": {
    "reason": "CLARIFICATION_NEEDED",
    "partial_results": [],
    "suggestions": ["$50K-$100K", "1 month", "Q1 2024"]
  }
}
Continue the conversation with the same context_id:
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",
  "data": {
    "products": [...]
  }
}

Complex Search (With Webhook)

For searches requiring deep inventory analysis, configure a webhook:
POST /api/mcp/call_tool

{
  "name": "get_products",
  "arguments": {
    "brief": "Premium inventory across all formats for luxury automotive brand",
    "brand_manifest": { "name": "Porsche", "url": "https://porsche.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",
  "data": {
    "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": [...]
  }
}

Status Overview

StatusWhen It HappensWhat You Do
completedSearch finished successfullyProcess the product results
input-requiredNeed clarification on the briefAnswer the question and continue
workingSearching across multiple sourcesWait for webhook or poll for updates
submittedCustom curation queuedWait for webhook notification
failedSearch couldn’t completeCheck error message, adjust brief
Note: For the complete status list see Core Concepts - Task Status System. 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 to execute campaign
  3. Prepare Creatives: Use list_creative_formats to see format requirements
  4. Upload Assets: Use sync_creatives to provide creative assets

Learn More