Skip to main content

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

Concept

Every cap config is a triple of:
FieldDescription
targetLevelWhat entity the cap binds to: ADVERTISER, CAMPAIGN, or CREATIVE
max_impressionsMaximum number of impressions allowed in the window
windowRolling window expressed as an AdCP Duration ({ interval, unit })
window.unit accepts these values:
UnitUse it for
minutesMinute-level pacing
hours”No more than 2 impressions per hour”
days”No more than 5 impressions per day”
campaignLifetime 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

curl -X POST 'https://api.agentic.scope3.com/api/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

curl -X PUT 'https://api.agentic.scope3.com/api/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" } }
    ]
  }'
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.

On the advertiser

curl -X PUT 'https://api.agentic.scope3.com/api/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

curl -X PUT 'https://api.agentic.scope3.com/api/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:
{
  "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

1

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

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

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

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.

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

LimitValue
Levels availableADVERTISER, CAMPAIGN, CREATIVE
Window unitsminutes, hours, days, campaign
max_impressionsPositive integer
Caps per targetNo hard limit, but each cap layers — keep it small