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

# Storefront

> Browse storefronts, connect inventory sources, and manage adapter provider accounts

## Overview

A **Storefront** is a publisher's buyer-facing home on Interchange: the business presence, name, description, and discovery surface buyers use to understand who they are buying from. The publisher's **Merchandising Agent** runs that Storefront. It implements the AdCP media-buy workflow, responds to buyer briefs, and draws from one or more **inventory sources**.

Inventory sources can be external sales agents you already run, your own ad servers with Interchange-managed sales-agent plumbing behind them, or other Storefronts that have authorized passthrough/federation.

Buying through a source-backed storefront is gated on **credentials per source**: every inventory source declares whether it requires authentication, and the buyer registers credentials (API key, JWT, or OAuth) once per source. Once a source reports `connected: true`, discovery and media buys flow through it normally. Adapter storefronts use adapter provider credentials instead: connect or reconnect the provider account through the storefront adapter connection flow, then use the returned adapter account and credential identifiers when linking advertiser accounts.

```
Storefront
  ├── Inventory Source(s)        (MCP / A2A agent)
  │     └── Buyer credentials    (per source, per buyer)
  │           └── Buyer accounts
  └── Adapter provider account(s)
        └── Adapter credentials  (per provider account, per buyer)
              └── Advertiser accounts
```

<Note>
  This page covers the **buyer** side: browsing storefronts, expressing interest
  in upcoming supply, and wiring up credentials. If you operate a storefront,
  see [Storefront onboarding](/v2/setup/storefront-onboarding).
</Note>

All examples below use the buyer base URL:

```
https://api.interchange.io/api/v2/buyer
```

## Why storefronts matter

