Overview
Product Discovery is the buyer-side loop for finding inventory across one or more sales agents (publishers, exchanges, content owners). You provide a brief — what you’re trying to accomplish, who you’re targeting, when it runs, and how much you can spend — and Scope3 fans the request out to every agent your seat has access to. The agents return products and (when supported) recommended proposals that allocate budget across those products. The flow:Connect private storefronts (optional)
For storefronts that gate inventory, register per-source credentials first — see the Storefront object guide.
Run discovery
POST /api/v2/buyer/discovery/discover-products with a brief. Returns a
discoveryId plus product groups, agent proposals, and budget context.Browse and refine
Page through results with
GET /api/v2/buyer/discovery/{id}/discover-products
or iterate by sending refine instructions back to the same discoveryId.Pick products or apply a proposal
Add specific products with
POST /api/v2/buyer/discovery/{id}/products or
apply a full proposal with POST /api/v2/buyer/discovery/{id}/apply-proposal.discoveryId. You
can re-open it, refine results, swap products in and out, and replay proposals
without losing context.
Storefront agents in discovery
Discovery fans out to both third-party sales agents and Scope3 storefront agents. A storefront agent hassalesAgentId: "storefront-{platform_id}", where platform_id is the storefront’s public platformId slug, and represents the storefront as a buyer-facing route for product discovery and media-buy execution. That ID identifies the storefront surface; it is not a source ID or underlying source agent ID.
Buyers call the same discovery endpoint for all agents. They do not need to distinguish between storefronts that compose products from raw inventory and storefronts that passthrough to an upstream inventory source. Composition storefronts return products assembled from active inventory sources and active operating instructions. Passthrough storefronts proxy get_products to an active source and apply storefront metadata and buyer-instruction overlays before results are returned. Buyer instructions are resolved from operator domain, brand domain, and optional country. Use storefrontIds or storefrontNames when you want to restrict discovery to specific storefronts.
Public buyer discovery only includes Storefronts that have passed marketplace
review and are listed. A Storefront can be live for known transactions while it
is still pending review, but it will not appear in public discovery results or
buyer marketplace browsing until an admin lists it.
Murph account analysis for adapter storefronts
Operators can ask Murph to runanalyze_account for adapter-routed
storefronts with delegated provider credentials. The tool analyzes a connected
advertising account through the storefront adapter and can render an
interactive account-analysis viewer in compatible MCP clients.
Inputs:
| Field | Type | Notes |
|---|---|---|
provider | string | Optional for self-service. Required when a super admin needs to disambiguate a client storefront. Supported values include amazon, audiostack, google, meta, pinterest, reddit, snap, spotify, and tiktok. |
targetCustomerId | integer | Super-admin-only customer ID whose adapter storefront should be analyzed. |
storefrontPlatformId | string | Super-admin-only public storefront platform ID to analyze instead of targetCustomerId. |
accountId | string | Optional upstream provider account ID to request from the connected delegated credential. |
analysis object returned by the adapter, plus a
recommendations[] array for the operator. Adapter implementations may include
findings, metrics, account health signals, and other provider-specific details
inside analysis.
analyze_account is read-only but requires the storefront to be adapter-routed
and connected with delegated provider auth. Without a stored delegated
credential, Murph returns an auth-required response that includes the provider
OAuth setup path. Cross-customer targeting is restricted to SuperAdmins; normal
storefront operators can analyze only their own storefront.
Public vs private storefronts
Storefronts fall into two visibility tiers:| Tier | Who can discover | How to access |
|---|---|---|
| Public | Any authenticated buyer | Available by default after marketplace review |
| Private / gated | Buyers with credentials registered for the source | Register credentials per inventory source (API key, OAuth, or JWT) |
Step 1: Connect private storefronts (optional)
If you only need public inventory, skip ahead to Step 2. To unlock private storefronts, register credentials for the relevant inventory source. Three auth types are supported (API key, OAuth, JWT) — the source declares which it requires. Full walkthrough lives in the Storefront object guide; the short version:/api/v2/storefront/oauth/callback
so AI agents and back-end clients don’t need to handle redirects themselves.
Step 2: Run discovery
POST /api/v2/buyer/discovery/discover-products is the main entry point. You
can pass a brief inline or seed the request from an existing campaign with
campaignId.
Request
| Field | Type | Description |
|---|---|---|
advertiserId | integer | Required. Resolves the brand reference used to score agent results. |
discoveryId | string | Reuse an existing session. Required when sending refine. |
campaignId | string | Seed brief, flight dates, and budget from a campaign. Inline values override. |
brief | string (≤5000 chars) | Natural-language search context. |
budget | number | Total budget in account currency. Used for budget context + proposals. |
channels | string[] | display, olv, ctv, social, or video (alias for olv). Defaults to ["display","olv","ctv","social"]. |
countries | string[] | ISO 3166-1 alpha-2 country codes. Defaults to brand agent countries. |
flightDates | { startDate, endDate } | ISO 8601 datetimes (e.g. 2026-07-01T00:00:00Z). Filters by agent availability. |
publisherDomain | string | Filter to a single publisher domain. |
pricingModel | string | Filter by AdCP pricing model (cpm, cpcv, etc.). |
storefrontIds | integer[] | Restrict to specific storefronts (IDs from list_storefronts). Empty array = same as omitted (no request-level filter); falls back to the campaign-level pin if a campaignId is also provided. |
storefrontNames | string[] | Restrict to storefronts whose name matches (case-insensitive substring). Empty array = same as omitted; falls back to the campaign-level pin if available. |
groupLimit | integer (≤10) | Max product groups per page. Default 10. |
productsPerGroup | integer (≤15) | Max products inside each group. Default 10. |
groupOffset / productOffset | integer | Pagination cursors. |
debug | boolean | Include per-agent ADCP request/response logs in the response. |
refine | array | Refinement directives (see Refining). Requires discoveryId. |
Response
discoveryId.
Persist the returned discoveryId — every subsequent call references it.
Discovery fans out across all reachable agents in parallel. Slow agents do
not block fast ones; agents that fail or are skipped are surfaced under
agentResults only when you pass debug: true.Agents whose advertised channel coverage does not overlap with the requested
channels are skipped before fanout (no round-trip), and appear in
agentResults with a skipReason like
Agent does not sell requested channels (supports: display, ctv; requested: social).
For agents that respond with an error, skipReason carries the
human-readable rejection text from the agent (e.g. "We do not support the list of channels you specified"); prefer it over error when surfacing the
reason in a UI. skipReason is agent-controlled content, sanitize before
rendering as HTML.Step 3: Page through results
Use the GET endpoint to browse the same session without spending another LLM-enriched discovery call. Filters narrow the cached result set in place.groupLimit, groupOffset,
productsPerGroup, productOffset, publisherDomain, pricingModel,
storefrontIds, storefrontNames, debug.
Refining results
Iterate on a previous response by sendingrefine instructions back to the
same discoveryId. Refinements come in three scopes:
refinementApplied
(matched by position) with status: "applied" | "partial" | "unable" and an
optional explanation.
Step 4: View specific products
Add products you want to evaluate to the session. Each selection records theproductId, the salesAgentId it came from, and the group it was discovered
in. Optionally pin a budget allocation, pricing option, or bid.
bidPrice (read it from the product’s
pricingOptions[].rate or floorPrice in the discovery response).
List the current session selection at any time:
Step 5: Apply a proposal
When an agent returns a proposal, you can accept its full allocation in one call instead of adding products one at a time.| Field | Description |
|---|---|
proposalId | Required. ID from the discover-products response. |
totalBudget | Optional. Defaults to proposal.totalBudgetGuidance.recommended. |
replace | When true, clears existing selected products before applying. |
productsSkipped).
Auto-select on a campaign
For agentic / hands-off flows, attach an existing campaign and let Scope3 pick products for you. This wraps discovery + selection in a single call against the campaign’s existing brief, flight dates, and budget.refine directives accepted by discover-products:
discoveryId so you can drop into the
manual flow at any point to inspect or adjust the selection.
Best practices
- Briefs
- Pagination & polling
- Source credentials
- Refinement loops
- Lead with the outcome, not just the demographic. Agents score against campaign objective + creative + audience together.
- Include guardrails that matter: brand-safety needs, format
constraints (
16:9,:30s), exclusions. - Keep briefs under ~500 characters when possible — long briefs are auto-summarized for LLM enrichment but lose nuance.
Related
- Campaigns guide — promote a discovery selection into a live media buy
- Storefronts — connect, refresh, and manage credentials per inventory source
- Buyer storefronts — register against operator-hosted storefronts
- Buyer API reference — full endpoint and schema reference