FLEDGE on Android includes the Custom Audience API and the Ad Selection API. Ad tech platforms and advertisers can use these APIs to serve customized ads based on previous app engagement that limits the sharing of identifiers across apps and limits sharing a user's app interaction information with third-parties.
The Custom Audience API is centered around the "custom audience" abstraction, which represents a group of users with common intentions. An advertiser can register a user with a custom audience and associate relevant ads with it. This information is stored locally and can be used to inform advertiser bids, ad filtering, and ad rendering.
The Ad Selection API provides a framework that allows multiple developers to run an auction locally for a custom audience. To achieve this, the system considers relevant ads associated with the custom audience and performs additional processing on ads that an ad tech platform returns to the device.
Ad tech platforms can integrate these APIs to implement remarketing that preserves user privacy. Support for additional use cases, including app install ads, are planned for future releases. Learn more about FLEDGE on Android in the design proposal.
This developer guide describes how to work with FLEDGE on Android to do the following:
Before you begin
Before you get started, complete the following:
- Set up your development environment for the Privacy Sandbox on Android.
- Either install a system image onto a supported device or set up an emulator that includes support for the Privacy Sandbox on Android.
Join a custom audience
A custom audience represents a group of users with common intentions or interests as decided by an advertiser app. An app or SDK may use a custom audience to indicate a particular audience, such as someone who has left items in a shopping cart. To create or join a custom audience asynchronously, do the following:
- Initialize the
CustomAudienceManager
object. - Create a
CustomAudience
object by specifying key parameters such as the buyer’s package and a relevant name. Then, initialize theJoinCustomAudienceRequest
object with theCustomAudience
object. - Call the asynchronous
joinCustomAudience()
with theJoinCustomAudienceRequest
object and relevantExecutor
andOutcomeReceiver
objects.
Kotlin
val customAudienceManager: CustomAudienceManager =
context.getSystemService(CustomAudienceManager::class.java)
// Initialize a custom audience.
val audience = CustomAudience.Builder()
.setBuyer(buyer)
.setName(name)
...
.build()
// Initialize a custom audience request.
val joinCustomAudienceRequest: JoinCustomAudienceRequest =
JoinCustomAudienceRequest.Builder().setCustomAudience(audience).build()
// Request to join a custom audience.
customAudienceManager.joinCustomAudience(joinCustomAudienceRequest,
executor,
outcomeReceiver)
Java
CustomAudienceManager customAudienceManager =
context.getSystemService(CustomAudienceManager.class);
// Initialize a custom audience.
CustomAudience audience = new CustomAudience.Builder()
.setBuyer(buyer)
.setName(name)
...
.build();
// Initialize a custom audience request.
JoinCustomAudienceRequest joinCustomAudienceRequest =
new JoinCustomAudienceRequest.Builder().setCustomAudience(audience).build();
// Request to join a custom audience.
customAudienceManager.joinCustomAudience(joinCustomAudienceRequest,
executor,
outcomeReceiver);
The combination of the following parameters uniquely identifies each
CustomAudience
owner
: Package name of the owner app. If not specified, it defaults to the package name of the calling app.buyer
: Identifier for the buyer ad network which manages ads for this custom audience.name
: An arbitrary name or identifier for the custom audience.
Calling joinCustomAudience()
repeatedly with a different instance of
CustomAudience
will update any existing CustomAudience
with
matching owner, buyer
, and name
parameters. To help preserve privacy, the
result of the API does not distinguish between "creation" and "update."
Additionally, the CustomAudience
must be created with these required
parameters:
- Daily update URL: An HTTPS URL queried daily in the background to update a custom audience’s user bidding signals, trusted bidding data, and render URLs and metadata for ads.
- Bidding logic URL: An HTTPS URL queried during ad selection to fetch a buyer’s JavaScript bidding logic. See the required function signatures in this JavaScript.
Optional parameters for a CustomAudience
object may include:
- Activation time: A custom audience can only participate in ad selection and daily updates after its activation time. This can be useful to engage lapsed users of an app, for example.
- Expiration time: A future time after which the custom audience will be removed from the device.
- User bidding signals: A JSON string containing user signals, such as the user’s preferred locale, that a buyer’s bidding logic JavaScript will consume to generate bids during the ad selection process. This format helps ad tech platforms reuse code across platforms and eases the consumption in JavaScript functions.
- Trusted bidding data: An HTTPS URL and a list of strings used during the ad selection process that fetch bidding signals from a trusted key/value server.
- Ads: A list of
AdData
objects corresponding to the ads which will participate in ad selection. EachAdData
object consists of:- Render URL: An HTTPS URL that is queried to render the final ad.
- Metadata: A JSON object serialized as a string containing information to be consumed by buyer bidding logic during the ad selection process.
Here's an example of a CustomAudience
object instantiation:
Kotlin
// Minimal initialization of a CustomAudience object
val customAudience: CustomAudience = CustomAudience.Builder()
.setBuyer("my.buyer.domain.name")
.setName("example-custom-audience-name")
.setDailyUpdateUrl(Uri.parse("https://DAILY_UPDATE_URL"))
.setBiddingLogicUrl(Uri.parse("https://BIDDING_LOGIC_URL"))
.build()
Java
// Minimal initialization of a CustomAudience object
CustomAudience customAudience = CustomAudience.Builder()
.setBuyer("my.buyer.domain.name")
.setName("example-custom-audience-name")
.setDailyUpdateUrl(Uri.parse("https://DAILY_UPDATE_URL"))
.setBiddingLogicUrl(Uri.parse("https://BIDDING_LOGIC_URL"))
.build();
Handle joinCustomAudience() outcomes
The asynchronous joinCustomAudience()
method uses the OutcomeReceiver
object to signal the result of the API call.
- The
onResult()
callback signifies the custom audience is successfully created or updated. - The
onError()
callback signifies two possible conditions.- If the
JoinCustomAudienceRequest
is initialized with invalid arguments, theAdServicesException
will indicate anIllegalArgumentException
as the cause. - All other errors will receive an
AdServicesException
with anIllegalStateException
as the cause.
- If the
Here's an example of handling the outcome of joinCustomAudience()
:
Kotlin
var callback: OutcomeReceiver<Void, AdServicesException> =
object : OutcomeReceiver<Void, AdServicesException> {
override fun onResult(result: Void) {
Log.i("CustomAudience", "Completed joinCustomAudience")
}
override fun onError(error: AdServicesException) {
// Handle error
Log.e("CustomAudience", "Error executing joinCustomAudience", error)
}
};
Java
OutcomeReceiver callback = new OutcomeReceiver<Void, AdServicesException>() {
@Override
public void onResult(@NonNull Void result) {
Log.i("CustomAudience", "Completed joinCustomAudience");
}
@Override
public void onError(@NonNull AdServicesException error) {
// Handle error
Log.e("CustomAudience", "Error executing joinCustomAudience", error);
}
};
Leave a custom audience
If the user no longer satisfies the business criteria for a given custom
audience, an app or SDK can call leaveCustomAudience()
to remove the custom
audience from the device. To remove a CustomAudience
based on its unique
parameters, do the following:
- Initialize the
CustomAudienceManager
object. - Initialize the
LeaveCustomAudienceRequest
with the custom audience’sowner, buyer
, andname
. To learn more about these input fields, read "Join a custom audience." - Call the asynchronous
leaveCustomAudience()
method with theLeaveCustomAudienceRequest
object and relevantExecutor
andOutcomeReceiver
objects.
Kotlin
val customAudienceManager: CustomAudienceManager =
context.getSystemService(CustomAudienceManager::class.java)
// Initialize a LeaveCustomAudienceRequest
val leaveCustomAudienceRequest: LeaveCustomAudienceRequest =
JoinCustomAudienceRequest.Builder()
.setOwner(owner)
.setBuyer(buyer)
.setName(name)
.build()
// Request to leave a custom audience
customAudienceManager.leaveCustomAudience(
leaveCustomAudienceRequest,
executor,
outcomeReceiver)
Java
CustomAudienceManager customAudienceManager =
context.getSystemService(CustomAudienceManager.class);
// Initialize a LeaveCustomAudienceRequest
LeaveCustomAudienceRequest leaveCustomAudienceRequest =
new JoinCustomAudienceRequest.Builder()
.setOwner(owner)
.setBuyer(buyer)
.setName(name)
.build();
// Request to leave a custom audience
customAudienceManager.leaveCustomAudience(
leaveCustomAudienceRequest,
executor,
outcomeReceiver);
Similar to calling joinCustomAudience()
, the OutcomeReceiver
will
signal the end of an API call. To help protect privacy, an error outcome will
not distinguish between internal errors and invalid arguments. The onResult()
callback is called when the API call has completed, whether or not a matching
custom audience is removed successfully.
Run ad selection
To use FLEDGE to select ads, call the runAdSelection()
method:
- Initialize an
AdSelectionManager
object. - Build an
AdSelectionConfig
object. - Call the asynchronous
runAdSelection()
method with theAdSelectionConfig
object and relevantExecutor
andOutcomeReceiver
objects.
Kotlin
val adSelectionManager: AdSelectionManager =
context.getSystemService(AdSelectionManager::class.java)
// Initialize AdSelectionConfig
val adSelectionConfig: AdSelectionConfig =
AdSelectionConfig.Builder()
.setSeller(seller)
.setDecisionLogicUrl(decisionLogicUrl)
.setCustomAudienceBuyers(customAudienceBuyers)
.setAdSelectionSignals(adSelectionSignals)
.setSellerSignals(sellerSignals)
.setPerBuyerSignals(perBuyerSignals)
.build()
// Run ad selection with AdSelectionConfig
adSelectionManager.runAdSelection(
adSelectionConfig,
executor,
outcomeReceiver)
Java
AdSelectionManager adSelectionManager =
context.getSystemService(AdSelectionManager.class);
// Initialize AdSelectionConfig
AdSelectionConfig adSelectionConfig =
new AdSelectionConfig.Builder()
.setSeller(seller)
.setDecisionLogicUrl(decisionLogicUrl)
.setCustomAudienceBuyers(customAudienceBuyers)
.setAdSelectionSignals(adSelectionSignals)
.setSellerSignals(sellerSignals)
.setPerBuyerSignals(perBuyerSignals)
.build();
// Run ad selection with AdSelectionConfig
adSelectionManager.runAdSelection(
adSelectionConfig,
executor,
outcomeReceiver);
The runAdSelection()
method requires an AdSelectionConfig
input, where
you must specify the following required parameters:
- Seller: Identifier for the seller ad network initiating the ad selection.
- Decision logic URL: An HTTPS URL queried to obtain the seller ad network’s JavaScript logic. See the required function signatures in this JavaScript.
- Custom audience buyers: A full list of identifiers for buyer ad networks that are allowed by the seller to participate in the ad selection process. These buyer identifiers correspond to CustomAudience.getBuyer() of participating custom audiences.
The following parameters can be optionally specified for more customized ad selection:
- Ad selection signals: A JSON object, serialized as a string, containing
signals to be consumed by buyer bidding logic JavaScript fetched from
CustomAudience.getBiddingLogicUrl()
. - Seller signals: A JSON object, serialized as a string, containing signals
consumed by the seller’s fetched JavaScript decision logic from
AdSelectionConfig.getDecisionLogicUrl()
. - Per buyer signals: A map of JSON objects, serialized as strings,
containing signals to be consumed by specific buyers’ bidding logic JavaScript
fetched from
CustomAudience.getBiddingLogicUrl()
, which are identified by the buyer fields of participating custom audiences.
Once an ad is selected, the results, bids, and signals are persisted internally for later reporting. From the OutcomeReceiver.onResult() callback, you’ll get back an AdSelectionOutcome that contains:
- A render URL for the winning ad, obtained from
AdData.getRenderUrl()
. - An ad selection ID unique to the device user. This ID is used for reporting the ad impression.
If the ad selection can’t be completed successfully due to reasons such as
invalid arguments, timeouts, or excessive resource consumption, the
OutcomeReceiver.onError()
callback will provide an AdServicesException
with the following behaviors:
- If the ad selection is initiated with invalid arguments, the
AdServicesException
will indicate anIllegalArgumentException
as the cause. - All other errors will receive an
AdServicesException
with anIllegalStateException
as the cause.
Report an ad impression
After a winning ad has been chosen from the ad selection workflow, you can
report the impression back to participating buy-side and sell-side platforms
with the AdSelectionManager.reportImpression()
method. To report an ad
impression:
- Initialize an
AdSelectionManager
object. - Build a
ReportImpressionRequest
object with the ad selection ID. - Call the asynchronous
reportImpression()
method with theAdSelectionConfig
object and relevantExecutor
andOutcomeReceiver
objects.
Java
AdSelectionManager adSelectionManager =
context.getSystemService(AdSelectionManager.class);
// Initialize a ReportImpressionRequest
ReportImpressionRequest adSelectionConfig =
new ReportImpressionRequest.Builder()
.setAdSelectionId(adSelectionId)
.setAdSelectionConfig(adSelectionConfig);
.build();
// Request to report the impression with the ReportImpressionRequest
adSelectionManager.reportImpression(
reportImpressionRequest,
executor,
outcomeReceiver);
Kotlin
val adSelectionManager = context.getSystemService(AdSelectionManager::class.java)
// Initialize a ReportImpressionRequest
val adSelectionConfig: ReportImpressionRequest =
ReportImpressionRequest.Builder()
.setAdSelectionId(adSelectionId)
.setAdSelectionConfig(adSelectionConfig);
.build()
// Request to report the impression with the ReportImpressionRequest
adSelectionManager.reportImpression(
reportImpressionRequest,
executor,
outcomeReceiver)
Initialize the ReportImpressionRequest
with the following required
parameters:
- Ad selection ID: An ID unique only to a device user that identifies a successful ad selection.
- Ad selection config: The same configuration used in the
runAdSelection()
call identified by the provided ad selection ID.
The asynchronous reportImpression()
method uses the OutcomeReceiver
object to signal the result of the API call.
- The
onResult()
callback indicates if the ad selection has been completed. - The
onError()
callback indicates the following possible conditions:- If the call is initialized with an invalid input argument, the
AdServicesException
will indicate anIllegalArgumentException
as the cause. - All other errors will receive an
AdServicesException
with anIllegalStateException
as the cause.
- If the call is initialized with an invalid input argument, the
Impression reporting endpoints
The report impression API will issue HTTPS GET requests to endpoints provided by the sell-side platform and the winning buy-side platform:
Buy-side platform endpoint:
- The API will use the Bidding logic URL specified in the custom audience to fetch the buyer’s bidding logic JavaScript.
- Invoke the
reportResult()
JavaScript function, which is expected to return the buyer's impression reporting URL.
Sell-side platform endpoint:
- Use the Decision logic URL specified in the
AdSelectionConfig
object to fetch the seller’s decision logic JavaScript. - Invoke the
reportWin()
JavaScript function, which is expected to return the buyer impression reporting URL.
Best effort Impression reporting
The reportImpression()
is designed to offer a best-effort completion of
reporting.
JavaScript for ad selection
The ad selection workflow orchestrates the execution of buyer-provided and seller-provided JavaScript.
Buyer-provided JavaScript is fetched from the Bidding logic URL specified in the custom audience. The returned JavaScript should include the following functions:
Seller-provided JavaScript is fetched from the decision logic URL specified in
the AdSelectionConfig
parameter for the ad selection API. The returned
JavaScript should include the following functions:
generateBid()
function generateBid(
ad,
auction_signals,
per_buyer_signals,
trusted_bidding_signals,
contextual_signals,
user_signals,
custom_audience_signals) {
return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };
}
Input parameters:
ad
: a JSON object with the following format varad = { 'render_url': url, 'metadata': json_metadata }
;auction_signals, per_buyer_signals
: JSON objects, they are specified in the auction configuration objectcustom_audience_signals
: JSON object generated by the platform. The format for this JSON object is:var custom_audience_signals = { "owner":"ca_owner", "buyer":"ca_buyer", "name":"ca_name", "activation_time":"ca_activation_time_epoch_ms", "expiration_time":"ca_expiration_time_epoch_ms", "user_bidding_signals":"ca_user_bidding_signals" }
where:
owner, buyer
, andname
are string taken from the properties with the same name of the Custom Audience participating to the ad selectionactivation_time
andexpiration_time
are the time of activation and expiration of the custom audience, expressed as seconds since the Unix epochca_user_bidding_signals
is a JSON string specified in theuserBiddingSignals
field of theCustomAudience
at creation timetrusted_bidding_signals, contextual_signals
, anduser_signals
are JSON objects. They are currently passed as empty objects and will be filled up in future releases. Their format is not enforced by the platform and is managed by the ad tech.
Result:
ad
: is the ad the bid refers to. The script is allowed to return a copy of the ad it received with different metadata. Therender_url
property of the ad is expected to be unaltered.bid
: a float value representing the bid value for this adstatus
: an integer value that can be:- 0: for a successful execution
- 1: (or any non-zero value) in case any of the input signals is invalid. In case a non-zero value is returned by generate-bid the bidding process is invalidated for all the CA ads
scoreAd()
function scoreAd(
ad,
bid,
ad_selection_config,
seller_signals,
trusted_scoring_signals,
contextual_signal,
user_signal,
custom_audience_signal) {
return {'status': 0, 'score': score };
}
Input parameters:
ad
: see thegenerateBid
documentationbid
: the bid value for the adad_selection_config
: a JSON object representing theAdSelectionConfig
parameter of therunAdSelection
API. The format is:var ad_selection_config = { 'seller': 'seller', 'decision_logic_url': 'url_of_decision_logic', 'custom_audience_buyers': ['buyer1', 'buyer2'], 'auction_signals': auction_signals, 'per_buyer_signals': per_buyer_signals, 'contextual_ads': [ad1, ad2] }
seller_signals
: JSON objects read from thesellerSignals
AdSelectionConfig
API parametertrusted_scoring_signal
: read from theadSelectionSignals
field in theAdSelectionConfig
API parametercontextual_signals, user_signals
: JSON objects. They are currently passed as empty objects and will be filled up in future releases. Their format is not enforced by the platform and is managed by the ad-techper_buyer_signals
: JSON object read from theperBuyerSignal
map in theAdSelectionConfig
API parameter using as key the current Custom Audience buyer. Empty if the map doesn’t contain any entry for the given buyer.
Output:
score
: a float value representing the score value for this adstatus
: an integer value that can be:- 0: for a successful execution
- 1: in case the
customAudienceSignals
are invalid - 2: in case the
AdSelectionConfig
is invalid - 3: in case any of the other signals is invalid
- Any non-zero value will cause the failure of the process, the value would determine the type of exception thrown
reportResult()
function reportResult(ad_selection_config, render_url, bid, contextual_signals) {
return {
'status': status,
'results': {'signals_for_buyer': signals_for_buyer, 'reporting_url': reporting_url }
};
}
Input parameters:
ad_selection_config
: see the documentation ofscoreAds
render_url
: the render URL of the winning adbid
: the bid offered for the winning adcontextual_signals
: see the documentation ofgenerateBid
Output:
status: 0
for success and non-zero for failureresults
: a JSON objects containing:signals_for_buyer
: a JSON object that will be passed to the reportWin functionreporting_url
: a URL that will be used by the platform to notify the impression to the buyer
reportWin()
function reportWin(
ad_selection_signals,
per_buyer_signals,
signals_for_buyer,
contextual_signals,
custom_audience_signals) {
return {'status': 0, 'results': {'reporting_url': reporting_url } };
}
Input parameters:
ad_selection_signals, per_buyer_signals
: see the documentation ofscoreAds
signals_for_buyer
: a JSON object returned byreportResult
contextual_signals, custom_audience_signals
: see the documentation forgenerateBid
Output:
status: 0
for success and non-zero for failureresults
: a JSON objects containing:reporting_url
: a URL that will be used by the platform to notify the impression to the seller
Testing
To help you get started with FLEDGE, we've created sample apps in Kotlin and Java, which can be found on GitHub.
Prerequisites
To test ad selection and impression reporting, you will need to set up 4 HTTPS endpoints that your test device or emulator can access:
- Buyer endpoint that serves the bidding logic JavaScript.
- Seller endpoint that serves the decision logic JavaScript.
- Winning buyer impression reporting endpoint.
- Seller impression reporting endpoint.
For convenience, the GitHub repo provides trivial JavaScript code for testing purposes. It also includes OpenAPI service definitions which can be deployed to a supported mock or microservices platforms. For more details, see the project README file.
Functionality to test
- Exercise joining/leaving and setting up a custom audience based on prior user actions.
- Exercise the initiation of on-device ad selection through JavaScripts hosted remotely.
- Observe how an app’s association with custom audience settings may affect ad selection outcomes.
- Exercise impression reporting after ad selection.
Limitations
For a list of in-progress capabilities, read the release notes.
Report bugs and issues
Your feedback is a crucial part of the Privacy Sandbox on Android! Let us know of any issues you find or ideas for improving Privacy Sandbox on Android.