Buyer guide: join interest groups and generate bids

Buyer API guide and references to join remarketing lists and bid in Protected Audience API auctions.

In this article, you'll find a technical reference for interest groups, as used in the current iteration of the experimental Protected Audience API.

Read the developer guide for the full life cycle of the Protected Audience API, and refer to the Protected Audience API explainer for an in-depth proposal of how browsers record interest groups.

Not a developer? Refer to the Protected Audience API overview.

Protected Audience API interest groups

A Protected Audience API interest group represents a group of people with a common interest, corresponding to a remarketing list. Every Protected Audience API interest group has an owner.

Interest group owners act as the buyer in the Protected Audience API ad auction. Interest group membership is stored by the browser, on the user's device, and is not shared with the browser vendor or anyone else.

Bid in a Protected Audience API ad auction

Owners of Protected Audience API interest groups can be invited to bid in Protected Audience API ad auctions.

API functions


The advertiser's demand-side platform (DSP) or the advertiser itself calls navigator.joinAdInterestGroup() to ask the browser to add an interest group to the browser's membership list.

The origin of the calling context for joinAdInterestGroup() must match the interest group owner's origin, so joinAdInterestGroup() will need to be called from an iframe (for example, from a DSP) unless the origin of the interest group owner matches the origin of the current document (for example, a website with its own interest groups).

joinAdInterestGroup() requires permission from:

This means it's not possible for malicious.example to call joinAdInterestGroup() for an interest group owned by, without granting permission.

Permission from the visited site

Permission can be granted from the same origin or cross-origin.

By default, permission is granted for joinAdInterestGroup() calls from the same origin as the site visited, (in other words, from the same origin as the top-level frame of the current page). Sites can use the join-ad-interest-group permissions policy header to disable joinAdInterestGroup() calls.

Calling joinAdInterestGroup() cross-origin (origins that are different from the current page) can only succeed if the site being visited has set a permissions policy that allows calls to joinAdInterestGroup() from cross-origin iframes.

Permission from the interest group owner

Interest group owner permission is implicitly granted by calling joinAdInterestGroup() from an iframe with the same origin as that of the interest group's owner. For example, a iframe can call joinAdInterestGroup() for interest groups owned by

In essence, joinAdInterestGroup() can run in a page or iframe on the owner's domain, or be delegated to other domains provided using a list at a .well-known URL.

Example usage

Here's an example of how one might define an interest group and ask the browser to join the group.

