A campaign capped at three impressions per user per day still shows up in a fourth session. The first instinct is to blame the counter: a race condition, a stale cache, a bug in the bidder’s Redis. That instinct is usually wrong. The counter is doing exactly what it was built to do. The problem is that the counter was never asked to see the fourth impression.
Frequency caps don’t leak because one counter fails. They leak because cap enforcement is scoped to the system that hosts the counter, and the programmatic stack has more than one such system. Google’s own Authorized Buyers documentation states the boundary plainly: all Google-hosted frequency capping enforcement is scoped to impressions served by Google, and impressions served by other exchanges can’t be taken into account. That sentence is the mechanism. Everything else in this article is unpacking what it means for the way you configure caps.
Two halves of a Google-hosted cap
Google-hosted frequency capping splits into a declaration and an enforcement step, and the documentation describes both.
The declaration lives in the bid response, under ext.fcap. Each cap carries four fields: fcap_id (the unit being capped—campaign, creative, line item, advertiser, or whatever label you choose, up to 64 characters), time_unit (MINUTE, DAY, WEEK, MONTH, or INDEFINITE), time_range (how many time units the window spans), and max_imp (the impression limit). A bid response from Google’s own example looks like this:
{
"seatbid": [{
"bid": [{
"ext": {
"fcap": [{
"fcap_id": "video-campaign-US",
"time_unit": "WEEK",
"time_range": 1,
"max_imp": 10
}]
}
}]
}]
}
The enforcement step happens before the auction. Google’s documentation is explicit that a bid violating its declared cap is prevented from winning even if it carries the highest CPM. The cap is not a post-auction filter or a reporting annotation; it removes the bid from competition.
That design is worth pausing on. It means the cap is a property of the bid, not a property of the user. The bidder declares what it wants enforced, and the exchange enforces it against the population of impressions the exchange can see. The question that determines whether the cap holds is therefore not “is the cap set?” but “what population can the enforcing system observe?”
The scope field bounds the counter
Google’s documentation describes a bid-request field, frequency_capping_scope (called fcap_scope in the OpenRTB integration guide), that tells the bidder what scope of enforcement the request supports. The documented values are App, Browser, Device, Site, and None.
- App — caps are enforced within a single app. A user who switches apps can see additional impressions.
- Browser — caps are enforced across sites within the same browser.
- Device — caps are enforced across multiple apps (excluding browsers) on the same device.
- Site — caps are enforced within a single site. The documentation’s own example notes the user might see more impressions on other sites.
- None — frequency capping is not supported for the request, and bids containing frequency caps are filtered before the auction.
Read those definitions carefully. Each one describes a boundary, and every boundary excludes something. Site scope excludes other sites. App scope excludes other apps. Device scope excludes browsers. The scope field is not a statement about the user; it is a statement about the fence around the counter.
The None case is the cleanest diagnostic. If a request arrives with scope None and your bidder attaches a cap anyway, the bid is filtered before the auction—not because the user hit the cap, but because the request cannot support cap enforcement at all. A bidder that treats every request as cap-eligible will lose auctions it never needed to lose.
The load-bearing sentence
Here is the line from Google’s documentation that explains most cross-exchange over-delivery:
Regardless of the indicated scope, all frequency capping enforcement is scoped to impressions served by Google. Impressions served by other exchanges can’t be taken into account when determining if the impression limit has been met.
Two things follow from that sentence, and they are worth separating.
First, the scope field and the exchange boundary are different constraints. Scope tells you whether the counter spans sites, apps, devices, or a browser. The exchange boundary tells you that no matter which scope you pick, the counter only counts impressions Google served. A device-scoped cap of five is a cap of five Google-served impressions on that device. Impressions the same user saw through another exchange are invisible to the counter.
Second, this is documented behavior, not a defect. The documentation does not describe a bug, a race, or a failure mode. It describes a boundary. If your campaign over-delivers relative to a user-level cap, the first hypothesis to test is not “the counter is broken” but “the counter never saw the other impressions.”
The identifier dependency
The framework has a second dependency the documentation names directly: Google-hosted capping “depends on Google having access to a stable user identifier, such as a third-party cookie or a first-party cookie.”
That dependency matters because it determines whether the counter can resolve at all. Google’s identity-solutions documentation includes a table of available identity solutions and marks which ones aid frequency management and capping. The table is a pointer, not a guarantee: it tells you which solutions are designed to support capping, not that any particular integration closes the cross-exchange gap. The gap described in the previous section is about which impressions the counter can see, and an identity solution that helps Google recognize a user does not by itself make another exchange’s impressions visible to Google’s counter.
When the identifier is unavailable or the lookup fails, the documentation lists a distinct rejection reason: “Frequency cap lookup failure.” That is not the same as a cap being met. One means the user has seen enough; the other means the system could not determine what the user has seen. Conflating them hides where the over-delivery originates. If your logs show lookup failures, you have an identity-resolution problem. If your logs show caps being met inside Google but over-delivery across the open internet, you have a scope problem. They require different fixes.
Rejection reasons as instrumentation
Google’s documentation lists the conditions under which a bid with frequency caps is rejected for malformed configuration. These are worth treating as a checklist rather than trivia:
- More than 10 frequency caps on a single bid.
- An
fcap_idlonger than 64 characters. max_impless than 1.time_rangeless than 1.time_unitnot specified.- Any of the bid response frequency capping fields missing.
Each of these produces a filtered bid, and a filtered bid is a lost auction that may look like a cap hit in aggregate reporting if you are not reading the rejection reason. The documentation also notes that caps are applied to each bid independently, and that a bidder can include additional bids as alternates in case a capped bid is rejected. That is a mitigation for a single rejected bid. It does not create a counter that spans exchanges, so it should not be presented as a fix for scope leakage.
What OpenRTB 2.6 does and does not say
It is tempting to look for the answer in the protocol. OpenRTB 2.6, released in April 2022 and drafted by the IAB Tech Lab’s Programmatic Supply Chain Working Group, defines the exchange as “a service that conducts an auction among bidders per impression.” It defines win notices as informing the bidder’s pricing algorithms of a success, and billing notices as indicating that spend should actually be applied. Those definitions describe the auction and the settlement events.
What the retrieved OpenRTB 2.6 material does not establish is a standard frequency-capping field or cross-exchange cap semantics. The ext.fcap structure in Google’s documentation is an exchange-specific extension, not a core OpenRTB object. That matters for the argument: the protocol standardizes the auction and the notices, but it does not standardize a shared counter across exchanges. Cap synchronization, to the extent it exists, is a bilateral arrangement between parties, not a property of the spec. If you are auditing a non-Google exchange, you cannot assume its cap semantics match Google’s, and the retrieved sources do not describe how those exchanges implement or reconcile caps.
A practical audit: enumerate the counters
The useful reframing is this: a frequency cap is a contract with one counter, not a guarantee about a user. The documented scope rule means the unit of enforcement is “impressions served by this exchange,” so over-delivery across exchanges is a predictable consequence of where the counter lives.
That leads to an audit procedure that does not depend on any particular vendor’s internals:
- List every system that can serve the same user. Exchanges, SSPs, ad servers, direct deals, and any mediation layer that can render an impression.
- For each system, identify the counter. Where does the impression count live? Is it declared in the bid response, enforced server-side, or tracked client-side?
- For each counter, identify its scope and its identifier. What population does it see (site, app, device, browser, exchange-wide)? What identifier does it key on, and is that identifier stable across the systems in step 1?
- Find the pairs that share neither a scope nor an identifier. Those are the places where the same user can accumulate impressions against separate counters that never reconcile.
As a labeled illustration of the scope logic—not a measured result—consider a user reachable through two exchanges, each enforcing a site-scoped cap of three. Under the documented rule that each exchange counts only its own served impressions, each exchange can independently allow up to three impressions on that site unless a shared counter exists. The user-level total can exceed three without either counter malfunctioning. The number three is arbitrary; the structure is the point.
What the sources do not settle
Several questions matter for a real audit and are not answered by the documentation retrieved for this article. They should be verified against your own stack rather than assumed:
- How, if at all, frequency caps are reconciled across multiple exchanges or SSPs for the same user. The Google documentation explicitly excludes other exchanges’ impressions from its count; it does not describe a reconciliation mechanism.
- How non-Google exchanges implement and reconcile frequency caps. The retrieved sources do not establish this, and behavior should not be generalized from Google’s documentation.
- What “frequency cap lookup failure” means operationally beyond the listed rejection reason, and how it differs from a cap being met in a specific integration.
- Whether any particular identity solution closes the cross-exchange gap in a given integration. The identity-solutions table marks which solutions aid frequency management and capping; it does not establish that any one of them makes another exchange’s impressions visible to Google’s counter.
The defensible conclusion is narrow and useful: when a cap appears to leak, check the scope before you check the code. The counter is probably working. It is probably counting exactly the impressions it was built to see—and no others.