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

# How Apostra uses AdCP (buy side)

> How Apostra, as a buyer platform, uses the Ad Context Protocol to discover and transact across many sellers — and where it conforms, extends, or doesn't implement a surface.

Apostra is built on the [Ad Context Protocol (AdCP)](https://docs.adcontextprotocol.org). This page covers the
**buy side** — how Apostra, acting as a buyer platform, *consumes* AdCP across many sellers.

<Info>
  This is the buy-side view. For the seller (supply) side — connecting your own AdCP sales agent as a Seller Account
  source — see [Connect your sales agent](/v2/storefront/inventory-sources/connect-your-agent). When this page and
  the AdCP spec disagree on what the *protocol* means, the AdCP spec wins.
</Info>

<Warning>
  The external-buyer campaign spine described below is alpha and available only
  on Sellers with the Listing + Distribution package. It supports governed
  create, update, cancellation, and live delivery through platform-composed
  hosted Sellers and direct-adapter Sellers. Adding packages during an endpoint
  update, change webhooks, and scheduled reporting exports are not part of this
  slice.
</Warning>

## Two AdCP directions, one direct campaign mode

AdCP addresses one Seller at a time. Whether the Seller is reached through
an external buyer endpoint or a connected account, the campaign carries
`mode: "directed"` — a compatibility wire value; read the campaign's
`management` state for what the platform does with it:

| Direction                                                            | Source of truth                                                                              | Campaign meaning                                                                                                                         |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| An external AdCP buyer calls an Apostra-hosted Seller                | The Seller Account manages execution; Apostra supplies the commercial and reporting envelope | **Managed campaign** (`management: "managed"`) — `mode: "directed"`, legacy wire provenance `directed.provenance: "storefront_endpoint"` |
| An Apostra buyer connects and mirrors an external seller/ad platform | The external seller owns the campaign; Apostra reconciles a mirror                           | **Tracked campaign** (`management: "tracked"`) — `mode: "directed"`, `directed.provenance: "connected_account"`                          |

A task addressed directly to one Seller carries `mode: "directed"` because
Apostra does not perform cross-Seller discovery or allocation. A
discovery campaign can still finish with one seller when Apostra performed
that selection; the result count does not change its mode. See
[Campaigns on connected provider accounts](/v2/buyer/campaigns/directed-campaigns)
for the shared model.

You may also hear this called a *mediated AdCP campaign*. Mediation is the platform
invariant, so these docs use *inbound* when distinguishing the protocol direction.
Neither “inbound” nor “mediated” is a campaign `mode`.

## The cross-seller pattern

The defining difference between AdCP and Apostra on the buy side is the same everywhere: **AdCP defines a task
against one agent; Apostra runs it across many.** That fan-out is our core scaling layer.

| AdCP task (single seller)               | Apostra (many sellers)                                                                                                                                                                        |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_products`                          | **Get products across Sellers** — one canonical request fanned out to named or all connected Sellers, returned as a bounded canonical page with qualified IDs and optional proposal screening |
| `get_products` + session presentation   | **Discover Products** — the same raw fan-out projected into grouped products, summary/budget context, browse, and SSE; no separate ranking brain                                              |
| `get_signals`                           | cross-agent signal discovery                                                                                                                                                                  |
| `create_media_buy` / `update_media_buy` | `create_media_buys` and a **Campaign** cart that coordinate one bilateral media buy per seller                                                                                                |
| `get_media_buy_delivery`                | aggregated reporting rolled up across all sellers                                                                                                                                             |

An external AdCP client can also call one Apostra-hosted Seller directly.
That is deliberately a single-seller protocol interaction, while Apostra still
provides the buyer identity, contract, governance, audit, campaign, and ledger around
the seller call.

### Compliance at the edges, scale above them

Apostra does not ask a seller to understand a multi-seller request. The
buyer-facing `ext.interchange` object selects Sellers, controls progressive
polling, and optionally requests managed screening. Apostra then projects
the canonical part of the request onto separate one-to-one `get_products`
calls. Each seller returns its normal AdCP products/proposals; Apostra
validates that response independently before aggregation.

The aggregate page stays bounded rather than becoming one giant catalog:

* `pagination.max_results` caps proposal and proposal-less-product candidates;
* products referenced by a paged proposal travel with that proposal so its
  allocation graph is not split;
* revisioned snapshots expose fast sellers while slower sellers remain pending;
* cursors become stable only after all reachable sellers settle; and
* storefront-qualified `sf1:` / `sfp1:` identities prevent collisions and are
  preserved until the bilateral purchase edge.

Optional screening is also above the protocol. It evaluates each valid proposal
against buyer instructions and returns `accept`, `reject`, or `refine`. A refine
decision is sent back to the originating seller through canonical AdCP; only the
seller can change pricing, allocations, availability, or terms.

## Conformance (buy side)

**Status:** **1:1** conforms as written · **+ext** with an Apostra scaling layer · **none** not implemented yet.

| AdCP surface                                                                                                                                                                                                                                                                                                  | Status                 | What Apostra does                                                                                                                                                                                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`get_products`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/get_products)                                                                                                                                                                                                               | 1:1 edges / +ext buyer | Sends the canonical request separately to each selected Seller, then adds Seller selection, progressive aggregation, qualified identity, and optional proposal screening above those bilateral calls. See [Get products across Sellers](/v2/buyer/discovery/multi-storefront-get-products).                                                |
| [`sync_catalogs`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/sync_catalogs)                                                                                                                                                                                                             | 1:1                    | Matches the spec; the catalog is also injected into the discovery fan-out.                                                                                                                                                                                                                                                                 |
| [`create_media_buy`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/create_media_buy)                                                                                                                                                                                                       | +ext                   | Through the buyer API, a platform-managed **Campaign** can spawn one media buy per Seller. On an enrolled hosted-Seller endpoint, the strict AdCP request creates one governed campaign anchor—projected as `mode: directed` for compatibility—before Seller dispatch. See [Create a campaign](/v2/buyer/campaigns/tasks/create-campaign). |
| [`update_media_buy`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/update_media_buy)                                                                                                                                                                                                       | +ext                   | Campaign-level update cascade; a capability guard blocks updates an upstream agent can't support.                                                                                                                                                                                                                                          |
| [`get_media_buy_delivery`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/get_media_buy_delivery)                                                                                                                                                                                           | +ext                   | Aggregated reporting across all sellers.                                                                                                                                                                                                                                                                                                   |
| [`get_media_buys`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/get_media_buys)                                                                                                                                                                                                           | +ext                   | Campaign-granularity list; most-restrictive status rollup.                                                                                                                                                                                                                                                                                 |
| `list_creatives` / [`build_creative`](https://docs.adcontextprotocol.org/docs/creative/task-reference/build_creative)                                                                                                                                                                                         | +ext                   | Campaign-scoped manifests; conversational creative sessions.                                                                                                                                                                                                                                                                               |
| `get_signals`                                                                                                                                                                                                                                                                                                 | +ext                   | Cross-agent signal discovery.                                                                                                                                                                                                                                                                                                              |
| [`sync_audiences`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/sync_audiences) / [`sync_event_sources`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/sync_event_sources) / [`log_event`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/log_event) | +ext                   | First-party audience + event stores feeding the Measurement Engine.                                                                                                                                                                                                                                                                        |
| Measurement taxonomy                                                                                                                                                                                                                                                                                          | +ext                   | A Bayesian incrementality engine. See [Measurement & incrementality](/v2/guides/measurement-incrementality).                                                                                                                                                                                                                               |
| `list_accounts` / `sync_accounts`                                                                                                                                                                                                                                                                             | +ext / 1:1             | A three-stage buyer model (credentials → available → linked).                                                                                                                                                                                                                                                                              |
| [`provide_performance_feedback`](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/provide_performance_feedback)                                                                                                                                                                               | none                   | Not implemented; an internal optimization loop substitutes.                                                                                                                                                                                                                                                                                |
| [`get_creative_delivery`](https://docs.adcontextprotocol.org/docs/creative/task-reference/get_creative_delivery)                                                                                                                                                                                              | none                   | Not implemented; reporting currently stops at the package level.                                                                                                                                                                                                                                                                           |

## External buyer to Apostra to seller (alpha)

An external AdCP client follows the Seller's Discovery Card action. On a
verified Seller CNAME the endpoint is:

```text theme={null}
https://ads.seller.example/adcp/mcp
```

The platform-managed fallback is:

```text theme={null}
https://api.apostra.com/seller/{platformId}/mcp
```

`/adcp/mcp` is deliberately stable and unversioned. MCP/AdCP negotiates a
compatible protocol version directly, avoiding redirects for authenticated MCP
POST requests. Every hosted Seller has one deterministic endpoint. The endpoint
is the Seller's external demand surface for AdCP buyers: it exposes that
Seller's products and directed media-buy lifecycle, rather
than performing Apostra's cross-seller discovery or allocation. The
endpoint can exist before the Seller has Listing + Distribution; campaign-spine
write tasks remain fail-closed until that Seller package is active.

Use the normal authenticated AdCP 3.0/3.1 task lifecycle:

1. Call `get_adcp_capabilities` and `get_products` on that Seller.
2. Send `create_media_buy` with an `idempotency_key`, brand identity, flight,
   currency, and one or more explicitly budgeted packages.
3. Use the returned `media_buy_id` with `get_media_buys` and
   `get_media_buy_delivery`.

### Country and channel routing

For an external sales agent, Apostra reads portfolio coverage from
`get_adcp_capabilities.media_buy.portfolio`:

* `primary_channels` and `primary_countries` are the declared coverage for that
  source. Apostra keeps the two arrays correlated, and accepted countries
  equal primary countries.
* Coverage from separate sources is never combined to manufacture a match.

There is no separate local accepted-country allowlist for a pure pass-through storefront.
After the staged buyer-routing flag is enabled, Apostra includes a source
only when both primary arrays are complete and match the requested coverage. A
missing or malformed primary array is unknown and is a go-live readiness gap.
Before that flag is enabled, legacy product fan-out remains unchanged.

Missing primary coverage means **unknown**, not global coverage. Apostra
does not infer an upstream agent's countries from the storefront listing or
business profile.

### Linked account scope

When a request carries a linked storefront `account_id`, Apostra resolves
the authenticated buyer's grant before contacting any inventory source. Each
source receives only the account identifier the seller assigned to that source;
the linked Apostra account ID is never forwarded as if it were a source
account.

`get_products` returns structured AdCP errors when the account cannot be used:

| Code                  | `details.reason`              | What to do                                                                                                                                     |
| --------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_REQUIRED`       | `account_requires_principal`  | Authenticate as the buyer, or omit the account for public discovery.                                                                           |
| `AUTH_REQUIRED`       | `account_grant_inactive`      | Complete or restore account approval, then retry.                                                                                              |
| `NOT_FOUND`           | `account_context_not_found`   | Verify the account ID or request a new linked account. The response does not reveal whether an account belongs to another buyer or storefront. |
| `SERVICE_UNAVAILABLE` | `account_context_unavailable` | Retry the same request after a short delay.                                                                                                    |

Missing, ambiguous, or stale source mappings are excluded from discovery
without calling those sources. If every source required by `create_media_buy`
lacks a healthy mapping, the tool returns `ACCESS_DENIED` with
`details.reason: "source_account_not_bound"` and the coverage state in
`details.per_source`. The seller must repair the mapping; the buyer then runs
`get_products` again and submits a new media buy. Calls without a linked account
and legacy natural-key accounts are unchanged. A transient account-resolution
failure instead returns `SERVICE_UNAVAILABLE` with
`details.reason: "account_context_unavailable"`; retry the same request after a
short delay rather than changing the mapping.

The same linked account now scopes media-buy reads. Call `get_media_buys` with
the opaque `account_id` and no `media_buy_ids` to enumerate the mapped account
across every inventory source that has a healthy native-account mapping.
Apostra drains each source's pages within a bounded discovery window,
qualifies colliding native IDs into stable storefront `media_buy_id` values,
and persists the route so
`get_media_buy_delivery` can reach the same source later. Pass the same account
and buyer credential on the delivery call; Apostra substitutes the
source-native account again. To include terminal history, supply the desired
`status_filter` values—an omitted filter keeps each source's normal AdCP
default. A targeted `get_media_buys` call with `media_buy_ids` reads already
discovered storefront IDs and does not re-enumerate sources. Follow
`pagination.cursor` to read the complete combined result.

If one mapped source is unavailable, the completed response contains the buys
from healthy sources plus a generic `SOURCE_UNAVAILABLE` entry; it never exposes
the source name, native account ID, or provider error. If every mapped source is
unavailable and there is no persisted result to return, the task fails with
`SERVICE_UNAVAILABLE` and
`details.reason: "account_media_buy_sources_unavailable"`. Sources marked
`not_required` are not enumerated for account history: an unscoped campaign list
could expose other buyers. Account-enumerated buys are tracked, read-only
records; create a managed buy through the normal product and media-buy flow
instead of updating a discovered buy. Webhook activity is not included in this
surface because the upstream call uses the storefront's source credential, not
the linked buyer principal.

On a platform-composed storefront endpoint, the same linked account can list
audience, signal, and event-source references with Apostra MCP extension
`get_account_resources`. Direct adapter storefronts do not expose this tool.
This is a live, read-only call: Apostra verifies the active grant for the
authenticated buyer principal before contacting a source, forwards each
eligible source only its own native account reference, and returns opaque
storefront-qualified resource IDs. It never returns private source IDs,
source-native account IDs, audience membership, setup secrets, or event
payloads.

| Resource type  | Provider read                                                 | Authority and coverage                                                                                                                                                                                                                                                                           |
| -------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `signal`       | Account-scoped `get_signals` wholesale discovery              | Authoritative only when the provider explicitly reports an account cache scope. Missing or public cache scope is labeled non-authoritative and partial.                                                                                                                                          |
| `audience`     | Discovery-only `sync_audiences` (no audience payload)         | Returned references are authoritative, but coverage is labeled `partial` because provider enumeration differs. TikTok exhausts the selected advertiser's Customer File inventory; tenant-owned governed rows use canonical buyer IDs, while unmanaged rows remain provider inventory references. |
| `event_source` | Discovery-only `sync_event_sources` (no event-source payload) | Buyer-managed sources are returned as references. Seller-managed sources are excluded and counted in coverage. No event data or setup secret is returned.                                                                                                                                        |

Every source/resource-kind pair reports `capability`, `coverage`,
`authoritative`, `observed_at`, item/exclusion counts, truncation, and a generic
error code when a provider read fails. Missing, ambiguous, stale, and
`not_required` source mappings are never called. Demo or placeholder resources
are explicitly labeled non-authoritative. Results default to 50 resources and
accept `pagination.max_results` up to 100; follow the opaque cursor while
`pagination.has_more` is true. A request is also bounded by a 30-second live-read
window and a 1,000-resource safety ceiling, so `partial` or `truncated` must be
checked before treating the result as a complete account inventory.

This is different from the tracked-campaign `.../presences` endpoint. That
endpoint reads a background mirror for subscribed connection accounts;
`get_account_resources` uses the linked account's principal grant directly.

The same buyer credential must resolve to an Apostra buyer account. A
transacting credential should be scoped to an advertiser. During the alpha, an
unscoped credential may resolve by `brand.domain` only when that domain identifies
exactly one active advertiser in the buyer account; missing or ambiguous identity
fails closed.

### What Apostra adds before seller dispatch

For an enrolled relationship, media-buy writes do more than relay the protocol
request:

* checks a buyer/storefront currency policy plus per-buy and aggregate exposure caps;
* journals create, update, and cancellation idempotently so an uncertain seller
  outcome is not silently dispatched twice;
* creates the compatibility `mode: directed` campaign projection, media buy, and package records in the
  buyer account;
* pins the buyer's zero-fee AdCP entitlement terms for the life of the buy;
* commits the authorized amount to the campaign ledger after the seller accepts or
  begins processing the buy; and
* records dark alpha usage at zero billable amount.

Zero-billable alpha usage applies to the feature's usage meter, not to media. The
authorized media budget and zero-fee AdCP entitlement still govern the buy.

Every caller budget is **gross**: the all-in amount authorized by the buyer. Pure
AdCP access pins a zero fee, so gross and net are equal and the full media budget
reaches the seller. Ordinary buyer clearing fees are not inherited on this path;
advanced value is governed by entitlements/IUs. The seller never receives buyer
terms. See
[Budgets and fees](/v2/concepts/budgets-and-fees).

The route is dual-keyed. Apostra campaign/media-buy identity drives contract,
governance, and reporting records; the AdCP media-buy identity correlates the public
task response and downstream seller object. `get_media_buys` and
`get_media_buy_delivery` therefore remain addressable with the external buyer's
AdCP ID without making the seller's ID the buyer-account primary key.

### Current reporting behavior

`get_media_buy_delivery` is live read-through: Apostra calls the seller, returns
the current delivery response, and opportunistically records realized spend against
the pinned zero-fee entitlement ledger. The alpha does not promise a cached time series, change
webhooks, scheduled delivery, or billing UI from that read.

<Warning>
  Any future non-zero buyer-edge fee requires separate fee-inclusive delivery
  verification before rollout. The current alpha remains zero-fee and is not an invoice
  statement.
</Warning>

### Current eligibility and limits

* The buyer campaign spine follows the Seller's Listing + Distribution package
  and is fail-closed. It is not enabled buyer by buyer, and there is no
  environment-variable bypass.
* Every active Listing + Distribution storefront route creates the governed campaign, contract,
  operation, and ledger anchor before dispatch. This includes platform-composed
  storefronts and direct social/ad-platform `ADAPTER` storefronts. Wonderstruck ESA
  is the first live managed-agent proof; adapter routes also require a delegated,
  advertiser-mapped provider account.
* Seller rejection and non-retryable dispatch failures mark the campaign as failed.
  A temporary source or adapter outage remains pending while Apostra retries for
  up to six hours, then becomes failed if the retry window ends without a successful
  dispatch.
* The governed endpoint spine covers `create_media_buy`, supported
  `update_media_buy` fields, and cancellation. Update/cancel share the pinned policy,
  durable mutation journal, budget-delta caps, SCD2 activation, dark metering,
  ledger commitment sync, and ambiguous-outcome fence. Adding packages still returns a
  capability error.
* Strict SDK lifecycle testing and source-specific end-to-end verification are both
  required before enabling another Seller's distribution package.

## Where the truth lives

<CardGroup cols={2}>
  <Card title="AdCP specification" href="https://docs.adcontextprotocol.org" icon="book">
    The protocol's source of truth.
  </Card>

  <Card title="Connect your sales agent" href="/v2/storefront/inventory-sources/connect-your-agent" icon="plug">
    The seller (supply) side — bring your AdCP agent in as a storefront source.
  </Card>

  <Card title="Get products across storefronts" href="/v2/buyer/discovery/multi-storefront-get-products" icon="network-wired">
    Canonical AdCP-shaped retrieval with Apostra fan-out, screening, refinement, and purchase continuation.
  </Card>

  <Card title="Discover Products" href="/v2/buyer/discovery/tasks/discover-products" icon="magnifying-glass">
    The legacy-compatible grouped, browsable discovery-session projection.
  </Card>

  <Card title="Create a campaign" href="/v2/buyer/campaigns/tasks/create-campaign" icon="bullhorn">
    How a campaign spawns media buys across sellers.
  </Card>

  <Card title="Budgets and fees" href="/v2/concepts/budgets-and-fees" icon="sack-dollar">
    Why the buyer budget is gross while the seller receives net media spend.
  </Card>

  <Card title="Campaigns on connected provider accounts" href="/v2/buyer/campaigns/directed-campaigns" icon="arrows-rotate">
    Tracked campaign mirrors — the opposite ownership direction.
  </Card>
</CardGroup>
