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
joinAdInterestGroup()
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:
- The site being visited
- The interest group owner
This means it's not possible for malicious.example
to call
joinAdInterestGroup()
for an interest group owned by dsp.example.com
,
without dsp.example.com
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 dsp.example.com
iframe can call
joinAdInterestGroup()
for interest groups owned by dsp.example.com
.
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:
biddingLogicUrl
1, 2- Example:
https://dsp.example/bid/custom-bikes/bid.js
- Role: URL for bidding JavaScript run in worklet.
biddingWasmHelperUrl
1, 2- Example:
https://dsp.example/bid/custom-bikes/bid.wasm
- Role: URL for WebAssembly code driven from
biddingLogicUrl
. dailyUpdateUrl
2- Example:
https://dsp.example/bid/custom-bikes/update
- Role: URL that returns JSON to update interest group attributes. (See Update the interest group.)
trustedBiddingSignalsUrl
2- Example:
https://dsp.example/trusted/bidding-signals
- Role: Base URL for key-value requests to bidder's trusted server.
trustedBiddingSignalsKeys
- Example:
['key1', 'key2' ...]
- Role: Keys for requests to key-value trusted server.
userBiddingSignals
- Example:
{...}
- Role: Additional metadata the owner can use during bidding.
ads
1- Example:
[bikeAd1, bikeAd2, bikeAd3]
- Role: Ads that might be rendered for this interest group.
adComponents
- 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>
</caption>
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
}
generateBid()
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, ...]
};
}
Arguments
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. |
browserSignals 3 |
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()`. |
leaveAdInterestGroup()
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.
navigator.leaveAdInterestGroup({
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
advertiser.com/product
. - 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
advertiser.com/product
. - 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:
- Developer guide for the Protected Audience API.
- Ad buyer guide to Protected Audience interest groups and bid generation.
- Ad seller guide to Protected Audience ad auctions.
- Guide to reporting auction results
- Best practices for Protected Audience ad auction latency
- Troubleshoot Protected Audience
The Protected Audience API explainer also provides detail about feature support and constraints.