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/v3/media-buy/get-products-request.json Response Schema: /schemas/v3/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({
  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(', ')}`);
}

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:
import { testAgent } from '@adcp/client/testing';

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

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

Request Parameters

ParameterTypeRequiredDescription
buying_modestringYes"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".
briefstringConditionalNatural language description of campaign requirements. Required when buying_mode is "brief". Must not be provided when buying_mode is "wholesale" or "refine".
refineRefine[]ConditionalArray 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 below.
brandBrandRefNoBrand reference (domain + optional brand_id). Resolved to full identity at execution time.
accountAccountRefNoAccount reference for account-specific pricing. Returns products with pricing from this account’s rate card.
catalogCatalogNoCatalog 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 below.
filtersFiltersNoStructured filters (see below)
property_listPropertyListRefNo[AdCP 3.0] Reference to a property list for filtering. See Property Lists
paginationPaginationRequestNoCursor-based pagination for large product catalogs (see below)
time_budgetDurationNoMaximum 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"}.
Property GovernanceThe property_list filter references a property list created via create_property_list on a property governance agent. Property lists define which publisher properties meet compliance requirements — COPPA-certified sites, sustainability-scored inventory, brand-safe publishers, etc.To use property list filtering:
  1. Call get_adcp_capabilities on a property governance agent to discover available property_features
  2. Create a property list via create_property_list with your feature requirements
  3. Pass the resulting property_list_id to get_products to filter inventory
The seller must declare features.property_list_filtering: true in get_adcp_capabilities to support this filter. See the Property Governance overview for the full workflow.

Filters Object

ParameterTypeDescription
delivery_typestringFilter by "guaranteed" or "non_guaranteed"
is_fixed_pricebooleanFilter for fixed price vs auction products
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"])
regionsstring[]Filter by region coverage using ISO 3166-2 codes (e.g., ["US-NY", "GB-SCT"]). Best for locally-bound inventory
metrosobject[]Filter by metro coverage. Each entry: { system, code } (e.g., [{ "system": "nielsen_dma", "code": "501" }])
channelsstring[]Filter by advertising channels (e.g., ["display", "ctv", "social", "streaming_audio"]). See Media Channel Taxonomy
postal_areasobject[]Filter by postal area coverage. Each entry: { system, values } (e.g., [{ "system": "us_zip", "values": ["10001"] }])
geo_proximityobject[]Filter by proximity to geographic points. Each entry uses exactly one boundary method: radius, travel_time + transport_mode, or geometry
keywordsobject[]Filter by keyword relevance for search/retail media. Each entry: { keyword, match_type? }. match_type defaults to broad if omitted

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.

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”

FieldTypeRequiredDescription
scopestringYes"request"
askstringYesDirection for the selection as a whole (e.g., "more video options", "suggest how to combine these products").

scope: “product”

FieldTypeRequiredDescription
scopestringYes"product"
idstringYesProduct ID from a previous get_products response
actionstringYes"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).
askstringNoWhat 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”

FieldTypeRequiredDescription
scopestringYes"proposal"
idstringYesProposal ID from a previous get_products response
actionstringYes"include": return with updated allocations and pricing. "omit": exclude from the response.
askstringNoWhat 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:
FieldTypeRequiredDescription
scopestringNoEchoes the scope from the corresponding refine entry for cross-validation.
idstringNoEchoes the id from the corresponding refine entry (for product and proposal scopes).
statusstringYes"applied": ask fulfilled. "partial": partially fulfilled. "unable": could not fulfill.
notesstringNoSeller 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 object used throughout AdCP. You can reference a synced catalog by catalog_id, provide inline items, or use selectors to filter:
FieldTypeDescription
typeCatalogTypeCatalog type (required) — product, job, hotel, flight, offering, etc.
catalog_idstringReference a synced catalog by ID
idsstring[]Filter to specific item IDs
gtinsstring[]Filter by GTIN for cross-retailer matching (product type only)
tagsstring[]Filter by tags (OR logic)
categorystringFilter by category
querystringNatural 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.

Products Array

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_measurementDeliveryMeasurement(Optional) How delivery is measured (impressions, views, etc.)
pricing_optionsPricingOption[]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).
showsCollectionSelector[](Optional) Collections available in this product. Each entry has publisher_domain and collection_ids. Buyers resolve full collection objects from the referenced adagents.json. See Collections and installments.
collection_targeting_allowedboolean(Optional, default: false) Whether buyers can target a subset of this product’s shows. When false, the product is a bundle.
brief_relevancestringWhy this product matches the brief (when brief provided)
measurement_readinessMeasurementReadiness(Optional) Whether the buyer’s event setup is sufficient for this product’s optimization. Only present when the seller can evaluate the buyer’s account context.

Proposals Array (Optional)

Publishers may return proposals alongside products - structured media plans with budget allocations. See Proposals for details.
FieldTypeDescription
proposal_idstringUnique identifier for executing this proposal via create_media_buy
namestringHuman-readable name for the media plan
allocationsProductAllocation[]Budget allocations across products (percentages must sum to 100). Each allocation may include optional start_time and end_time for per-flight scheduling.
forecastDeliveryForecastAggregate delivery forecast for the proposal. Contains forecast points with metric ranges. See Delivery Forecasts
total_budget_guidanceobjectOptional min/recommended/max budget guidance
brief_alignmentstringHow this proposal addresses the campaign brief
expires_atstringISO 8601 timestamp when this proposal expires

Pagination

For large product catalogs, use cursor-based pagination:
Request ParameterTypeDescription
pagination.max_resultsintegerMaximum products per page (1-100, default: 50)
pagination.cursorstringCursor from previous response for next page
Response FieldTypeDescription
pagination.has_morebooleanWhether more products are available
pagination.cursorstringCursor to pass for the next page
pagination.total_countintegerTotal 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

FieldTypeDescription
property_list_appliedboolean[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_appliedbooleantrue 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_appliedRefinementResult[]Seller acknowledgment of each refine entry, matched by position. Only present when buying_mode is "refine". See refinement_applied above.
incompleteIncompleteEntry[]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 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.
FieldTypeRequiredDescription
scopestringYes"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.
descriptionstringYesHuman-readable explanation of what is missing and why.
estimated_waitDurationNoHow much additional time would resolve this scope.
See schema for complete field list: 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:
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}`);
      }
    }
  }
}
A response with incomplete data — products are returned but some scopes are missing:
test=false
{
  "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

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`);
}

Multi-Format Discovery

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: {
    channels: ['display', 'ctv']
  }
});

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({
  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`);
}

