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 anyfrequency_cap a publisher may apply on its own inventory and travel with the
buyer’s request as part of the targeting overlay.
Concept
Every cap config is a triple of:| Field | Description |
|---|---|
targetLevel | What entity the cap binds 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 AdCPFrequencyCap 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.
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
On campaign update
On the advertiser
On a creative
Response shape
GET responses on advertiser, campaign, or creative include the caps with
their full server-assigned metadata:
Behavior
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.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.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.
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
campaignunit for lifetime caps. Don’t try to express “for the entire campaign” as a multi-weekdayswindow — useunit: "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_overlayis 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 |