Skip to main content

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

Connect private storefronts (optional)

For storefronts that gate inventory, register per-source credentials first — see the Storefront object guide.
2

Run discovery

POST /api/buyer/discovery/discover-products with a brief. Returns a discoveryId plus product groups, agent proposals, and budget context.
3

Browse and refine

Page through results with GET /api/buyer/discovery/{id}/discover-products or iterate by sending refine instructions back to the same discoveryId.
4

Pick products or apply a proposal

Add specific products with POST /api/buyer/discovery/{id}/products or apply a full proposal with POST /api/buyer/discovery/{id}/apply-proposal.
5

Promote to a campaign

Run POST /api/buyer/campaigns/{id}/auto-select-products for hands-off selection, or attach the discovery session’s selected products to a campaign you’ve already set up.
A discovery session is a long-lived workspace identified by discoveryId. You can re-open it, refine results, swap products in and out, and replay proposals without losing context.

Public vs private storefronts

Storefronts fall into two visibility tiers:
TierWho can discoverHow to access
PublicAny authenticated buyerAvailable by default — no extra setup
Private / gatedBuyers with credentials registered for the sourceRegister credentials per inventory source (API key, OAuth, or JWT)
Private storefronts gate inventory behind per-source authentication. Scope3 speaks to all sources — public and private — over the Ad Context Protocol (ADCP); the difference is just whether the source requires the buyer to present credentials before products are returned. The buyer never needs an AAO key directly: AAO compliance gating is handled server-side by Scope3.

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:
curl -X POST \
  'https://api.agentic.scope3.com/api/buyer/storefronts/42/sources/agt_premium_ctv/credentials' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "auth": { "type": "api_key", "token": "<source-issued-token>" } }'
For OAuth-secured sources, request an authorize URL and complete the buyer consent flow — Scope3 hosts the callback at /api/storefront/oauth/callback so AI agents and back-end clients don’t need to handle redirects themselves.
Once credentials are registered for a source, every discovery call from your customer automatically queries it — no per-call configuration needed.

Step 2: Run discovery

POST /api/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

curl -X POST 'https://api.agentic.scope3.com/api/buyer/discovery/discover-products' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "advertiserId": 12345,
    "brief": "Premium CTV inventory for Q3 sports launch — adults 25-54, US only, video creative",
    "budget": 250000,
    "channels": ["ctv", "olv"],
    "countries": ["US"],
    "flightDates": {
      "startDate": "2026-07-01T00:00:00Z",
      "endDate": "2026-09-30T23:59:59Z"
    },
    "groupLimit": 10,
    "productsPerGroup": 10
  }'
FieldTypeDescription
advertiserIdintegerRequired. Resolves the brand manifest used to score agent results.
discoveryIdstringReuse an existing session. Required when sending refine.
campaignIdstringSeed brief, flight dates, and budget from a campaign. Inline values override.
briefstring (≤5000 chars)Natural-language search context.
budgetnumberTotal budget in account currency. Used for budget context + proposals.
channelsstring[]display, olv, ctv, social, or video (alias for olv). Defaults to ["display","olv","ctv","social"].
countriesstring[]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.
publisherDomainstringFilter to a single publisher domain.
pricingModelstringFilter by AdCP pricing model (cpm, cpcv, etc.).
salesAgentIds / salesAgentNamesstring[]Restrict to specific agents.
groupLimitinteger (≤10)Max product groups per page. Default 10.
productsPerGroupinteger (≤15)Max products inside each group. Default 10.
groupOffset / productOffsetintegerPagination cursors.
debugbooleanInclude per-agent ADCP request/response logs in the response.
refinearrayRefinement directives (see Refining). Requires discoveryId.

Response

