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

# Frequency Caps

> Limit how often a single user is exposed to your ads across publishers

## Overview

A **frequency cap** limits how many times a single user can be exposed to your
advertising within a rolling time window. Caps are enforced by Scope3 across
every publisher running a given advertiser, campaign, or creative — so a user
who sees the cap-worth of impressions on one publisher will not see another
impression on a different publisher in the same window.

Scope3 stores caps as **buyer-side** configuration. They are distinct from any
`frequency_cap` a publisher may apply on its own inventory and travel with the
buyer's request as part of the targeting overlay.

<Tip>
  Buyer-side caps are about *your* exposure budget for a user — they are
  enforced even when you buy across publishers that have no view of each
  other's delivery.
</Tip>

<Note>
  Cross-publisher enforcement on a given seller's inventory requires that seller
  to be connected to real-time targeting — see
  [Real-time targeting (TMP)](/v2/storefront/tmp/overview). On inventory that
  isn't TMP-connected, the cap still applies to the buyer's overall exposure
  budget but cannot be enforced at that publisher's request time.
</Note>

## Concept

Every cap config is a triple of:

| Field             | Description                                                             |
| ----------------- | ----------------------------------------------------------------------- |
| `targetLevel`     | What entity the cap applies to: `ADVERTISER`, `CAMPAIGN`, or `CREATIVE` |
| `max_impressions` | Maximum number of impressions allowed in the window                     |
| `window`          | Rolling window expressed as an AdCP `Duration` (`{ interval, unit }`)   |

`window.unit` accepts these values:

| Unit       | Use it for                            |
| ---------- | ------------------------------------- |
| `minutes`  | Minute-level pacing                   |
| `hours`    | "No more than 2 impressions per hour" |
| `days`     | "No more than 5 impressions per day"  |
| `campaign` | Lifetime cap for the campaign         |

`max_impressions` is a positive integer.

### Buyer-side vs publisher-side

The schema deliberately omits the publisher-side fields from the AdCP
`FrequencyCap` shape (`suppress`, `suppress_minutes`, `per`). Those are
suppression instructions that publishers act on inside their own ad server.
Buyer-side caps care only about how many impressions you'll spend on a single
user — Scope3 picks the reach unit, not you.

### Cascade across levels

Caps are applied at the level you set them on:

* **Advertiser-level** caps apply to every campaign on the advertiser.
* **Campaign-level** caps apply to every media buy and creative inside that
  campaign.
* **Creative-level** caps apply to that single creative across every campaign
  it runs in.

When more than one cap matches a request, **all** caps must be satisfied for
the impression to serve. The strictest cap wins.

## How to use

Frequency caps are not a standalone REST resource — they are embedded into the
parent advertiser, campaign, or creative endpoints. To change the caps on an
entity, send the full array you want stored. **PUT replaces the full array.**

### On campaign create

```bash theme={null}
curl -X POST 'https://api.interchange.io/api/v2/buyer/campaigns' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "advertiserId": "12345",
    "name": "Spring Launch",
    "flightDates": { "startDate": "2026-05-01T00:00:00Z", "endDate": "2026-05-31T23:59:59Z" },
    "budget": { "total": 50000, "currency": "USD" },
    "frequencyCaps": [
      { "max_impressions": 3, "window": { "interval": 1, "unit": "days" } },
      { "max_impressions": 10, "window": { "interval": 7, "unit": "days" } }
    ]
  }'
```

This sets two caps that both apply: a user sees at most 3 impressions per day
**and** at most 10 impressions per week.

### On campaign update

```bash theme={null}
curl -X PUT 'https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "frequencyCaps": [
      { "max_impressions": 2, "window": { "interval": 12, "unit": "hours" } }
    ]
  }'
```

<Warning>
  Sending `frequencyCaps` on update **replaces** all previously stored caps
  for that target. To clear caps entirely, send an empty array. Omit the
  field to leave existing caps untouched.
</Warning>

### On the advertiser

```bash theme={null}
curl -X PUT 'https://api.interchange.io/api/v2/buyer/advertisers/12345' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "frequencyCaps": [
      { "max_impressions": 20, "window": { "interval": 7, "unit": "days" } }
    ]
  }'
```

This puts a brand-wide ceiling: no user sees more than 20 impressions for
this advertiser in any 7-day window, regardless of which campaign serves
them.

### On a creative

```bash theme={null}
curl -X PUT 'https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321/creatives/cm_abcdef' \
  -H 'Authorization: Bearer scope3_<your_api_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "frequencyCaps": [
      { "max_impressions": 1, "window": { "interval": 1, "unit": "days" } }
    ]
  }'
```

Useful for "first-impression" creatives that should run at most once per user
per day.

## Response shape

`GET` responses on advertiser, campaign, or creative include the caps with
their full server-assigned metadata:

```json theme={null}
{
  "frequencyCaps": [
    {
      "id": "12345",
      "targetLevel": "CAMPAIGN",
      "targetId": "cmp_987654321",
      "max_impressions": 3,
      "window": { "interval": 1, "unit": "days" },
      "createdAt": "2026-04-25T10:30:00.000Z",
      "updatedAt": "2026-04-25T10:30:00.000Z",
      "archivedAt": null
    }
  ]
}
```

## Behavior

<Steps>
  <Step title="Replace, don't merge">
    `replaceForTarget` archives every existing cap for the
    `(customer_id, target_level, target_id)` triple, then inserts the new set
    in a single transaction. There is no partial update.
  </Step>

  <Step title="Archive, don't delete">
    Old caps stay in the database with `archived_at` set. They are excluded
    from API responses but preserved for audit and reporting.
  </Step>

  <Step title="All caps must pass">
    When multiple caps apply to a request, the impression serves only if every
    cap allows it. There is no priority or override.
  </Step>

  <Step title="Cap counts at the configured level">
    A `CAMPAIGN` cap counts impressions on that campaign only. An `ADVERTISER`
    cap counts impressions across every campaign on that advertiser.
  </Step>
</Steps>

## Best practices

* **Layer caps from broad to narrow.** Set an advertiser-level lifetime cap
  for brand exposure, then add tighter campaign-level caps for short flights.
* **Keep windows in human units.** `{ interval: 1, unit: "days" }` is easier
  to reason about than `{ interval: 1440, unit: "minutes" }`.
* **Use `campaign` unit for lifetime caps.** Don't try to express "for the
  entire campaign" as a multi-week `days` window — use `unit: "campaign"`.
* **Empty array clears caps.** To remove all caps from a target, send
  `"frequencyCaps": []`. Omitting the field leaves them in place.
* **Don't fight the publisher.** Publisher-side suppression in
  `target_overlay` is a separate feature; if you need to enforce that a user
  doesn't see your ad in a given session, work with the agent rather than
  layering more buyer-side caps.

## Limits

| Limit             | Value                                              |
| ----------------- | -------------------------------------------------- |
| Levels available  | `ADVERTISER`, `CAMPAIGN`, `CREATIVE`               |
| Window units      | `minutes`, `hours`, `days`, `campaign`             |
| `max_impressions` | Positive integer                                   |
| Caps per target   | No hard limit, but each cap layers — keep it small |
