Protected Audience frequency capping

Frequency capping is an advertising practice that limits the number of ads from a given category that are shown to a user within a given time period. Frequency capping improves the end-user experience by keeping ad impressions fresh and interesting, and helps advertisers manage ad spend.

This proposal introduces how Protected Audience on Android can be used to implement frequency capping functionality in an accurate and privacy-preserving way.

Protected Audience implements frequency capping by combining two features: The on-device storage of counters for ad-specific events, and the ability to filter ads according to a predefined set of filter strategies. Frequency capping enables advertisers to indicate a counter threshold over a sum of histogram values for a given time period.

Counters are unique for each combination of device profile, ad tech, and counter key. Each ad should contain a set of counter keys to use in case a view or impression for the ad is registered. For each key, Protected Audience stores a set of counters, and each counter tallies all ad-specific events that occur within a specific time interval. On-device counters are incremented when an impression or view occurs, and counter data will be persisted on device. The exact persistence time will be defined later.

The ad filtering logic in Protected Audience's ad selection workflow has access to counters, remarketing ads, and contextual ads, giving Protected Audience frequency capping the ability to work with all such types of ad requests.

Note: Ad filtering is only available in the Privacy Sandbox on Android. Chrome's Protected Audience implementation does not currently implement a mechanism for filtering contextually-targeted non-Protected Audience ads. This proposal covers buy-side support only. If there is demand, we will add sell-side support at a later date.

Protected Audience frequency capping supports a broad range of requirements, including:

  • Real-time filtering, with minimal server-side delay when on-device counters are updated.
  • Flexible hierarchy of keys, including individual ads, campaigns, or any other grouping.
  • Congruence with other frequency capping methods, without dependency on AdID.
  • Works across apps on a given device user profile.
  • Accurate and complete counters.
  • Support for custom definitions of ad events, such as views or impressions.
  • One function for both remarketing and contextual ads.

To set up frequency capping, follow these steps:

Step 1: Add frequency capping information to ads

Contextual and remarketing ads indicate relevant histogram counters to update in case of a view or impression using the ad_counter_keys field that contains a list of arbitrary integer. The field is not included in the metadata field that is not parsed by Protected Audience.

The following example shows the data format for the adsData field in AdSelectionConfig. For remarketing, the format of the list of ads for a given custom audience is consistent with the content of the ads field shown in the following example:

