> ## Documentation Index
> Fetch the complete documentation index at: https://docs.interchange.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Get products across storefronts

> Use one canonical AdCP-shaped request to retrieve, evaluate, refine, and buy proposals across your connected storefronts

`get_products` is the canonical product door for buyer agents. Send one
AdCP-shaped request to selected storefronts—or all storefronts connected to an
advertiser—and receive one bounded canonical page as `products[]` plus optional
sibling `proposals[]`. Every identity remains qualified by its originating
storefront.

Interchange can optionally evaluate valid seller proposals before returning
them. You supply plain-language operating instructions. A managed model chooses
`accept`, `reject`, or `refine`, can attach buyer-owned enrichment, and can rank
the accepted cohort across sellers.

<Info title="AdCP at every seller edge; Interchange above it">
  AdCP remains a one-buyer-to-one-seller protocol. Interchange is the
  many-to-one buyer layer: it fans out the canonical request, validates each
  seller independently, qualifies identities, and returns a bounded aggregate
  page. Sellers do not see one another or receive an Interchange-specific
  request.
</Info>

## One protocol request, marketplace scale

```mermaid theme={null}
flowchart LR
  B[Buyer agent] -->|canonical request + ext.interchange| I[Interchange get_products]
  I -->|1:1 AdCP get_products| S1[Storefront A]
  I -->|1:1 AdCP get_products| S2[Storefront B]
  I -->|1:1 AdCP get_products| SN[Storefront N]
  S1 --> I
  S2 --> I
  SN --> I
  I -->|bounded canonical page + qualified IDs| B
```

The scaling layer does four things without forking the protocol:

* **Bilateral calls stay canonical.** Buying mode, brief, filters, proposals,
  and refine are ordinary AdCP semantics at each seller edge.
* **Identity survives aggregation.** `sf1:` product IDs and `sfp1:` proposal
  IDs prevent cross-storefront collisions and round-trip into purchase.
* **Slow sellers do not erase fast sellers.** Revisioned replacement snapshots
  expose completed results while other agents remain pending.
* **Responses stay agent-sized.** `pagination.max_results` bounds candidates;
  proposal graphs remain whole, and the cursor is frozen only when the
  execution is complete.

<Note title="Fan-out breadth and response pagination are separate">
  `get_products` does not stop after the first 10 storefronts or choose a final
  cohort by response speed. When no storefront selector is supplied (or
  `storefronts: "all_connected"` is explicit), Interchange resolves every
  storefront in the buyer's eligible discovery scope and queries each backing
  sales agent once. The first provisional revision contains only sellers that
  have answered so far, so poll until `results_complete: true`. Then follow
  `pagination.cursor` until `has_more: false` to consume the complete candidate
  set. `pagination.max_results` limits each response page, not the seller
  fan-out. The legacy Discover Products `groupLimit` and `groupOffset` fields do
  not apply to this surface.
</Note>

Interchange rejects ambiguous seller-specific controls such as conditional
catalog versions on the aggregate surface. Query that seller's AdCP endpoint
directly when you need a seller-specific conditional request.

## Choose `get_products` or Discover Products

Both surfaces share the same seller fan-out implementation. They are different
buyer projections, not different integrations.

| Use                  | `get_products`                                                                                                                              | Discover Products                                                                                            |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Primary contract     | Canonical AdCP products and proposals, plus `ext.interchange` orchestration                                                                 | Interchange discovery-session view                                                                           |
| Best for             | Buyer agents, protocol-compatible workflows, named/all storefront queries, seller proposals, screening/refine, direct purchase continuation | Existing discovery-session integrations that need grouped pagination, summary/budget context, browse, or SSE |
| Intelligence         | Optional absolute proposal screen: accept, reject, or seller refine                                                                         | The same optional screen; no separate ranking brain                                                          |
| Result shape         | Flat, bounded `products[]` and optional `proposals[]`, with per-storefront progress metadata                                                | `productGroups[]`, discovery summaries, budget context, and plans                                            |
| Durable continuation | `execution_id` → `create_media_buys` → DRAFT campaign cart                                                                                  | `discoveryId` → legacy selection endpoints or the same campaign reconciliation path                          |

Start with `get_products` for new agent integrations. Use
[Discover Products](/v2/buyer/discovery/tasks/discover-products) when you need
its legacy grouped/session presentation or need to continue an existing
`discoveryId` workflow. Calling both for the same request is normally
unnecessary; Discover Products projects the same raw seller snapshot rather
than running another ranking implementation.

## Surfaces

* REST: `POST /api/v2/buyer/products/query`
* Buyer MCP: `get_products`
* Buyer `api_call`: operation `get_products`

The purchasable continuation is:

* REST: `POST /api/v2/buyer/media-buys/batch`
* Buyer MCP: `create_media_buys`
* Buyer `api_call`: operation `create_media_buys`

## Retrieval without evaluation

Omit `ext.interchange.evaluation` to receive valid seller responses without a
managed evaluation:

