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

# Media-buy Frequency Caps

> Apply an AdCP 3.2 frequency cap independently to each seller media buy in a V3 campaign

## Overview

Use `frequencyCap` on V3 `save_campaign` to limit exposure within each seller's
media buy. The seller maintains one counter across every participating package
in that media buy, following the AdCP 3.2 aggregate frequency-capping contract.

Existing campaigns are unchanged. The field is optional and is only available
while the campaign is a draft.

<Warning>
  This is not a cross-seller campaign cap. Each seller maintains its own
  counter. If a campaign buys from two sellers with a cap of three impressions
  per individual, the same individual could receive three impressions from
  each seller.
</Warning>

## Set the cap

Pass the cap when creating a campaign, or update it before launch:

```json theme={null}
{
  "campaignId": "CAMPAIGN_ID",
  "frequencyCap": {
    "level": "mediaBuy",
    "config": {
      "maxImpressions": 3,
      "per": "individuals",
      "window": {
        "interval": 7,
        "unit": "days"
      }
    }
  },
  "idempotencyKey": "campaign-frequency-cap-v1"
}
```

| Field                   | Description                                                                             |
| ----------------------- | --------------------------------------------------------------------------------------- |
| `level`                 | Must be `mediaBuy`. Other levels are rejected.                                          |
| `config.maxImpressions` | Maximum impressions in the configured window.                                           |
| `config.per`            | Reach unit: `individuals`, `households`, `devices`, `accounts`, `cookies`, or `custom`. |
| `config.window`         | Positive interval with a unit of `seconds`, `minutes`, `hours`, `days`, or `campaign`.  |

Pass `frequencyCap: null` to clear the cap from a draft before accepting a
proposal. Omit the field to leave the current value unchanged.

## Seller and product eligibility

When a cap is present, `request_proposals` asks sellers for products that can
participate in the requested shared media-buy counter. A seller must declare
the AdCP 3.2 `media_buy.aggregate_frequency_capping` capability, and every
selected product must declare compatible `media_buy_support.frequency_cap`
support.

A seller proposal must also state the exact cap it is offering. If it leaves
the cap out or changes it, that proposal is not shown as a usable quote. The
seller's compatible products may still be available for a direct MediaBuy
request, which must confirm the cap when created.

The campaign launch rechecks those declarations against the exact selected
products. It fails before seller dispatch if support is missing or the cap is
outside a seller or product constraint.

## Cap levels

| Level     | Counter scope                                               | V3 campaign support                                                                                  |
| --------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Campaign  | Shared across every seller in the campaign                  | Not available. This requires cross-seller identity and counter coordination.                         |
| Media buy | Shared across participating packages bought from one seller | Available through `save_campaign.frequencyCap` with `level: "mediaBuy"`.                             |
| Package   | Maintained for one package                                  | Available on `save_media_buy.products[].targetingOverlay.frequency_cap` when the seller supports it. |

Selecting one seller does not change the declared level to `campaign`. It
remains a media-buy cap, which keeps the saved contract stable if cross-seller
campaign capping is added later.

## Read and update behavior

The campaign workspace returned by `save_campaign` and
`get(kind: "campaign")` includes the saved `frequencyCap`. The value is the
campaign's requested media-buy policy. Each seller's completed AdCP 3.2 create
response must echo the effective root `frequency_cap` before synchronous or
asynchronous creation is treated as capped. `get(kind: "media_buy")` then
returns `frequencyCap.status: "confirmed"` with the seller's effective cap.

Package caps are independent. Put the AdCP `frequency_cap` directly on one
product selection when only that package should count exposures:

```json theme={null}
{
  "campaignId": "cmp_123",
  "sellerId": "42",
  "products": [
    {
      "productId": "sf1:42:reach",
      "budget": 5000,
      "targetingOverlay": {
        "frequency_cap": {
          "max_impressions": 3,
          "per": "individuals",
          "window": { "interval": 7, "unit": "days" }
        }
      }
    }
  ],
  "idempotencyKey": "package-frequency-cap-v1"
}
```

The package cap is sent only on that package. It is not combined with, or
promoted into, the MediaBuy-wide counter.

On Meta and Pinterest, a capped package must resolve to at most one offering
(one ad set or ad group). Each offering would otherwise have a separate
provider counter. If a package resolves to several offerings, the request
fails before creating the provider buy; separate the offerings into packages
or remove the package cap.

Package-cap shapes are seller-specific. V3 accepts the AdCP 3.2 cooldown
controls (`suppress` or deprecated `suppress_minutes`) and the
`max_impressions`/`per`/`window` control. The seller advertises package-cap
targeting through `media_buy.execution.targeting.frequency_cap` and must reject
any shape it cannot represent exactly.

Live cap changes are not available yet. A saved MediaBuy draft keeps the cap
it had when it was created, even if the campaign's draft cap changes later.
MediaBuys saved before this feature remain uncapped.
To use the new cap, remove the old draft, request proposals again, and create a
new MediaBuy. Once a proposal is accepted and bound to a MediaBuy, remove that
buy before changing the cap. Launch rejects a buy if its saved cap differs
from the cap used for the accepted proposal.