Property Tag Resolution

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)`);
}

Guaranteed Delivery Products

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`);
}

Standard Formats Only

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`);
}

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:
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');
  }
}
You can also use GTIN matching, reference a synced catalog, or discover products for other catalog types:
{
  "catalog": {
    "type": "product",
    "gtins": ["00013000006040", "00013000006057"]
  }
}
{
  "catalog": {
    "catalog_id": "gmc-primary",
    "type": "product"
  }
}
{
  "catalog": {
    "type": "job",
    "catalog_id": "chef-vacancies"
  }
}

Property List Filtering

AdCP 3.0 - Property list filtering requires governance agent support.
Filter products to only those available on properties in your approved list:
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)`);
  }
}
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 for more details and Property Governance 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 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:
test=false
{
  "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:
test=false
{
  "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:
test=false
{
  "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 before sending.

Error Handling

Error CodeDescriptionResolution
AUTH_REQUIREDAuthentication needed for full catalogProvide credentials via auth header
INVALID_REQUESTBrief too long or malformed filtersCheck request parameters
PRODUCT_NOT_FOUNDOne or more referenced product IDs are unknown or expiredRemove invalid IDs and retry, or re-discover with a brief request
PROPOSAL_EXPIREDA referenced proposal ID has passed its expires_at timestampRe-discover with a new brief or wholesale request
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({
  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`);
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": {
    "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:
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:
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:
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": [...]
  }
}

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