const interestGroup = {
  owner: 'https://dsp.example',
  name: 'custom-bikes',
  biddingLogicUrl: ...,
  biddingWasmHelperUrl: ...,
  dailyUpdateUrl: ...,
  trustedBiddingSignalsUrl: ...,
  trustedBiddingSignalsKeys: ['key1', 'key2'],
  userBiddingSignals: {...},
  ads: [bikeAd1, bikeAd2, bikeAd3],
  adComponents: [customBike1, customBike2, bikePedal, bikeFrame1, bikeFrame2],

navigator.joinAdInterestGroup(interestGroup, 7 * kSecsPerDay);

The interestGroup object passed to the function must be no more than 50 kiB in size, otherwise the call will fail. The second parameter specifies the duration of the interest group, capped at 30 days. Successive calls overwrite previously stored values.

Required properties

The only required properties for interest groups are owner and name:

Property Example Role
owner https://dsp.example Origin of the interest group owner.
name custom-bikes Name of the interest group.

Optional properties

The remaining properties are optional:

biddingLogicUrl1, 2
Example: https://dsp.example/bid/custom-bikes/bid.js
Role: URL for bidding JavaScript run in worklet.
biddingWasmHelperUrl1, 2
Example: https://dsp.example/bid/custom-bikes/bid.wasm
Role: URL for WebAssembly code driven from biddingLogicUrl.
Example: https://dsp.example/bid/custom-bikes/update
Role: URL that returns JSON to update interest group attributes. (See Update the interest group.)
Example: https://dsp.example/trusted/bidding-signals
Role: Base URL for key-value requests to bidder's trusted server.
Example: ['key1', 'key2' ...]
Role: Keys for requests to key-value trusted server.
Example: {...}
Role: Additional metadata the owner can use during bidding.
Example: [bikeAd1, bikeAd2, bikeAd3]
Role: Ads that might be rendered for this interest group.
Example: [customBike1, customBike2, bikePedal, bikeFrame1, bikeFrame2]
Role: Components for ads composed of multiple pieces.
<caption style="text-align:left">
<p id="first-ref"><sup>1</sup> The `biddingLogicUrl` and `ads` properties are optional, but
required to participate in an auction. There may be use cases for creating an interest group without these properties: for example, an interest group owner might want to add a browser to an interest group for a campaign that isn't running yet, or for some other future use, or they may temporarily have run out of advertising budget.</p>

<p id="second-ref"><sup>2</sup> In the current implementation of the Protected Audience API, `biddingLogicUrl`,
`biddingWasmHelperUrl`, `dailyUpdateUrl` and `trustedBiddingSignalsUrl` must
have the same origin as owner. That may not be a long-term constraint, and
the `ads` and `adComponents` URLs have no such constraint.</p>

Update attributes

dailyUpdateUrl specifies a web server that returns JSON defining interest group properties, corresponding to the interest group object passed to joinAdInterestGroup().

This allows the group's owner to periodically update the attributes of the interest group. In the current implementation, the following attributes can be changed:

  • biddingLogicUrl
  • biddingWasmHelperUrl
  • trustedBiddingSignalsUrl
  • trustedBiddingSignalsKeys
  • ads
  • priority

Any field not specified in the JSON will not be overwritten—only fields specified in the JSON get updated—whereas calling navigator.joinAdInterestGroup() overwrites any existing interest group.

Updates are best-effort, and can fail under the following conditions:

  • Network request timeout (currently 30 seconds).
  • Other network failure.
  • JSON parsing failure.

Updates are rate-limited to a maximum of one per day.

Updates can be canceled if too much contiguous time has been spent updating, though this doesn't impose any rate limiting on canceled (remaining) updates. Updates that fail due to network errors are retried after an hour, and updates that fail due to disconnection from the internet are retried immediately on reconnection.

Manual updates

Updates to interest groups owned by the current frame's origin can be triggered manually via navigator.updateAdInterestGroups().

Rate limiting prevents updates from happening too frequently: repeated calls to navigator.updateAdInterestGroups() don't do anything until the rate limit period (currently one day) has passed.

The rate limit gets reset if navigator.joinAdInterestGroup() is called again for the same interest group owner and name.

Automatic updates

All interest groups loaded for an auction are updated automatically after an auction completes, subject to the same rate limits as manual updates.

For each owner with at least one interest group participating in an auction, it's as if navigator.updateAdInterestGroups() is called from an iframe whose origin matches that owner.

Specify ads for an interest group

ads and adComponents objects include a URL for an ad creative and, optionally, arbitrary metadata that can be used at bidding time.

For example:

  renderUrl: 'https://cdn.example/.../bikeAd1.html',
  metadata: bikeAd1metadata // optional


The interest group owner's script at biddingLogicUrl must include a generateBid() function.

When a seller calls navigator.runAdAuction(), the generateBid() function is called once for each candidate ad. In other words, it's called for each interest group that the browser is a member of—if the interest group's owner is invited to bid.

The seller provides a decisionLogicUrl in the auction configuration parameter passed to navigator.runAdAuction(). The code at this URL must include a scoreAd() function, which scores the bid generated by each participating bidder.

The script at biddingLogicUrl provided by a buyer must include a generateBid() function.

This function is called once for each candidate ad. runAdAuction() individually checks each ad, along with its associated bid and metadata, then assigns the ad a numerical desirability score.

generateBid(interestGroup, auctionSignals, perBuyerSignals,
    trustedBiddingSignals, browserSignals) {
  return {
    ad: adObject,
    bid: bidValue,
    render: renderUrl,
    adComponents: [adComponentRenderUrl1, ...]


generateBid() takes the following arguments:

Argument Role
interestGroup An object passed to by the ad buyer. The interest group may be updated with dailyUpdateUrl.
auctionSignals A property of the auction config argument passed to navigator.runAdAuction() by the seller. This provides information about page context (such as the ad size and the publisher ID), the type of auction (first-price or second-price), and other metadata.
perBuyerSignals A property of the auction config argument passed by the seller. This can provide contextual signals from the buyer's server about the page, if the seller is an SSP which performs a real-time bidding call to buyer servers and pipes the response back, or if the publisher page contacts the buyer's server directly. If so, the buyer may wish to check a cryptographic signature of those signals inside generateBid() as protection against tampering.
trustedBiddingSignals An object whose keys are the trustedBiddingSignalsKeys for the interest group, and whose values are returned in the trustedBiddingSignals request.
browserSignals3 An object constructed by the browser, which might include information about page context (such as the hostname of the current page, which the seller could otherwise fake) and data for the interest group itself (such as a record of when the group previously won an auction, to allow on-device frequency capping).

3 The browserSignals object has the following properties:

  topWindowHostname: 'publisher.example',
  seller: 'https://ssp.example',
  joinCount: 3,
  bidCount: 17,
  prevWins: [[time1,ad1],[time2,ad2],...],
  wasmHelper: ... /* WebAssembly.Module object based on interest group's biddingWasmHelperUrl. */
  dataVersion: 1, /* Data-Version value from the buyer's Key/Value service response(s). */

To calculate a bid value, code in generateBid() can use the properties of the function's parameters.

For example:

function generateBid(interestGroup, auctionSignals, perBuyerSignals,
    trustedBiddingSignals, browserSignals) {
  return {
    bid: auctionSignals.is_above_the_fold ? perBuyerSignals.atf_value : perBuyerSignals.btf_value,

generateBid() returns an object with four properties:

Property Role
ad Arbitrary metadata about the ad, such as information the seller expects to learn about this bid or ad creative. The seller uses this information in its auction and decision logic.
bid A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (such as"USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear.
render A URL, or a list of URLs, that will be used to render the creative if this bid wins the auction. The value has to match the `renderUrl` of one of the ads defined for the interest group.

Ads Composed of Multiple Pieces explainer
adComponents An optional list of up to 20 components for ads composed of multiple pieces, taken from the adComponents property of the interest group argument passed to `navigator.joinAdInterestGroup()`.


The interest group owner can request to a browser be removed from an interest group. The browser removes the interest group from its membership list.

  owner: 'https://dsp.example',
  name: 'custom-bikes'

If a user returns to the site which asked the browser to add an interest group, the interest group owner can call the navigator.leaveAdInterestGroup() function to request the browser remove the interest group.

Code for an ad can also call this function for its interest group.

Frequently asked questions

How do I implement frequency control by click?

For simple frequency control, you can use the prevWins field in browserSignals inside generateBid(). Alternatively, you can call navigator.leaveAdInterestGroup() to request that a user's browser leave an interest group when an ad is clicked. This prevents future bidding and acts as a form of frequency capping.

You can also use a first-party cookie to store click information. When the ad is rendered, overwrite an existing interest group with the click data as user bidding signals. The workflow would look something like:

  • User visits
  • The advertiser writes "0 clicks" in a first-party cookie and calls joinAdInterestGroup({ ..., userBiddingSignals: { clicks: [] } }).
  • User clicks on an ad at a later time and is taken to
  • The advertiser reads and increments first-party cookie click data, then calls joinAdInterestGroup({ userBiddingSignals: { clicks: ["1667499972"] } }).
  • For future bidding, the click data available in userBiddingSignals can be used in bidding logic.

How do I use a user's recent browsing history for ad recommendations?

A user's browsing history for the site that called joinAdInterestGroup() can be updated in userBiddingSignals, which can be used during on-device bidding. See the product-level TURTLEDOVE original proposal which includes some analysis by RTB House on the impact of core metrics for recommendation use case adoption.

dailyUpdateUrl provides a mechanism to periodically update the attributes of the interest group, but this update is not based on the user's browsing history.

What's the maximum number of interest groups per group owner for a single user?

Chrome allows up to 1000 interest groups per owner, and up to 1000 interest group owners. These limits are meant as guard rails, not to be hit in regular operation.

How can I maximize interest group ads that meet 𝑘-anon thresholds?

As the public explainer notes, since a single interest group can carry multiple possible ads that it might show, the group will have an opportunity to re-bid another one of its ads to act as a "fallback ad" any time its most-preferred choice is below threshold. This means that a small, specialized ad that is still below the 𝑘-anonymity threshold could still choose to participate in auctions, and its interest group has a way to fall back to a more generic ad until the more specialized one has a large enough audience.

From a tactical perspective, you may consider the following:

  • To get a new ad to start showing, just start bidding with it in cases where you want it to show. There is nothing additional that you need to do.

  • You can have a fallback ad that you use when new ads are not 𝑘-anon. There is some risk of your fallback ad itself not being 𝑘-anon, so you could consider sometimes just bidding with the fallback ad in the first place. Perhaps do this 1% of the time, for example, if that is a good level to ensure that you expect the fallback to stay over threshold.

There has been some recent discussion of other ways things could work, so if you have some use case for which this mechanism would pose a problem, please continue engaging in the public conversation about ways in which the API could improve.

All Protected Audience API references

API reference guides are available:

The Protected Audience API explainer also provides detail about feature support and constraints.