```json theme={null}
{
  "buying_mode": "wholesale",
  "filters": {},
  "pagination": { "max_results": 50 },
  "ext": {
    "interchange": {
      "advertiser_id": 123,
      "storefronts": "all_connected",
      "wait_mode": "quick"
    }
  }
}
```

Protocol validation always runs. Malformed seller responses, broken proposal
allocations, and invalid identities are rejected before evaluation and do not
consume evaluation work.

## Evaluate and negotiate proposals

Add evaluation instructions when you want Interchange to remove unsuitable
proposals, enrich accepted results, negotiate promising proposals, and
optionally rank the accepted cohort before it reaches your agent:

```json theme={null}
{
  "buying_mode": "brief",
  "brief": "Premium UK sports video with measurable reach",
  "filters": { "countries": ["GB"] },
  "pagination": { "max_results": 50 },
  "ext": {
    "interchange": {
      "advertiser_id": 123,
      "storefronts": "all_connected",
      "evaluation": {
        "instructions": "Accept proposals that fit the brief and include measurable reach. If CPM is above the stated budget, ask for a lower-cost option. Reject proposals without usable measurement.",
        "max_refinement_rounds": 1,
        "max_evaluation_passes": 20,
        "enrichment": true,
        "ranking": {
          "instructions": "Prefer stronger measurement and efficient expected reach."
        }
      }
    }
  }
}
```

The first evaluation pass is absolute:

* **Accept** returns the seller's unchanged proposal and referenced products,
  with optional buyer-owned enrichment.
* **Reject** suppresses the proposal from the normal result.
* **Refine** sends a canonical AdCP refine instruction back to that proposal's
  seller, then evaluates the seller's revised response again.

Interchange never rewrites seller pricing, allocations, availability, or terms.
Any changed proposal must come back from the seller.

Autonomous refinement is bounded by `max_refinement_rounds`, repeated-request
detection, the request deadline, and `max_evaluation_passes`. A proposal still
requesting refinement at the limit is rejected rather than silently accepted.

When `ranking` is present, a separate comparative pass scores accepted
candidates under its optional instructions. The response is ordered by score
and includes a rank and rationale. Provisional rankings replace earlier
snapshots as sellers settle; the final completed revision is frozen.

The platform selects the managed models. This version does not expose provider
model names, customer executable code, or a model picker.

<Note>
  `ext.interchange.screening` remains a deprecated compatibility alias for
  accept, reject, and refine. It does not enable enrichment or comparative
  ranking. Do not send `screening` and `evaluation` together.
</Note>

## Discover Products compatibility

`discover_products` is a convenience view over the same seller fan-out and
evaluation engine. Passing its optional `evaluation` field enables the same
disposition, enrichment, refinement, and ranking behavior, projected into the
Discover response shape. Without `evaluation`, Discover preserves its existing
enrichment, relevance, ordering, and refinement behavior. Existing Discover
clients do not need to migrate to keep their current behavior.

## Proposals and products

Canonical AdCP proposals reference sibling products through allocation
`product_id` values; they do not duplicate complete product objects. Evaluation
resolves that graph automatically. A top-level product that is not allocated by
any proposal remains a proposal-less candidate, principally for wholesale
catalogs.

`pagination.max_results` bounds the combined number of proposals and
proposal-less products. Products referenced by a paged proposal accompany that
proposal and do not consume another candidate slot, so the proposal graph is
never split across pages. Interchange accepts at most 50 allocated products in
one proposal. A response contains at most 100 total proposal and product objects;
when complete proposal graphs reach that limit, the page returns fewer proposal
candidates and the cursor continues from the next whole proposal.

Both IDs are opaque and storefront-qualified:

```json theme={null}
{
  "products": [
    {
      "product_id": "sf1:42:...",
      "ext": {
        "interchange": {
          "storefront_id": "42",
          "source_product_id": "seller-product-7"
        }
      }
    }
  ],
  "proposals": [
    {
      "proposal_id": "sfp1:42:...",
      "allocations": [{ "product_id": "sf1:42:..." }],
      "ext": {
        "interchange": {
          "storefront_id": "42",
          "source_proposal_id": "seller-proposal-9",
          "evaluation": {
            "disposition": "accept",
            "evaluated": true,
            "confidence": 0.86,
            "reasons": ["Fits the brief and measurement requirements"],
            "enrichment": {
              "summary": "Premium UK sports video with measurable reach.",
              "strengths": ["Strong measurement"],
              "concerns": [],
              "labels": ["video", "sports"]
            },
            "ranking": {
              "rank": 1,
              "score": 0.91,
              "rationale": "Best combination of measurement and reach.",
              "provisional": false
            }
          }
        }
      }
    }
  ]
}
```

Pass qualified IDs back unchanged in refine and media-buy creation calls.

## Stage or buy the result

The `execution_id` in the response is also the durable search/refinement
record used by `create_media_buys`. Choose whole proposals and/or
proposal-less products from that execution. The operation stages them on a
DRAFT campaign—the campaign is the shopping cart and the parent of every
media buy.