* **One-stop access to publisher inventory** — a storefront aggregates multiple inventory sources behind a single ID, so buyers don't track agent endpoints individually
* **Per-source credential management** — credentials are scoped to the inventory source, so rotating or revoking one doesn't affect access to the others
* **AAO registry signals** — Interchange reads [Agentic Advertising Organization](https://agenticadvertising.org) registration and compliance signals; registration gates external source connection, compliance is advisory for activation, and Scope3 marketplace review controls public listing
* **Multiple auth options** — sources support API key, JWT, and OAuth flows, so the platform fits whatever the publisher's agent already uses
* **Discoverable upcoming supply** — `/storefronts/discover` and `/storefronts/interest` let buyers see and request access to publishers they aren't yet connected to

## Adapter credential health

Connected adapter credentials continue through a health loop after setup. OAuth credentials refresh lazily during use and proactively in a nightly sweep. If refresh fails because the token is expired or revoked, the adapter credential moves to `EXPIRED`. If a delegated adapter call returns an auth or permission failure that cannot be fixed by retrying, the adapter credential moves to `EXPIRED` or `ERROR` and the connection summary reports `error`.

API-key and bearer adapter credentials are not refreshable. When they carry an expiry timestamp, the sweep notifies the operator before expiry and marks the adapter credential `EXPIRED` after expiry. Reconnect or rotate the adapter credential to restore the storefront connection.

Adapter credential recovery is not a generic API-call fallback. Use the typed storefront adapter connection flow to reconnect OAuth accounts or rotate provider tokens, then continue with typed adapter operations such as listing and linking advertiser accounts.

## Storefronts in buyer discovery

Every transacting, marketplace-listed Storefront is exposed through its Merchandising Agent for buyer-side product discovery. The generated sales agent ID is `storefront-{platform_id}`, where `platform_id` is the Storefront's public `platformId` slug; for example, a Storefront with `platformId: "premium-ctv"` appears as `salesAgentId: "storefront-premium-ctv"` in discovery results. Existing Storefronts are registered automatically once listed, so buyers can use the standard discovery flow without learning a separate endpoint.

The Merchandising Agent is the buyer-facing ADCP actor for the Storefront, not a replacement for inventory sources, source IDs, or underlying source agent IDs. Composition Storefronts return products assembled from active ingredient sources and active product rules. Passthrough Storefronts proxy `get_products` to an active upstream inventory source and apply Storefront identity, metadata, and buyer-instruction overlays to the response. Buyer instructions are resolved from operator domain, brand domain, and optional country.

<Note title="Marketplace review">
  New Storefronts start in a pending-review marketplace state. They can be
  configured and used by the owning organization, and they can open for known
  transactions, but public buyer discovery only includes Storefronts that Scope3
  has reviewed and listed. Admins can also hide a listed Storefront, which removes
  it from public buyer discovery without deleting the Storefront or its inventory
  sources.
</Note>

## Key fields

The storefront resource has two response shapes:

* **Summary** — returned by `GET /storefronts` (list). Row-level fields plus scalar source counts.
* **Full** — returned by `GET /storefronts/:id` (get). The complete storefront resource with rolled-up connection state and buyer accounts. Source-level diagnostic rows are returned by `GET /storefronts/:id/capabilities`.

### Storefront brand identity

An operator Storefront can persist `brandName`, `logoUrl`, and
`logoBackground` for its `operatorDomain`. These fields are projections of
owner-asserted identity from the AAO registry and the domain's `brand.json`;
Interchange does not fill them from Brandfetch or website inference.

`POST /api/v2/storefront/resolve-brand` performs a fresh authoritative lookup.
When the registry confirms that no identity exists, it returns
`resolved: false` without a resolved logo. Positive owner-asserted fields are
reconciled back to the same operator domain. A missing record or logo does not
erase an existing field because legacy rows do not record whether that field
was inferred or explicitly supplied; absence is not proof that an explicit
value should be deleted. If the registry cannot complete the fresh origin
check, the endpoint returns `503 SERVICE_UNAVAILABLE` and leaves the current
identity unchanged. Identity reconciliation is conditional on the operator
domain still matching the domain that was resolved, so a concurrent domain
change cannot receive older identity data.

See [Resolve brand](/v2/storefront/tasks/resolve-brand) for the request and
response contract.

### Modular inventory source readiness

Operator-owned storefronts can include `MODULAR_SOURCE` inventory sources. A
modular source is composed from smaller source-side modules: inventory-feed
ingestion, a booking ledger, execution handoff, creative mapping, status sync,
reporting import, and source-side human work queues.

The modular source runtime projection includes `lifecycleSummary`, which is the
operator-facing rollup for each stage:

| Status                    | Meaning                                                                                                                                                                                                                                                                                                    |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UNSUPPORTED`             | Every attached module declared what it covers, and none of them implements the stage. Attach a module of the kind that runs it.                                                                                                                                                                            |
| `NOT_DECLARED`            | A module that could run the stage is attached, but we could not read what it does — whether it runs the stage is unknown, not absent. The stage's `issues` name that module and `modes` is empty. Do not read the stage as a setup gap; nothing in a seller's configuration produces or clears this state. |
| `MISSING_SETUP`           | A module supports the stage, but required module config is missing.                                                                                                                                                                                                                                        |
| `RUNTIME_INPUTS_REQUIRED` | Setup is complete, but the stage needs runtime data such as avails, creative refs, media-buy fields, or reporting uploads.                                                                                                                                                                                 |
| `HITL_PENDING`            | Setup is complete and at least one open human work item is waiting for this stage.                                                                                                                                                                                                                         |
| `READY`                   | The stage has the setup and runtime inputs it needs for the current source state.                                                                                                                                                                                                                          |
| `BLOCKED`                 | A module has a source-health issue or error that needs operator attention.                                                                                                                                                                                                                                 |

`activeAvailCount` is the number of active normalized avails available to the
source projection. `openWorkItemCountsByKind` groups source-side queue items by
kind, such as `CONFIRM_AVAILS`, `TRAFFIC_TO_CADENT`, `MAP_CREATIVE_REF`, or
`UPLOAD_FINAL_REPORT`.

Use the detailed [modular inventory source lifecycle guide](/v2/storefront/inventory-sources/modular-lifecycle)
for the operator task sequence: preview and commit avails, inspect products,
reserve capacity, prepare execution handoff, release capacity, and work HITL
queue items.

### Creative library capability

Storefront and source capability caches store AdCP `creative.has_creative_library` as `creativeHasCreativeLibrary`. Interchange uses this tri-state field to choose creative transport for media buys:

| State            | Meaning                                            | Creative transport                                                                                |
| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `true`           | Seller has a creative library.                     | `sync_creatives` is required and packages reference synced manifests with `creative_assignments`. |
| `false`          | Seller does not have a creative library.           | Creative manifests are delivered inline on package payloads.                                      |
| `null` or absent | Unknown, legacy, or synthetic capability metadata. | Interchange falls back to the advertised tool list and uses `sync_creatives` when available.      |

### Operator `businessProfile`

Operator setup surfaces may include a `businessProfile` object captured during
Murph-led storefront setup. This profile helps the Merchandising Agent describe
the operator's business, follow the right merchandising guidance, and
understand which publisher domains it should be authorized to sell through the
Storefront.

| Field              | Type      | Description                                                                                                                                                                                                                |
| ------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agentName`        | string    | Deprecated agent-name field, kept for legacy compatibility only. Prefer the Storefront display name for buyer-facing naming. Maximum 80 characters.                                                                        |
| `agentPersonality` | string    | **Deprecated.** An agent has no personality of its own — voice belongs to the brand and is read from the brand manifest (`tone`). Nothing consumes this field. Maximum 1000 characters.                                    |
| `publisherDomains` | string\[] | Publisher domains or properties the Merchandising Agent should be authorized to sell through the Storefront. Up to 128 valid domains; each domain is normalized to lowercase, deduplicated, and limited to 253 characters. |

```json Example theme={null}
{
  "businessProfile": {
    "publisherDomains": ["touchline.example", "football.touchline.example"]
  }
}
```

<Note>
  Omitting `publisherDomains` means the authorized domains are unknown or have
  not been captured yet. It does not mean the Merchandising Agent is authorized
  to sell every domain through the Storefront.
</Note>

For managed sales-agent storefronts, this field is the authoritative
publisher-domain set Interchange syncs to the managed sales agent. See
[Publisher domains](/v2/storefront/publisher-domains) for replacement and
removal behavior.

### Murph `sellerAnalytics`

When a storefront operator asks Murph for seller analytics, Murph can attach a
`sellerAnalytics` payload to the chat response for visual MCP App rendering.
The payload includes discovery-run mix, buyer asks, top surfaced products, and
commercial outcomes attributed to those runs.

The `historicalPerformance` object summarizes the same window into the
negotiation signals Murph and the Merchandising Agent can use:

| Field                           | Type           | Description                                                                                                                                         |
| ------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `runCount`                      | integer        | Recent intelligence runs considered                                                                                                                 |
| `bookedRunCount`                | integer        | Runs with at least one attributed booked media buy                                                                                                  |
| `winRate`                       | number         | Share of recent runs that booked, from `0` to `1`                                                                                                   |
| `askRunCount`                   | integer        | Runs with buyer asks, price objections, or packaging asks                                                                                           |
| `askToBookConversionRate`       | number \| null | Share of ask runs that booked, or `null` when no ask runs exist                                                                                     |
| `priceObjectionConversionRate`  | number \| null | Share of price-objection runs that booked, or `null` when none exist                                                                                |
| `packagingAskConversionRate`    | number \| null | Share of packaging-ask runs that booked, or `null` when none exist                                                                                  |
| `averageBookedBudget`           | number \| null | Average booked budget per attributed booked media buy                                                                                               |
| `averageProductsPerRun`         | number         | Average products shown per run                                                                                                                      |
| `averageProductsPerBookedRun`   | number \| null | Average products shown on booked runs                                                                                                               |
| `averageProductsPerUnbookedRun` | number \| null | Average products shown on unbooked runs                                                                                                             |
| `deliveryRate`                  | number \| null | Delivery-report events divided by booked media buys, or `null` when none booked                                                                     |
| `repeatBuyerCount`              | integer        | Buyer identities with at least two runs in the window                                                                                               |
| `recommendedPosture`            | string \| null | Directional negotiation posture: `hold_value`, `value_preserving_compromise`, `tradeoff_ladder`, `direct_fit`, `price_first`, or `wholesale_mirror` |

The `strategySignals[]` array turns `historicalPerformance` into deterministic
strategy guidance for sellers:

| Field      | Type                              | Description                                                 |
| ---------- | --------------------------------- | ----------------------------------------------------------- |
| `id`       | string                            | Stable signal key within the generated analytics payload    |
| `priority` | `"high"` \| `"medium"` \| `"low"` | Suggested attention level                                   |
| `label`    | string                            | Short display label                                         |
| `detail`   | string                            | Human-readable reason for the signal                        |
| `posture`  | string                            | Suggested negotiation posture                               |
| `evidence` | array                             | Small list of `{ label, value }` metrics backing the signal |

The `postureConversion.adherence` object reports how often the negotiation
posture the agent actually used matched the history-derived recommendation, and
how win rate differs when the seller follows that recommendation versus diverges
from it:

| Field                | Type           | Description                                                                                                        |
| -------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
| `comparableRunCount` | integer        | Runs where a recommended posture existed to compare the chosen posture against                                     |
| `followedCount`      | integer        | Comparable runs where the chosen posture matched the recommendation                                                |
| `followedWinRate`    | number \| null | Share of followed runs that booked, from `0` to `1`, or `null` when no run had a recommendation to compare against |
| `divergedWinRate`    | number \| null | Share of diverged runs that booked, from `0` to `1`, or `null` when no run had a recommendation to compare against |

When the Merchandising Agent composes products, the same recent outcome history can tune its
built-in negotiation defaults, including posture selection, target product
count, pricing guidance, and packaging guidance. Human operating instructions
and buyer-specific instruction notes remain authoritative over these historical
signals.

The top-level `outcomes` object summarizes attributed outcomes across the
window:

| Field                   | Type           | Description                                                                                   |
| ----------------------- | -------------- | --------------------------------------------------------------------------------------------- |
| `attributedRunCount`    | integer        | Number of recent runs with at least one attributed outcome event                              |
| `eventCount`            | integer        | Total attributed outcome events                                                               |
| `submittedCount`        | integer        | Media buys submitted for seller approval                                                      |
| `forwardedCount`        | integer        | Media buys forwarded to inventory sources                                                     |
| `forwardFailedCount`    | integer        | Media-buy forward attempts that failed                                                        |
| `rejectedCount`         | integer        | Media buys rejected by the seller                                                             |
| `deliveryReportedCount` | integer        | Delivery report events attributed to recent runs                                              |
| `bookedMediaBuyCount`   | integer        | Unique attributed media buys with a booked budget                                             |
| `bookedBudget`          | number         | Sum of attributed booked budget in the analytics window, counted once per media buy           |
| `deliveredImpressions`  | integer        | Sum of attributed delivered impressions                                                       |
| `deliveredSpend`        | number         | Sum of attributed delivered spend                                                             |
| `deliveredCurrency`     | string \| null | Delivery spend currency, or `null` when no delivery currency is known or currencies are mixed |

Each item in `runs[]` includes an `outcome` object for that run:

| Field                   | Type           | Description                                                                                                     |
| ----------------------- | -------------- | --------------------------------------------------------------------------------------------------------------- |
| `eventCount`            | integer        | Attributed outcome events for the run                                                                           |
| `latestType`            | string \| null | Latest attributed event type, or `null` when no events are attributed                                           |
| `latestStatus`          | string \| null | Latest media-buy status, or `null` when no status is available                                                  |
| `mediaBuyId`            | string \| null | Latest attributed media-buy ID, or `null` when no media buy is attributed                                       |
| `submittedCount`        | integer        | Media buys submitted for seller approval                                                                        |
| `forwardedCount`        | integer        | Media buys forwarded to inventory sources                                                                       |
| `forwardFailedCount`    | integer        | Media-buy forward attempts that failed                                                                          |
| `rejectedCount`         | integer        | Media buys rejected by the seller                                                                               |
| `deliveryReportedCount` | integer        | Delivery report events attributed to the run                                                                    |
| `bookedMediaBuyCount`   | integer        | Unique attributed media buys with a booked budget                                                               |
| `bookedBudget`          | number \| null | Sum of attributed booked budgets for the run, counted once per media buy, or `null` when no budget is available |
| `deliveredImpressions`  | integer        | Delivered impressions attributed to the run                                                                     |
| `deliveredSpend`        | number         | Delivered spend attributed to the run                                                                           |
| `deliveredCurrency`     | string \| null | Delivery spend currency, or `null` when no delivery currency is known or currencies are mixed                   |

`latestType` is one of `media_buy_submitted_for_approval`,
`media_buy_forwarded`, `media_buy_forward_failed`, `media_buy_rejected`, or
`delivery_reported`.

The `buyers[]` array rolls the same window up by buyer identity:

| Field                     | Type           | Description                                                                                   |
| ------------------------- | -------------- | --------------------------------------------------------------------------------------------- |
| `operatorDomain`          | string \| null | Buyer operator domain when known                                                              |
| `brandDomain`             | string \| null | Buyer brand domain when known                                                                 |
| `country`                 | string \| null | Buyer country when known                                                                      |
| `firstRunAt`              | string \| null | Earliest run timestamp for this buyer in the analytics window                                 |
| `lastRunAt`               | string \| null | Latest run timestamp for this buyer in the analytics window                                   |
| `runCount`                | integer        | Recent intelligence runs for this buyer identity                                              |
| `bookedRunCount`          | integer        | Runs for this buyer with at least one attributed booked media buy                             |
| `winRate`                 | number         | Share of this buyer's recent runs that booked, from `0` to `1`                                |
| `askRunCount`             | integer        | Runs for this buyer with buyer asks, price objections, or packaging asks                      |
| `askToBookConversionRate` | number \| null | Share of this buyer's ask runs that booked, or `null` when no ask runs exist                  |
| `attributedRunCount`      | integer        | Runs with at least one attributed outcome event                                               |
| `eventCount`              | integer        | Attributed outcome events for this buyer identity                                             |
| `totalShownProducts`      | integer        | Products shown across the buyer's runs                                                        |
| `averageShownProducts`    | number         | Average products shown per run                                                                |
| `priceObjectionCount`     | integer        | Runs with detected price objections                                                           |
| `packagingAskCount`       | integer        | Runs with detected packaging asks                                                             |
| `requestAskCount`         | integer        | Buyer request-level asks detected in the window                                               |
| `productAskCount`         | integer        | Product-level asks detected in the window                                                     |
| `proposalAskCount`        | integer        | Proposal-level asks detected in the window                                                    |
| `bookedMediaBuyCount`     | integer        | Unique attributed media buys with a booked budget                                             |
| `bookedBudget`            | number         | Sum of attributed booked budget for this buyer identity                                       |
| `averageBookedBudget`     | number \| null | Average booked budget for this buyer's attributed booked media buys                           |
| `deliveredImpressions`    | integer        | Delivered impressions attributed to this buyer identity                                       |
| `deliveredSpend`          | number         | Delivered spend attributed to this buyer identity                                             |
| `deliveredCurrency`       | string \| null | Delivery spend currency, or `null` when no delivery currency is known or currencies are mixed |
| `recommendedPosture`      | string \| null | Buyer-specific directional negotiation posture                                                |

The `seasonality[]` array buckets the same analytics window by UTC month:

| Field                     | Type           | Description                                                            |
| ------------------------- | -------------- | ---------------------------------------------------------------------- |
| `period`                  | string         | Month bucket in `YYYY-MM` form                                         |
| `runCount`                | integer        | Intelligence runs in the period                                        |
| `bookedRunCount`          | integer        | Runs in the period with at least one attributed booked media buy       |
| `winRate`                 | number         | Share of period runs that booked, from `0` to `1`                      |
| `askRunCount`             | integer        | Period runs with buyer asks, price objections, or packaging asks       |
| `askToBookConversionRate` | number \| null | Share of period ask runs that booked, or `null` when no ask runs exist |
| `priceObjectionCount`     | integer        | Period runs with detected price objections                             |
| `packagingAskCount`       | integer        | Period runs with detected packaging asks                               |
| `bookedBudget`            | number         | Sum of attributed booked budget in the period                          |
| `averageBookedBudget`     | number \| null | Average booked budget across booked runs in the period                 |
| `averageShownProducts`    | number         | Average products shown per run in the period                           |
| `recommendedPosture`      | string \| null | Directional posture inferred for the period                            |

The `sellerRecommendations[]` array turns those directional signals into
seller actions:

| Field      | Type                                                                                                                      | Description                                                                      |
| ---------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `id`       | string                                                                                                                    | Stable recommendation key within the generated analytics payload                 |
| `priority` | `"high"` \| `"medium"` \| `"low"`                                                                                         | Suggested attention level                                                        |
| `kind`     | `"protect_expand"` \| `"prioritize_follow_up"` \| `"packaging_friction"` \| `"price_resistance"` \| `"tighten_selection"` | Recommendation category                                                          |
| `title`    | string                                                                                                                    | Short display title                                                              |
| `detail`   | string                                                                                                                    | Human-readable reason for the recommendation                                     |
| `action`   | string                                                                                                                    | Suggested next seller action                                                     |
| `buyer`    | object \| null                                                                                                            | Buyer identity the recommendation is about, or `null` for global seller guidance |
| `evidence` | array                                                                                                                     | Small list of `{ label, value }` metrics backing the recommendation              |

<Note>
  Seller analytics outcome totals use last-touch product-overlap attribution and
  are directional. Seller recommendations are deterministic coaching signals
  derived from the same window, not automated pricing or packaging changes. Use
  buyer reporting endpoints for audited campaign delivery reporting.
</Note>

### `BuyerStorefrontSummary` (list rows)

| Field                  | Type           | Description                                          |
| ---------------------- | -------------- | ---------------------------------------------------- |
| `id`                   | integer        | Storefront ID                                        |
| `platformId`           | string         | Public-facing slug                                   |
| `name`                 | string         | Storefront display name                              |
| `publisherDomain`      | string \| null | Publisher domain                                     |
| `sourceCount`          | integer        | Total number of inventory sources in this storefront |
| `connectedSourceCount` | integer        | Number of sources the buyer is already wired to use  |

Use `GET /storefronts/:id` when you need rolled-up credential state and buyer accounts. Use `GET /storefronts/:id/capabilities` when you need source-level diagnostic rows.

### `BuyerStorefront` (single storefront)

| Field                 | Type                              | Description                                                                        |
| --------------------- | --------------------------------- | ---------------------------------------------------------------------------------- |
| `id`                  | integer                           | Storefront ID                                                                      |
| `platformId`          | string                            | Public-facing slug                                                                 |
| `name`                | string                            | Storefront display name                                                            |
| `publisherDomain`     | string \| null                    | Publisher domain                                                                   |
| `connected`           | boolean                           | Whether the buyer has the required credentials/account setup to use the storefront |
| `requiresCredentials` | boolean                           | Whether any source behind the storefront requires buyer credentials                |
| `customerAccounts`    | `[{ accountIdentifier, status }]` | Buyer's registered accounts, deduped across the storefront                         |
| `adcpCapabilities`    | object \| null                    | Rolled-up capability manifest when cached capability data exists                   |

### Storefront capability source rows

| Field                 | Type            | Description                                                                                                                                                                                            |
| --------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sourceId`            | string          | Inventory source identifier within the storefront                                                                                                                                                      |
| `sourceName`          | string \| null  | Inventory source display name                                                                                                                                                                          |
| `executionType`       | string          | Source execution type                                                                                                                                                                                  |
| `agentName`           | string \| null  | External sales-agent name when one exists                                                                                                                                                              |
| `requiresCredentials` | boolean \| null | Whether buyer-provided credentials are required for this external AdCP source. `null` when not applicable or unavailable                                                                               |
| `probeable`           | boolean         | Whether this row is an external AdCP sales agent that the capability diagnostic can check                                                                                                              |
| `probeStatus`         | string          | `reachable`, `unreachable`, `not_applicable`, or `unavailable`. `reachable` means capabilities are available (including synthesized capabilities for adapter and ESA storefronts), possibly from cache |
| `message`             | string \| null  | Explanation when the row is not probeable                                                                                                                                                              |
| `capabilities`        | object \| null  | Capability summary for external AdCP sources; `null` for managed sources or failed capability lookups                                                                                                  |

<Note>
  Managed ad-server-backed sources appear in the capability response with
  `probeable: false` and `probeStatus: "not_applicable"`. That means they are not
  external AdCP agents probed by this endpoint; it does not mean the source is
  unreachable.
</Note>

### Storefront readiness diagnostics

Storefront operators can call `GET /api/v2/storefront/readiness` to inspect
setup progress. In addition to the top-level readiness checks, the response
includes `sourceDiagnostics[]`, a per-inventory-source diagnostic summary used
by Murph and the storefront setup rail.

Each source diagnostic includes:

| Field                           | Description                                                                                                                                                                                           |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`, `sourceId`, `name`        | Internal row id, storefront source id, and display name.                                                                                                                                              |
| `executionType`, `sourceStatus` | Source type and current storefront source status.                                                                                                                                                     |
| `capabilities`                  | Support state for `products`, `createMediaBuy`, `updateMediaBuy`, `signals`, and `wholesaleProducts`. Synthetic or missing manifests report unknown capabilities rather than hard unsupported states. |
| `auth`                          | Whether source authentication is required, configured, and which auth type is expected.                                                                                                               |
| `productBuilder`                | Whether the source is used for product composition, pass-through routing, or is still unknown, plus wholesale product and signal counts when available.                                               |
| `compliance`                    | Latest AAO compliance summary, including pass/fail state, checked timestamp, and track counts.                                                                                                        |
| `debug`                         | Sanitized capability/debug metadata such as advertised tools, channels, publisher domains, and synthetic-manifest state.                                                                              |
| `lastActivity`                  | Latest setup activity for the inventory source, when available.                                                                                                                                       |

## Common operations

### Listing storefronts

`GET /storefronts` returns the storefronts you can transact with, paginated.

<CodeGroup>
  ```bash List theme={null}
  curl "https://api.interchange.io/api/v2/buyer/storefronts?limit=20" \
    -H "Authorization: Bearer $SCOPE3_API_KEY"
  ```

  ```bash Filter by name theme={null}
  curl "https://api.interchange.io/api/v2/buyer/storefronts?name=premium&limit=20" \
    -H "Authorization: Bearer $SCOPE3_API_KEY"
  ```
</CodeGroup>

Query parameters:

| Param    | Notes                                             |
| -------- | ------------------------------------------------- |
| `name`   | Case-insensitive partial match on storefront name |
| `limit`  | 1..50 (default 20)                                |
| `offset` | Pagination offset (default 0)                     |

Response (summary rows):

```json theme={null}
{
  "items": [
    {
      "id": 42,
      "platformId": "premium-ctv",
      "name": "Premium CTV Storefront",
      "publisherDomain": "premium-ctv.example.com",
      "sourceCount": 3,
      "connectedSourceCount": 1
    }
  ],
  "total": 1,
  "hasMore": false,
  "nextOffset": null
}
```

For rolled-up credential state and buyer accounts, call `GET /storefronts/:id`.

<Tip>
  When `hasMore` is `true`, pass `nextOffset` back as the `offset` parameter to
  walk pages.
</Tip>

### Getting a storefront

`GET /storefronts/:id` returns the full `BuyerStorefront` resource, including
rolled-up `connected`, `requiresCredentials`, and `customerAccounts`. Use this
when you need fresh storefront connection state (e.g. after registering
credentials or completing the OAuth flow) or when a row-level summary isn't
enough.

```bash theme={null}
curl "https://api.interchange.io/api/v2/buyer/storefronts/42" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

```json Response theme={null}
{
  "id": 42,
  "platformId": "premium-ctv",
  "name": "Premium CTV Storefront",
  "publisherDomain": "premium-ctv.example.com",
  "connected": false,
  "requiresCredentials": true,
  "customerAccounts": [
    {
      "accountIdentifier": "buyer-seat-123",
      "status": "ACTIVE"
    }
  ],
  "adcpCapabilities": null
}
```

### Inspecting source capabilities

`GET /storefronts/:id/capabilities` returns active source rows for capability
diagnostics. External AdCP sales-agent sources are capability-checkable through
the platform's cached-or-refreshed capability lookup. Managed ad-server-backed
sources are included but marked `probeable: false`.

```bash theme={null}
curl "https://api.interchange.io/api/v2/buyer/storefronts/42/capabilities" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

```json Response theme={null}
{
  "storefrontId": 42,
  "agents": [
    {
      "sourceId": "src_main",
      "sourceName": "Premium CTV Direct",
      "executionType": "AGENT",
      "agentName": "Premium CTV Sales Agent",
      "requiresCredentials": true,
      "probeable": true,
      "probeStatus": "reachable",
      "message": null,
      "capabilities": {
        "version": "3.1.0",
        "tools": ["get_products", "create_media_buy"],
        "protocols": ["mcp"],
        "sandboxSupported": true,
        "synthetic": false
      }
    }
  ]
}
```

### Discovery (lightweight)

`GET /storefronts/discover` returns every marketplace-listed storefront visible
to a buyer - the live ones (`TRANSACTING`) and any listed storefront still
working through the pre-launch lifecycle (`UNCLAIMED`, `VERIFICATION`,
`CONFIGURING`, `TESTING`) - with just `id`, `name`, `publisherDomain`, and
`status`. This is the right endpoint for buyers who don't yet have programmatic
access but want to see what supply is coming.

```bash theme={null}
curl "https://api.interchange.io/api/v2/buyer/storefronts/discover" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

```json theme={null}
{
  "items": [
    {
      "id": 42,
      "name": "Premium CTV Storefront",
      "publisherDomain": "premium-ctv.example.com",
      "status": "TRANSACTING"
    },
    {
      "id": 77,
      "name": "Travel Vertical Network",
      "publisherDomain": null,
      "status": "CONFIGURING"
    }
  ]
}
```

<Note>
  Discovery is intentionally minimal — no source list, no credential state.
  Use it to populate a "what's available" picker, then submit interest with
  the IDs you care about.
</Note>

### Submitting interest

For private or upcoming storefronts you don't yet have access to, submit an
interest form. Scope3's onboarding team picks it up via Slack.

`POST /storefronts/interest` accepts:

| Field           | Type       | Notes                                         |
| --------------- | ---------- | --------------------------------------------- |
| `storefrontIds` | `number[]` | Up to 100 storefront IDs you're interested in |
| `notes`         | string     | Free-form notes, up to 4000 chars             |

You must provide at least one of `storefrontIds` or `notes`.

```bash theme={null}
curl -X POST "https://api.interchange.io/api/v2/buyer/storefronts/interest" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storefrontIds": [42, 77],
    "notes": "Targeting Q3 CTV upfronts; need access to travel vertical."
  }'
