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

# How Semicola uses AdCP

> AdCP defines a task against one seller; Semicola runs it across many. What the buy side sends, what a hosted storefront answers, and how versions are negotiated.

Semicola is built on the [Ad Context Protocol (AdCP)](https://docs.adcontextprotocol.org). When
this page and the AdCP spec disagree about what the protocol means, the spec wins.

## Two directions

| Direction                                                    | Who owns the campaign                                       | Campaign `management` |
| ------------------------------------------------------------ | ----------------------------------------------------------- | --------------------- |
| A Semicola buyer buys from sellers through Semicola          | Semicola runs the campaign and one media buy per seller.    | `managed`             |
| A Semicola buyer mirrors a campaign that lives on a platform | The platform owns it; Semicola reconciles a read-only copy. | `tracked`             |

Separately, any AdCP buyer (Semicola or not) can call a storefront Semicola hosts. That's always a
single-seller conversation.

## The cross-seller pattern

AdCP addresses one agent at a time. Semicola fans the same task out across your connected sellers and
keeps each seller call a normal, one-to-one AdCP call.

| AdCP task (one seller)                    | Semicola (many sellers)                                                                        |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `get_products`                            | `request_proposals`: the campaign brief goes to every eligible seller.                         |
| `create_media_buy` / `update_media_buy`   | A campaign holds one media buy per seller; `save_media_buy` stages them and launch sends them. |
| `get_media_buy_delivery`                  | `get_delivery` and reporting roll delivery up across sellers.                                  |
| `sync_creatives`, `list_creative_formats` | Creatives are synced to each seller a buy uses.                                                |

A seller never sees a multi-seller request. Each seller's answer is validated on its own before it's
combined, and only the seller can change its pricing, allocations or terms: refining a proposal goes
back to the seller that made it.

## What a hosted storefront answers

Every storefront Semicola hosts is an AdCP sales agent at `/seller/<storefront>/mcp`. It answers
`get_adcp_capabilities`, `get_products`, `list_creative_formats`, `create_media_buy`,
`update_media_buy`, `get_media_buys`, `get_media_buy_delivery`, `sync_creatives`, `list_creatives`,
`provide_performance_feedback`, `sync_event_sources`, `log_event` and `sync_audiences`.

A hosted storefront declares conversion tracking and audience targeting (hashed email and phone;
the `audience-sync` specialism), so buyers can share measurement data with it. It keeps what it
receives per storefront, buyer account and brand:

* **`sync_event_sources`** registers a buyer's source and answers with the storefront's own id for
  it (`seller_id`). It honors `delete_missing` and discovery-only calls.
* **`log_event`** needs an `event_id`, a type the source accepts and a valid time (more than an hour
  ahead is refused). It drops a repeated `event_id` for the same type and source and keeps each other
  event's type, time, value and currency, never user identifiers. It takes the storefront's id for the
  source, or the buyer's id when that names one source; otherwise it answers `REFERENCE_NOT_FOUND`.
* **`sync_audiences`** counts members by the buyer's `external_id`. Hashed identifiers are checked for
  shape and not kept. With no identity graph to match against, every audience stays `processing` and
  no match count is ever reported.

Sellers can't see these events or audiences in the app yet.

Callers need a credential. Semicola's own buyer gateway calls hosted storefronts with a platform
buyer credential, plus the sandbox flag for sandbox accounts; production refuses anonymous calls.
New media buys go through the seller's approval gate, sandbox or not (see
[Approvals & reviews](/sell/approvals)).

## Versions

AdCP negotiates at release precision (`MAJOR.MINOR`, like `3.1`); patches aren't negotiated.

| Field                                  | Sent by                            | Meaning                                                                   |
| -------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------- |
| `adcp_version`                         | Buyer (request), seller (response) | On a request, the release the caller pins; on a response, the one served. |
| `adcp_major_version`                   | Buyer                              | The older integer form (`3`), still honored through 3.x.                  |
| `major_versions`, `supported_versions` | Seller (`get_adcp_capabilities`)   | What the seller speaks.                                                   |

The rule: a seller **downshifts** a pin to the highest release it supports at or below the pin within
the same major, and serves the request. `VERSION_UNSUPPORTED` is only for a different major.

| Buyer pins | Seller supports | Correct behavior                           |
| ---------- | --------------- | ------------------------------------------ |
| `3.1`      | `3.0`           | Downshift to `3.0` and serve; not an error |
| `3.1`      | only `2.x`      | `VERSION_UNSUPPORTED`                      |

Semicola's AdCP client pins **3.1** and sends `adcp_major_version` alongside `adcp_version` for older
sellers. When Semicola probes a seller, it records the seller's advertised major versions and its
`supported_versions`. Hosted storefronts advertise `supported_versions: ["3.0", "3.1"]` in
`get_adcp_capabilities` and serve a `3.0` pin by downshifting.

### Request shape is checked separately

Negotiating a release and validating a request's shape are separate. A 3.2 client can't send the
account-identity fields AdCP 3.2 introduced (`operator_unit`, `currency`, `timezone`,
`brand.countries`) to a storefront that serves only 3.0 and 3.1 and expect them to be dropped:
dropping them could merge two distinct advertiser accounts. So a hosted storefront refuses such a
`sync_accounts` call with `VERSION_UNSUPPORTED` before writing anything. `field` names the first
unsupported value, and `details` carries:

```json theme={null}
{
  "reason": "adcp_3_2_account_identity_not_supported",
  "unsupported_fields": ["accounts[0].currency"],
  "supported_versions": ["3.0", "3.1"]
}
```

That isn't a refusal to negotiate. Retry without the 3.2 fields, using an existing 3.0/3.1 account
binding, or wait until `get_adcp_capabilities` advertises 3.2.

If a seller returns `VERSION_UNSUPPORTED` for a same-major request, that's a stale seller build; the
seller has to upgrade its AdCP server.

## Not available yet

* Signals discovery across sellers (`get_signals`); see [Signals](/sell/signals).
* A cross-seller `get_products` page with seller-qualified ids, progressive polling and screening
  (`accept` / `reject` / `refine`) for external buyers.
* Account setup with sellers over AdCP. The buy side doesn't call `list_accounts` or `sync_accounts`,
  and a hosted storefront answers them with the caller's own account without creating or approving one.
* Performance feedback (`provide_performance_feedback`) from the buy side: we don't send it. A hosted
  storefront stores what buyers send (one row per `idempotency_key`), but nothing reads it yet.
* Hosted storefronts answering `get_signals`.