Only proposals in the execution's durable accepted set can be selected. Run
`get_products` again before purchasing from an older execution that predates
that set.

Use an existing campaign cart:

```json theme={null}
{
  "productQueryId": "query-id",
  "campaign": { "campaignId": "campaign_123" },
  "mode": "stage",
  "selections": [
    {
      "kind": "proposal",
      "proposalId": "sfp1:42:...",
      "totalBudget": 40000
    },
    {
      "kind": "product",
      "productId": "sf1:57:...",
      "pricingOptionId": "cpm-standard",
      "budget": 10000,
      "bidPrice": 8.5
    }
  ]
}
```

Or provide `campaign.create` with the same required fields as a discovery-mode
campaign (`advertiserId`, `name`, `flightDates`, and `budget`). Interchange
creates the DRAFT campaign and returns its ID. Media buys are never standalone,
even when the caller did not create a campaign first.

* `mode: "stage"` updates the cart and prepares DRAFT media buys without
  contacting sellers.
* `mode: "execute"` performs the same preparation, then sends each resulting
  buy to its originating storefront through ordinary bilateral AdCP
  `create_media_buy` calls.
* `replace: true` replaces the cart selection before applying this batch;
  otherwise selections merge idempotently.

Use `mode: "execute"` when the selected result can be submitted immediately.
When you need to inspect or customize per-buy creatives, dates, pacing, or
optimization goals, use `mode: "stage"`, make those changes on the returned
DRAFT media buys, then call `execute_campaign` or repeat `create_media_buys`
with the same campaign and selections using `mode: "execute"`. Execution
submits the prepared DRAFT directly; it does not rebuild it from the product
query.

One marketplace call may reach several sellers, but the seller calls are not
one atomic transaction. The response reports `mediaBuysExecuted`, `success`,
and per-buy `errors`. Retry the same request after a partial failure; completed
buys are not dispatched again. A completed product query accepts only an
unchanged retry of its recorded batch. To change the selection after execution,
start a new `get_products` query.

See [Create media buys from a product query](/v2/buyer/discovery/tasks/create-media-buys)
for the complete request and response contract.

## Progressive responses

Seller responses arrive independently. Each response is a replacement snapshot:

* `revision` increases as sellers settle or a refinement produces a new version;
* `provisional: true` means replace your previous displayed result;
* `pending_agents` identifies sellers still working;
* accepted proposals may appear while other sellers remain pending;
* proposals being refined are withheld;
* no stable cursor is issued until `results_complete: true`;
* `storefront_results[].message` preserves a seller's explanation for a
  successful empty response, including when readiness prevents that storefront
  from transacting; do not interpret that case as “no matching inventory”;
* the final cursor binds to the execution, revision, evaluation instructions,
  and offsets.

Response speed affects which sellers are visible in an early provisional
revision, not which sellers are retained in the completed execution. Without
comparative ranking, completed products use deterministic storefront-qualified
identity ordering; when `evaluation.ranking` is present, the accepted cohort is
ordered by that ranking instead.

Poll with the returned execution identity:

```json theme={null}
{
  "buying_mode": "brief",
  "ext": {
    "interchange": {
      "execution_id": "query-id",
      "since_revision": 3
    }
  }
}
```

Do not append provisional pages. Replace the prior page with the new revision.

Evaluation is idempotent by candidate version and instruction fingerprint.
Polling an unchanged revision does not rerun or remeter it. A seller's changed
proposal is evaluated as a new candidate version.

## Evaluation metadata and cost

The response reports evaluation separately from seller progress:

```json theme={null}
{
  "ext": {
    "interchange": {
      "evaluation": {
        "enabled": true,
        "status": "complete",
        "model_class": "fast",
        "evaluation_passes": 2,
        "ranking_passes": 1,
        "candidates_evaluated": 17,
        "candidates_ranked": 8,
        "accepted": 8,
        "rejected": 7,
        "refining": 0,
        "refinement_limit_reached": 2,
        "estimated_ius": 3,
        "charged_ius": 0,
        "billing_status": "calibrating"
      }
    }
  }
}
```

One **Proposal Evaluation Pass** evaluates a bounded batch of at most ten
candidates. One **Comparative Proposal Ranking Pass** ranks at most 100 accepted
candidates. We measure these value units rather than provider tokens. Both are
currently calibrating, so `charged_ius` remains `0`. Retrieval without
evaluation reports `billing_status: "included"`.

If managed evaluation is unavailable, valid candidates pass through and the
evaluation status becomes `degraded`; an infrastructure failure does not silently
reject seller supply. Every pass-through candidate is explicitly marked
`ext.interchange.evaluation.evaluated: false` (with `confidence: 0`), and the
response `guidance` says so. Check `evaluated` before treating an accept as a
judgment: `evaluated: false` means your evaluation instructions were **not**
applied to that candidate — re-run the request or apply your own screening
before buying on it.