```

```json theme={null}
{
  "submitted": true,
  "submittedAt": "2026-04-26T15:00:00.000Z"
}
```

To check whether you've already submitted, call `GET /storefronts/interest`:

```bash theme={null}
curl "https://api.interchange.io/api/v2/buyer/storefronts/interest" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

```json theme={null}
{
  "submitted": true,
  "submittedAt": "2026-04-26T15:00:00.000Z"
}
```

### Listing your credentials

`GET /storefronts/credentials` returns every credential you've registered,
scoped to the storefront sources each credential gives access to. Use it to
build a single "connected sources" view in your dashboard without paging
through each storefront.

```bash theme={null}
curl "https://api.interchange.io/api/v2/buyer/storefronts/credentials" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

```json Response theme={null}
[
  {
    "id": "722",
    "accountIdentifier": "your-account-id",
    "accountType": "CLIENT",
    "status": "ACTIVE",
    "registeredBy": "user@example.com",
    "createdAt": "2026-02-23T19:55:11.602Z",
    "updatedAt": "2026-02-23T19:56:56.272Z",
    "sources": [
      {
        "storefrontId": 42,
        "storefrontName": "Premium CTV Storefront",
        "sourceId": "src_main",
        "sourceName": "Premium CTV — Direct"
      }
    ]
  }
]
```

Each credential lists every `(storefrontId, sourceId)` pair it covers in
`sources[]`. A single credential can cover the same source across multiple
storefronts when the underlying agent is shared.

### Registering credentials per source

When a storefront reports `requiresCredentials: true`, at least one source
behind it needs buyer credentials before discovery or media buys can flow.
Use the capabilities endpoint to identify active source IDs and inspect
top-level `requiresCredentials` on external AdCP sources. Use the credentials
list to see which `(storefrontId, sourceId)` pairs already have credentials,
then register credentials for the external source that still needs them. If
`requiresCredentials` is `null`, do not infer that registration is required from
the capability row alone.

`POST /storefronts/:storefrontId/sources/:sourceId/credentials`

<Note>
  Check the parent storefront first. If `requiresCredentials` is `false`, the
  buyer does not need this credential step for that storefront.
</Note>

<CodeGroup>
  ```bash API key theme={null}
  curl -X POST \
    "https://api.interchange.io/api/v2/buyer/storefronts/42/sources/src_main/credentials" \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "accountIdentifier": "your-account-id",
      "auth": {
        "type": "api_key",
        "token": "<SOURCE_API_KEY>"
      }
    }'
  ```

  ```bash JWT theme={null}
  curl -X POST \
    "https://api.interchange.io/api/v2/buyer/storefronts/42/sources/src_main/credentials" \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "accountIdentifier": "your-account-id",
      "auth": {
        "type": "jwt",
        "privateKey": "<PRIVATE_KEY>",
        "issuer": "your-issuer",
        "subject": "your-subject",
        "keyId": "your-key-id",
        "scope": "adcp:read adcp:write",
        "tokenEndpointUrl": "https://idp.example.com/oauth/token",
        "audienceUrl": "https://agent.example.com"
      }
    }'
  ```
</CodeGroup>

Body fields:

| Field                | Type    | Notes                                                                               |
| -------------------- | ------- | ----------------------------------------------------------------------------------- |
| `accountIdentifier`  | string  | Unique account ID at the source (1..255 chars)                                      |
| `auth`               | object  | API key or JWT credentials. Required for non-OAuth sources.                         |
| `marketplaceAccount` | boolean | Admin-only. When `true`, creates a marketplace account instead of a client account. |

<Warning>
  **Auth tokens grant AdCP access on your behalf.** Treat `auth.token` (and any
  private keys) as a production secret:

  * Store it in a managed secret vault. Scope3 itself stores it in Google Secret
    Manager and references it via `auth_secret_ref`.
  * Never log the raw token, never echo it to stdout, and never commit it to
    source control.
  * Rotate on any suspected exposure and on a documented schedule.
  * Use placeholders like `<SOURCE_API_KEY>` in documentation and sample
    requests — never a real key.
</Warning>

### Registering an account for a source

When you want a source to bill or report against a specific advertiser
in your account, register an account with `advertiserId`:

`POST /storefronts/:storefrontId/sources/:sourceId/accounts`

```bash theme={null}
curl -X POST \
  "https://api.interchange.io/api/v2/buyer/storefronts/42/sources/src_main/accounts" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "advertiserId": "12345",
    "accountIdentifier": "acme-brand-account",
    "auth": {
      "type": "api_key",
      "token": "<SOURCE_API_KEY>"
    }
  }'