{
  "discoveryId": "disc_01HZX3YQ7K9R6V3M2P1E0F8B2T",
  "productGroups": [
    {
      "groupId": "ctx_disc_01HZX3-group-0",
      "groupName": "Hulu Connected TV",
      "description": "Premium CTV across Hulu's owned-and-operated apps",
      "productCount": 4,
      "totalProducts": 12,
      "hasMoreProducts": true,
      "products": [
        {
          "productId": "prod_hulu_ctv_sports",
          "name": "Hulu Sports CTV — 30s",
          "channel": "ctv",
          "formatTypes": ["video", "30s"],
          "cpm": 32.5,
          "salesAgentId": "agent_hulu",
          "salesAgentName": "Hulu Ad Sales",
          "deliveryType": "guaranteed",
          "briefRelevance": "Lives squarely on Hulu's sports verticals — adults 25-54 over-index here",
          "pricingOptions": [
            { "pricingOptionId": "po_fixed_30", "pricingModel": "cpm", "isFixed": true, "fixedPrice": 32.5, "currency": "USD" }
          ],
          "estimatedExposures": 7700000
        }
      ]
    }
  ],
  "totalGroups": 18,
  "hasMoreGroups": true,
  "summary": {
    "totalProducts": 142,
    "publishersCount": 18,
    "priceRange": { "min": 4.5, "max": 78.0, "avg": 21.4 }
  },
  "budgetContext": {
    "sessionBudget": 250000,
    "allocatedBudget": 0,
    "remainingBudget": 250000
  },
  "proposals": [
    {
      "proposalId": "proposal_hulu_q3_sports",
      "name": "Q3 Sports — premium CTV mix",
      "salesAgentName": "Hulu Ad Sales",
      "briefAlignment": "Combines guaranteed sports with non-guaranteed sports-adjacent OLV to extend reach",
      "allocations": [
        { "productId": "prod_hulu_ctv_sports", "allocationPercentage": 60, "rationale": "Anchors the plan with guaranteed delivery" },
        { "productId": "prod_hulu_olv_sports", "allocationPercentage": 40 }
      ],
      "totalBudgetGuidance": { "min": 150000, "recommended": 250000, "max": 400000, "currency": "USD" }
    }
  ]
}
The first call always creates a new session unless you pass 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 are surfaced under agentResults only when you pass debug: true.

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.
curl -X GET 'https://api.agentic.scope3.com/api/buyer/discovery/disc_01HZX.../discover-products?groupOffset=10&publisherDomain=hulu&pricingModel=cpm' \
  -H 'Authorization: Bearer scope3_<your_api_key>'
Query parameters mirror the discover request: groupLimit, groupOffset, productsPerGroup, productOffset, publisherDomain, pricingModel, salesAgentIds, salesAgentNames, debug.

Refining results

Iterate on a previous response by sending refine instructions back to the same discoveryId. Refinements come in three scopes:
{
  "advertiserId": 12345,
  "discoveryId": "disc_01HZX...",
  "refine": [
    { "scope": "request", "ask": "Less display, more video. Drop anything below $10 CPM." }
  ]
}
The agent’s reply to each instruction comes back under 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 the productId, the salesAgentId it came from, and the group it was discovered in. Optionally pin a budget allocation, pricing option, or bid.
curl -X POST 'https://api.agentic.scope3.com/api/buyer/discovery/disc_01HZX.../products' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "products": [
      {
        "productId": "prod_hulu_ctv_sports",
        "salesAgentId": "agent_hulu",
        "groupId": "ctx_disc_01HZX3-group-0",
        "groupName": "Hulu Connected TV",
        "pricingOptionId": "po_fixed_30",
        "budget": 150000
      }
    ]
  }'
For non-fixed pricing, include bidPrice (read it from the product’s pricingOptions[].rate or floorPrice in the discovery response). List the current session selection at any time:
curl -X GET 'https://api.agentic.scope3.com/api/buyer/discovery/disc_01HZX.../products' \
  -H 'Authorization: Bearer scope3_<your_api_key>'
Pull a single product’s full detail (including extended specs from the sales agent) with the per-product detail endpoint:
curl -X GET 'https://api.agentic.scope3.com/api/buyer/discovery/disc_01HZX.../products/prod_hulu_ctv_sports/details?salesAgentId=agent_hulu' \
  -H 'Authorization: Bearer scope3_<your_api_key>'
Remove products you no longer want:
curl -X DELETE 'https://api.agentic.scope3.com/api/buyer/discovery/disc_01HZX.../products' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "productIds": ["prod_random_display"] }'

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.
curl -X POST 'https://api.agentic.scope3.com/api/buyer/discovery/disc_01HZX.../apply-proposal' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "proposalId": "proposal_hulu_q3_sports",
    "totalBudget": 250000,
    "replace": true
  }'
FieldDescription
proposalIdRequired. ID from the discover-products response.
totalBudgetOptional. Defaults to proposal.totalBudgetGuidance.recommended.
replaceWhen true, clears existing selected products before applying.
The response echoes the applied proposal, the budget actually distributed, the products that were added, and any products from the proposal that could not be matched back to the discovery results (productsSkipped).
productsSkipped is non-empty when an agent’s proposal references a product that has aged out of the cached discovery results. Re-run discover to refresh the session, then re-apply.

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.
curl -X POST 'https://api.agentic.scope3.com/api/buyer/campaigns/cmp_987654321/auto-select-products' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "maxProducts": 5,
    "minBudgetPerProduct": 5000
  }'
The body is optional — omit it for a fully automated first pass. To iterate, send back the same refine directives accepted by discover-products:
{
  "refine": [
    { "scope": "request", "ask": "Drop anything social. Lean into CTV." },
    { "scope": "product", "id": "prod_low_quality", "action": "omit" }
  ],
  "maxProducts": 8
}
The response includes the underlying discoveryId so you can drop into the manual flow at any point to inspect or adjust the selection.

Best practices

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