'adsData': [
    "buyer": "",
    "ads": [
        'render_url': 'exampleUrl',
        'metadata': {...},   /* metadata are opaque to Protected Audience are
                                required to be in valid JSON format */
        'ad_counter_keys': [1234, 5678]

Step 2: Register a view or impression

Ad techs can invoke the updateAdCounterHistogram method to register occurrences of events that are used for frequency capping. A method can be invoked repeatedly on the same event for keys specified in the winning ad's eventType.

void updateAdCounterHistogram(@EventType eventType, long adSelectionId)


  • eventType: Identifies whether an event is counted as a view, an impression, a click, or the win of the ad selection process.
  • adSelectionId: ID values in the AdSelectionOutcome object that are returned by selectAds calls.

The updateAdCounterHistogram call updates the histogram for the set of keys defined as part of either the remarketing ads fetched by a CustomAudience or the contextual ads included in the AdSelectionConfig parameter for selectAds.

If you assume that the ad in Step 1 is the winner of an AdSelection with an id value of 9999, a call to updateAdCounterHistogram(FrequencyCapFilters.AD_EVENT_TYPE_VIEW, adSelectionId: 999) increments the counters for the following three primary keys:

  • {'', 1234, VIEW}
  • {'', 5678, VIEW}

The ad tech name is taken from the buyer field, either from contextual ads or from custom audiences, depending on where the winning ads come from.

Protected Audience for Android automatically increments all the counters mentioned above for the event type FrequencyCapFilters.AD_EVENT_TYPE_WIN for ads returned by a selectAds API call. This is functionally equivalent to the addition of the prev_wins argument to browser_signals in generateBid in Chrome's Protected Audience implementation.

Step 3: Implement frequency cap filtering with filters

For optimal performance, the frequency cap filtering function is executed within AdServices. Protected Audience understands if a message has to be filtered by reading the filters field in the AdsData object. A list of filters is specified in frequency_cap. The values for key, event_type and interval_in_seconds are used to retrieve a histogram of events that are used for filtering and Protected Audience.

Filtering information can be specified for remarketing ads provided by a custom audience and for contextual ads as part of the AdSelectionConfig object.

For contextual ads with frequency cap filters, ads are passed in using the ads field in the AdSelectionConfig object. Ads are filtered, and the ad with the highest bid is returned as the result of the selectAds call.

For remarketing ads with frequency cap filters, ads are filtered before the buyer-provided generateBid() JavaScript function is invoked.

The following example shows a message with frequency cap filtering:

  'render_url': 'url',
  'metadata': {...},   /* metadata are opaque to Protected Audience and assumed
                        to be in valid JSON format */

  'ad_counter_keys': [1234, 5678],

  "filters": {
    "frequency_cap": {
      "view": [
          "ad_counter_key": 1234
          "max_count": 10,
          "interval_in_seconds": 86400
          "ad_counter_key": 5678
          "max_count": 10,
          "interval_in_seconds": 86400
      "win": [
          "ad_counter_key": 1234
          "max_count": 5,
          "interval_in_seconds": 604800
          "ad_counter_key": 5678
          "max_count": 5,
          "interval_in_seconds": 345600

  // This field is only required in contextual ads and is used in
  // reportImpression calls to fetch the reportWin function.
  'reportingJS': ""

Step 4: Report on winning Ads

Once the ad selection process is complete, it returns an AdSelectionOutcome object containing the renderUri and adSelectionId, a numeric identifier for the selectAds call. This ID can be used to invoke the reportImpression API that currently supports event-level reporting. In Beta 1, this method supports reporting for remarketing ads, and will be extended to support reporting for contextual ads in a later release. For contextual ads, the buyer is required to indicate where the reportWin function can be retrieved during a reportImpression call by using an extra field called reportingJS in the ad structure, as shown in the example above.

Best practices for selecting ad candidates

Protected Audience moves the enforcement of frequency capping from the server to the device. Although winning bids are reported with the Privacy Sandbox, developers won't know why an ad isn't shown. Ads might not be shown due to a lost bid, or due to frequency capping. With no full visibility into the reasons why certain ads don't win, bidding systems require additional work to ensure optimal ads are served. These best practices will help ensure optimal ad serving with Protected Audience.

Send enough remarketing ads

Remarketing ads cannot be optimized per user. If a user sees a significant number of ads from a custom audience and the ad limits are low, all ads may be filtered out. Remarketing ads are refreshed periodically, so enough ad inventory should pass through frequency capping to ensure remarketing ads continue to be served. This needs to be balanced with limitations on the size of the ads that can be specified during the joinCustomAudience call, and during the custom audience daily update. Buyers must consider that there might be an increase in latency during the bidding phase. To minimize the impact of these issues, frequency cap filtering is performed before the call to generateBid.

Keep contextual counters on the server

With server side estimation, a developer can have rough estimates for when frequency capping may be active. Those estimations can indicate that an ad has likely hit the frequency cap threshold, and should therefore be sent with more ad candidates or be eliminated completely.

Send multiple ad candidates on the contextual response

You should send multiple ad candidates with a contextual response before a Protected Audience auction. This ensures that if several ads are filtered out, other ads are still shown. Ad candidates can be prioritized so that some ads are provided as backup.

Since execution is time-bound, ad candidates should be chosen according to their likelihood to win an auction and to not be filtered out.


The following are known limitations of Protected Audience frequency capping:

  1. Protected Audience frequency capping operates at the device user profile level, with no shared counters on other devices and other profiles. Any increments of ads shown from other devices need to be incorporated manually, if needed.
  2. Device counters are stored and accessed on the device. Server-side counters need to be managed separately.
  3. As frequency capping and related ad filtering is processed on a device, ad tech platforms don't have direct control over these operations. To bypass the device's frequency capping threshold, ad tech platforms can send multiple candidate ads with different filters.
  4. Bid adjustments based on recorded frequency are unsupported. The generateBid functions cannot view frequency counters.