```

Body fields:

| Field               | Type   | Notes                                                             |
| ------------------- | ------ | ----------------------------------------------------------------- |
| `advertiserId`      | string | Numeric advertiser ID to associate with this account              |
| `accountIdentifier` | string | Unique account ID at the source (1..255 chars)                    |
| `auth`              | object | Credentials. Required for API\_KEY/JWT sources, omitted for OAuth |

### OAuth flow for OAuth-secured sources

Some inventory sources use OAuth. In that case, the credentials endpoint
isn't enough — the buyer must complete an interactive consent flow, and the
source agent exchanges an authorization code for tokens via the storefront
OAuth-authorize endpoint.

<Note>
  The OAuth endpoints sit on the `/api/v2/storefront/...` mount because they
  power the storefront's auth integration on behalf of buyers and sellers
  alike. Buyers usually start with the buyer source-credentials route above;
  when a source requires OAuth, follow the returned authorization flow instead
  of collecting secrets in your app.
</Note>

**Step 1 — Request an authorization URL.**

```bash theme={null}
curl -X POST \
  "https://api.interchange.io/api/v2/storefront/agents/agt_premium_ctv/accounts/oauth/authorize" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountIdentifier": "acme-brand-account"
  }'
```

`accountIdentifier` is optional — when omitted, the platform uses
`oauth_<customerId>` as the placeholder identifier and you can rename the
account later.

```json Response theme={null}
{
  "authorizeUrl": "https://idp.example-publisher.com/oauth/authorize?client_id=...&redirect_uri=https%3A%2F%2Fapi.interchange.io%2Fapi%2Fv2%2Fstorefront%2Foauth%2Fcallback&state=pending_abc123&scope=adcp%3Aread+adcp%3Awrite",
  "state": "pending_abc123"
}
```

**Step 2 — Send the operator to `authorizeUrl`.** Open it in a popup or new
tab. The publisher's IdP authenticates the user, asks them to consent, and
redirects back to the platform-hosted callback
(`/api/v2/storefront/oauth/callback`).

**Step 3 — The platform finishes the exchange.** The callback page exchanges
the authorization code for tokens, stores them under your account + the
source's underlying agent, and notifies the opener window via
`postMessage({ type: 'oauth-complete' })` before auto-closing. Refresh
`GET /api/v2/buyer/storefronts/:storefrontId` after that. The storefront's
rolled-up `connected` state should update once the required credential setup is
complete.

If you need to drive the callback yourself (for example, in a non-browser
agent), `POST /api/v2/storefront/agents/:agentId/oauth/callback` accepts the
`code` and `state` directly.

The seller side of OAuth (callback URLs, `state` handling, secret rotation)
is documented in [Storefront onboarding](/v2/setup/storefront-onboarding).

### Adapter-routed storefront providers

Some storefronts route directly to a wired provider adapter instead of an
inventory-source sales agent. These adapter-routed storefronts still appear to
buyers through the normal storefront discovery and media-buy surfaces, but the
operator connects the upstream provider account through delegated OAuth before
server-side workflows can run.

Supported wired adapter providers are:

| Provider   | `provider` value | Notes            |
| ---------- | ---------------- | ---------------- |
| Amazon     | `amazon`         | Sales adapter    |
| AudioStack | `audiostack`     | Creative adapter |
| Google Ads | `google`         | Sales adapter    |
| Meta       | `meta`           | Sales adapter    |
| Pinterest  | `pinterest`      | Sales adapter    |
| Reddit     | `reddit`         | Sales adapter    |
| Snap       | `snap`           | Sales adapter    |
| Spotify    | `spotify`        | Sales adapter    |
| TikTok     | `tiktok`         | Sales adapter    |

Provider OAuth apps should allow the adapter callback path:

```text theme={null}
https://api.interchange.io/oauth/adapters/{provider}/callback
```

Use the same path on staging, for example
`https://api.staging.interchange.io/oauth/adapters/snap/callback`.

## Endpoint reference

| Method | Path                                                       | Purpose                                                 |
| ------ | ---------------------------------------------------------- | ------------------------------------------------------- |
| `GET`  | `/storefronts`                                             | List storefronts (paginated)                            |
| `GET`  | `/storefronts/:storefrontId`                               | Get a single storefront                                 |
| `GET`  | `/storefronts/:storefrontId/capabilities`                  | Inspect source-level capability diagnostics             |
| `GET`  | `/storefronts/discover`                                    | Lightweight list of all storefronts (active + upcoming) |
| `GET`  | `/storefronts/interest`                                    | Check your interest submission status                   |
| `POST` | `/storefronts/interest`                                    | Submit interest in one or more storefronts              |
| `GET`  | `/storefronts/credentials`                                 | List your credentials across all storefronts            |
| `POST` | `/storefronts/:storefrontId/sources/:sourceId/credentials` | Register credentials for a source                       |
| `POST` | `/storefronts/:storefrontId/sources/:sourceId/accounts`    | Register an account (with advertiser linkage)           |

<Tip>
  When a storefront isn't returning products as expected, check `connected` via
  `GET /storefronts/:id` first. If it is `false`, credential or account setup is
  not complete. If it is `true`, use `GET /storefronts/:id/capabilities` for
  source-level diagnostics, then fall through to the
  [Discovery](/v2/guides/discovery) flow.
</Tip>

## Related

<CardGroup cols={2}>
  <Card title="Buyer onboarding" href="/v2/setup/buyer-onboarding" icon="user-plus">
    End-to-end setup for a new buyer account.
  </Card>

  <Card title="Discovery" href="/v2/guides/discovery" icon="magnifying-glass">
    Once your sources are connected, run discovery to find products.
  </Card>

  <Card title="Advertiser" href="/v2/object-guides/advertiser" icon="building">
    Advertisers are what you link to source accounts.
  </Card>

  <Card title="Storefront onboarding" href="/v2/setup/storefront-onboarding" icon="store">
    Operating a storefront? Start here for the seller-side setup.
  </Card>
</CardGroup>
