Protected App Signals developer's guide

To help developers start experimenting with the Protected App Signals API, this document describes all the APIs within the API surface, details how to set up a test environment, and provides examples of the configuration and scripts.

Version history

Jan 2024

First release of the developer's guide supporting PAS MVP release

March 2024

Changes in API to support the M-2024-05 release of the Android API and the April 2024 release of the server side components. Most notable changes:

  • Added details about the permissions required for the on-device API
  • Added details about on-device signals quota management
  • Updated generateBid signature with changes related with contextual ad retrieval and egress support
  • Updated reportWin documentation including egress support
  • Update Ad Retrieval API documentation removing support for BYOS ad retrieval and documenting the ad retrieval UDF

API overview

The Protected Signals API surface includes different subsets of API on different systems:

  • Android APIs:
    • Signal Curation API, composed of:
    • Update Signals API
    • Signals Encoding API
    • Protected Auction Support API: To be used by SDKs to run the protected auction on the Bidding and Auction (B&A) Servers using Protected App Signals.
  • Server-side APIs:
    • Protected Auction API: A series of JS scripts running in the Bidding and Auction Servers. This API enables Sellers and Buyers to write the logic to implement the protected auction.
    • Ad Retrieval API: Responsible for providing a list of candidate ads given the contextual and user information made available to the buyer's bidding server.

Android client

On the client side, the Protected App Signals surface consists of three different APIs:

  • Update Signals: An Android System API to enable the curation of signals on the device.
  • Signals Encoding: a JavaScript API to prepare the signals to be sent to the server during the auction.
  • Protected Auction Support: An API to support the execution of a Protected Auction on the Bidding and Auction Servers. This API is not specific to Protected App Signals and is also used to support auctions for the Protected Audience API.

Update Signals API

The Update Signals API provides ad techs with an ability to register user and app related signals on behalf of a buyer. The API works on a delegation model. The caller provides a URI from which the framework fetches the corresponding signals and the logic to encode those signals to be used in the auction.

The API requires the android.permission.ACCESS_ADSERVICES_PROTECTED_SIGNALS permission.

The updateSignals() API will retrieve a JSON object from the URI that describes which signals to add or remove, and how to prepare those signals for the auction.

Executor executor = Executors.newCachedThreadPool();
ProtectedSignalsManager protectedSignalsManager
     =  ProtectedSignalsManager.get(context);

// Initialize a UpdateSignalsRequest
UpdateSignalsRequest updateSignalsRequest = new

OutcomeReceiver<Object, Exception> outcomeReceiver = new OutcomeReceiver<Object, Exception>() {
  public void onResult(Object o) {
    //Post-success actions

  public void onError(Exception error) {
    //Post-failure actions

// Call updateSignals

The platform makes an https request to the URI provided in the request to fetch the signal updates. Along with the signal updates, the response can include an endpoint that hosts the encoding logic for converting the raw signals into encoded payload. The signal updates are expected to be in the form of JSON and can have the following keys:

The top level keys for the JSON object must correspond to one of five commands:




Adds a new signal, overwriting any existing signals with the same key. The value

for this is a JSON object where the keys are base 64 strings corresponding to the key to put for and the values are base 64 string corresponding to the value to put.


Appends a new signal/signals to a time series of signals, removing the oldest

signals to make room for the new ones if the size of the series exceeds the given maximum. The value for this is a JSON object where the keys are base 64 strings corresponding to the key to append to and the values are objects with two fields: "values" and "maxSignals".

"values": A list of base 64 strings corresponding to signal values to append to the time series.

"maxSignals": The maximum number of values that are allowed in this time series. If

the current number of signals associated with the key exceeds maxSignals the oldest signals will be removed. Note that you can append to a key added by put. Note that appending more than the maximum number of values will cause a failure.


Adds a new signal only if there are no existing signals with the same key. The value for this is a JSON object where the keys are base 64 strings corresponding to the key to put for and the values are base 64 string corresponding to the value to put.


Removes the signal for a key. The value of this is a list of base 64 strings corresponding to the keys of signals that should be deleted.


Provides an action to update the endpoint, and a URI which can be used

to retrieve an encoding logic. The sub-key for providing an update action is "action" and the

values currently supported is only "REGISTER" that will register the encoder endpoint if provided for the first time or overwrite the existing one with the newly provided endpoint. Providing the endpoint is required for the "REGISTER" action.The sub-key for providing an encoder endpoint is "endpoint" and the value is the URI

string for the endpoint.

A sample JSON request would look like the following:

    "put": {
        "AAAAAQ==": "AAAAZQ==",
        "AAAAAg==": "AAAAZg=="
    "append": {
        "AAAAAw==": {
            "values": [
            "max_signals": 3
    "put_if_not_present": {
        "AAAABA==": "AAAAaQ==",
        "AAAABQ==": "AAAAag=="
    "update_encoder": {
        "action": "REGISTER",
        "endpoint": " App Signals_encode_script.js"

Signals will have an on device quota in the order of 10-15Kb. Once the quota is exceeded PPAPI will evict signals using a FIFO strategy. The eviction process will allow the quota to be slightly exceeded for short interval of times in order to reduce the frequency of evictions.

Signals Encoding API

Buyers are required to supply a Java Script function to be used to encode the signals stored on the device to be sent to the server during the Protected Auction. Buyers can provide this script by adding the URL where it can be fetched from using the key "update_encoder" in any of the responses to an UpdateSignal API request. The script will have the following signature:

function encodeSignals(signals, maxSize) {
  let result = new Uint8Array(maxSize);
  // first entry will contain the total size
  let size = 1;
  let keys = 0;
  for (const [key, values] of signals.entries()) {
    // In this encoding we only care about the first byte
    console.log("key " + keys + " is " + key)
    result[size++] = key[0];
    result[size++] = values.length;
    for(const value of values) {
      result[size++] = value.signal_value[0];
  result[0] = keys;
  return { 'status': 0, 'results': result.subarray(0, size)};

The signals parameter is a map from keys in the form of UInt8Arrays of size 4 to lists of Protected App Signals objects. Each Protected App Signals object has three fields:

  • signal_value: A UInt8Array representing the value of the signal.
  • creation_time: A number representing the creation time of the signals in epoch-seconds.
  • package_name: A string representing the name of the package that created the signal.

The maxSize parameter is a number describing the largest allowed array size for the output.

The function should output an object with two fields:

  • status: Should be 0 if the script ran successfully.
  • results: Should be a UInt8Array of length less than or equal to maxSize. This array will be sent to the server during auctions, and prepared by the prepareDataForAdRetrieval script.

The encoding provides ad techs with an initial stage of feature engineering, where they can perform transformations such as compressing raw signals into concatenated versions based on their own custom logic. Note that during a Protected Auction running in the Trusted Execution Environments (TEE), ad tech custom logic will have read access to the signal payloads generated by the encoding. The custom logic, known as a User Defined Function (UDF), running in the Buyer's B&A TEE will have read access to the encoded signals and other contextual signals provided by the publisher app to perform ad selection (ad retrieval and bidding).

Signals encoding

Every hour, buyers who have provided encoding logic with their registered signals will have their the signals encoded into an auction payload.The byte array for the auction payload is persisted on the device, and is encrypted and will be collected by sellers as part of Ad Selection data to be included as part of a Protected Auction. For testing, you can trigger this encoding outside of its hourly cadence by running the following command:

adb shell cmd jobscheduler run -f 29
Encoder logic versioning

When a request is made to download the ad tech custom encoder logic, the ad tech endpoint can respond with a version number in the response headers. This version is persisted along with the encoder logic on the device. When the raw signals are encoded, the encoded payload is persisted along with the version used for encoding. This version is also sent along to the B&A server during a Protected Auction, so that ad techs can align their bidding and encoding logic based on the version.

Response header for providing encoder version : X_ENCODER_VERSION

Protected Auction Support API

On the device side, running an auction for Protected App Signals is the same as running an auction for protected audiences.

Bidding and Auction Services

The Server Side APIs include:

  • Protected Auction API: A series of JS functions or UDFs that buyers and sellers can deploy on the B&A components they own, to determine the bidding and the auction logic.
  • Ad Retrieval API: Buyers can implement this API by implementing a REST Endpoint that will be responsible for providing a set of candidate ads for the Protected App Signal auction.

Protected Auction API

The Protected Auction API consists of JS API or UDFs that buyers and sellers can use to implement their auction and bidding logic.

Buyer ad tech UDFs
prepareDataForAdRetrieval UDF

Before Protected App Signals can be used to fetch ads candidates from the TEE Ad Retrieval service, buyers must decode and prepare the Protected App Signals and other seller-provided data. Buyers prepareDataForAdRetrieval UDF output is passed to ads retrieval service to retrieve the top k candidate ads for bidding.

// Inputs
// ------
// encodedOnDeviceSignals: A Uint8Array of bytes from the device.
// encodedOnDeviceSignalsVersion: An integer representing the encoded
//   version of the signals.
// sellerAuctionSignals: Information about auction (ad format, size) derived
//                       contextually.
// contextualSignals: Additional contextual signals that could help in
//                    generating bids.
// Outputs
// -------
// Returns a JSON structure to be used for retrieval.
// The structure of this object is left to the adtech.
function prepareDataForAdRetrieval(encodedOnDeviceSignals,encodedOnDeviceSignalsVersion,sellerAuctionSignals,contextualSignals) {
   return {};
generateBid UDF

After the top k candidate ads are returned, the ad candidates are passed to the buyer's custom bidding logic, generateBid UDF:

// Inputs
// ------
// ads: Data string returned by the ads retrieval service. This can include Protected App Signals
//   ads and related ads metadata.
// sellerAuctionSignals: Information about the auction (ad format, size),
//                       derived contextually
// buyerSignals: Any additional contextual information provided by the buyer
// preprocessedDataForRetrieval: This is the output of this UDF.
function generateBid(ads, sellerAuctionSignals, buyerSignals,
                    rawSignals, rawSignalsVersion) {
    return { "ad": <ad Value Object>,
             "bid": <float>,
             "render": <render URL string>,
             'adCost': <optional float ad cost>,
             "egressPayload": <limitedEgressPayload>,
             "temporaryUnlimitedEgressPayload": <temporaryUnlimitedEgressPayload>

The output of this function is a single bid for an ad candidate, represented as a JSON equivalent to ProtectedAppSignalsAdWithBidMetadata. The function can also return two arrays that will then be passed to reportWin to enable model training (for more details about egress and model training refer to the reporting section in the PAS explainer)

reportWin UDF

When an auction concludes, the auction service will generate reporting URLs for the buyers and register beacons using reportWin UDF (This is the same reportWin function that is used for Protected Audiences). This will be pinged by the device once the Ad has been rendered by the client. The signature of this method is almost the same as the Protected Audience version except for two extra parameters egressPayload and temporaryUnlimitedEgressPayload that are used to enable model training and are populated with the results from generateBid.

// Inputs / Outputs
// ----------------
// See detailed documentation here.
function reportWin(auctionSignals, perBuyerSignals, signalsForWinner,
                   egressPayload, temporaryUnlimitedEgressPayload) {
  // ...
Seller ad tech UDFs
scoreAd UDF

This UDF is used by sellers to select which of the ads received from buyers will win the auction.

function scoreAd(adMetadata, bid, auctionConfig,
                 trustedScoringSignals, bid_metadata) {
  // ...
  return {desirability: desirabilityScoreForThisAd,
              allowComponentAuction: true_or_false};
reportResult UDF

This UDF allows the seller to (eventually) do event level reporting with the information regarding the winning ad.

function reportResult(auctionConfig, reporting_metadata) {
  // ...
  registerAdBeacon({"click", clickUrl,"view", viewUrl});
  return signalsForWinner;

Ad Retrieval API

In the MVP release , the ad retrieval service will be a buyer-managed and hosted service and the bidding service retrieves ad candidates from this service. Starting in April 2024, the ad retrieval server must run in a Trusted Execution Environment (TEE) and will expose a GRPC/proto interface. Ad tech companies must set up this server and provide its URL as part of the B&A stack deployment. An implementation of this service running in the TEE is available in the Privacy Sandbox GitHub and in the rest of the documentation we are assuming that this is the code used in the deployment.

Starting from April 2024, B&A versions support contextual path ad retrieval. In this case the Bidding server will receive a list of ad-identifiers sent by the RTB server during the contextual part of the auction. The identifiers will be sent to a TEE KV server to fetch all the ad related information to be used during the bidding phase (for example ad-render-url, metadata and ad embeddings to be used in top-k selection). This second path doesn't need any specific logic to be deployed so we will document here only how to configure the TEE based ad retrieval use case.

HandleRequest UDF
function HandleRequest(requestMetadata, preparedDataForAdRetrieval,
                      deviceMetadata, contextualSignals) {
    return adsMetadataString;


  • requestMetadata: JSON. Per-request server metadata to the UDF. Empty for now.
  • preparedDataForAdRetrieval: the content of this field depends on the ad retrieval strategy. In case of contextual ad retrieval , this parameter will contain the raw signals originating from the device, and passed from the bidding service. In case of TEE ad retrieval using the Ad Retrieval Server, this parameter will contain the result of the prepareDataForAdRetrieval UDF. Note: at this stage the Protected App Signals would be decoded and unencrypted.
  • deviceMetadata: JSON object containing device metadata forwarded by the Seller's Ad Service. See the B&A documentation for further details.
    • X-Accept-Language: language used on the device.
    • X-User-Agent: User Agent used on the device.
    • X-BnA-Client-IP: Device IP address.
  • contextualSignals: arbitrary string originated from the contextual bidding server operated by the same DSP. The UDF is expected to be able to decode the string and use it. Contextual Signals may contain any information such as ML model version information for the protected embedding passed in using Protected App Signals.

The UDF should return a string on success. The string is returned to the bidding server which then passes it to the generateBid UDF. Although the string can just be a simple string, most likely the string should be a serialized object whose schema is defined by each ad tech on their own. There is no constraint on the schema as long as the ad tech's generateBid logic can recognize and use the string.

Set up your system for development


To setup your Android development environment you need to do the following:

  1. Create an emulator (preferred) or physical device that is running the Developer Preview 10 image
  2. Run the following:
adb shell am start -n

Then select the option shown to consent to app-suggested ads.

  1. Run the following command to enable the relevant APIs. You may need to re-run this occasionally as the default configuration of disabled will be periodically synced.
adb shell device_config put adservices fledge_custom_audience_service_kill_switch false;  adb shell device_config put adservices fledge_select_ads_kill_switch false; adb shell device_config put adservices fledge_on_device_auction_kill_switch false; adb shell device_config put adservices fledge_auction_server_kill_switch false; adb shell "device_config put adservices disable_fledge_enrollment_check true";  adb shell device_config put adservices ppapi_app_allow_list '\*'; adb shell device_config put adservices fledge_auction_server_overall_timeout_ms 60000;
  1. Restart the device.
  2. Override the device's auction keys to point to your auction key server. It is important to run this step before attempting to run an auction to prevent incorrect keys from being cached.

Bidding & Auction Services

To set up the B&A servers, refer to the self-serve setup documentation.

This document will focus on how to configure the buyer specific servers, as there are no changes required for sellers.


Before deploying a B&A service stack, buyer ad tech needs to:

  • Ensure that they have deployed their own TEE Ad retrieval Service (see the relevant section).
  • Ensure that the ad tech have all the necessary UDFs (prepareDataForAdRetrieval, generateBid, reportWin, HandleRequest) defined and hosted.

An understanding of how the Protected Auction with Protected Audience works with B&A will also be helpful but is not mandatory.

Terraform configuration

To use Protected App Signals, ad techs must:

  • Enable Protected App Signals support in B&A.
  • Provide URL endpoints from which the new UDFs for prepareDataForAdRetrieval, generateBid and reportWin can be fetched.

Additionally, this guide assumes that ad techs wanting to use B&A for remarketing would continue to set all the existing configuration flags for remarketing auction as usual.

Buyer ad tech configuration

Using this demo file as an example, buyers need to set the following flags:

  • Enable Protected App Signals: Enabled to collect Protected App Signals data.
  • Protected App Signals URLs: Set to the URLs of the Protected App Signals servers.

Ad techs must substitute the correct URLs in the placeholders for the following fields:

module "buyer" {
  # ... More config here.

  runtime_flags = {
    # ... More config here.

    ENABLE_PROTECTED_APP_SIGNALS                  = "true"
    TEE_AD_RETRIEVAL_KV_SERVER_ADDR               = "<service mesh address of the instance>"
    AD_RETRIEVAL_TIMEOUT_MS                       = "60000"
    BUYER_CODE_FETCH_CONFIG                       = <<EOF
        "protectedAppSignalsBiddingJsUrl": "<URL to Protected App Signals generateBid UDF>",
        "urlFetchTimeoutMs": 60001, # This has to be > 1 minute.
        "urlFetchPeriodMs": 13000000,
        "prepareDataForAdsRetrievalJsUrl": "<URL to the UDF>"

  }  # runtime_flags

}  # Module "buyer"

Seller ad tech configuration

Using this demo file as an example, sellers must set the following flags. (Note: only Protected App Signals related config is highlighted here). Ad techs need to ensure that they substitute the correct URLs in the placeholders:

module "seller" {
  # ... More config here.

  runtime_flags = {
    # ... More config here.

    ENABLE_PROTECTED_APP_SIGNALS                  = "true"

    SELLER_CODE_FETCH_CONFIG                           = <<EOF
    "urlFetchTimeoutMs": 60001, # This has to be > 1 minute.
    "urlFetchPeriodMs": 13000000,
    "protectedAppSignalsBuyerReportWinJsUrls": {"<Buyer Domain>": "URL to reportWin UDF"}

  }  # runtime_flags

}  # Module "seller"

KV and Ad Retrieval Services

Depending on the strategies chosen to support ad retrieval, the system will require the deployment of one or two instances of the KV service. We will refer to the KV instance instance used for TEE based ad retrieval as the Ad Retrieval Server and to the instance to support contextual path based retrieval as the KV Lookup Server.

In both cases the servers deployment follows the documentation available in the KV server GitHub, the difference between the two cases is that the lookup case works out of the box without any additional configuration whilst the retrieval one requires the deployment of the HandleRequest UDF to implement the retrieval logic. For more details take a look at the KV server onboarding guide. Note that B&A expects both of the services to be deployed in the same service mesh as bidding service.

Example Setup

Consider the following scenario: using the Protected App Signals API, an ad tech stores relevant signals based on user app usage. In our example, signals are stored that represent in-app purchases from several apps. During an auction, the encrypted signals are collected and passed into a Protected Auction running in B&A. The buyer's UDFs running in B&A use the signals to fetch ad candidates and compute a bid.

[Buyer] Signals examples

Adds a signal with a key of 0 and a value of 1.

  "put": {
    "AA==": "AQ=="
  "update_encoder": {
    "action": "REGISTER",
    "endpoint": ""

Adds a signal with a key of 1 and a value of 2.

  "put": {
    "AQ==": "Ag=="
  "update_encoder": {
    "action": "REGISTER",
    "endpoint": ""

[Buyer] encodeSignals example

Encodes each signal into two bytes, with the first byte being the first byte of the signal key and the second byte being the first byte of the signal value.

function encodeSignals(signals, maxSize) {
  // if there are no signals don't write a payload
  if (signals.size === 0) {
      return {};

  let result = new Uint8Array(signals.size * 2);
  let index = 0;
  for (const [key, values] of signals.entries()) {
    result[index++] = key[0];
    result[index++] = values[0].signal_value[0];
  return { 'status': 0, 'results': result};

[Buyer] prepareDataForAdRetrieval example

 * `encodedOnDeviceSignals` is a Uint8Array and would contain
 * the app signals emanating from device. For purpose of the
 * demo, in our sample example, we assume that device is sending
 * the signals with pair of bytes formatted as following:
 * "<id><In app spending>". Where id corresponds to an ad category
 * that user uses on device, and the in app spending is a measure
 * of how much money the user has spent in this app category
 * previously. In our example, id of 0 will correspond to a
 * fitness ad category and a non-zero id will correspond to
 * food app category -- though this info will be useful
 * later in the B&A pipeline.
 * Returns a JSON object indicating what type of ad(s) may be
 * most relevant to the user. In a real setup ad techs might
 * want to decode the signals as part of this script.
 * Note: This example script makes use of only encoded device signals
 * but adtech can take other signals into account as well to prepare
 * the data that will be useful down stream for ad retrieval and
 * bid generation. The max length of the app signals used in this
 * sample example is arbitrarily limited to 4 bytes.
function prepareDataForAdRetrieval(encodedOnDeviceSignals,
                                   contextualSignals) {
  if (encodedOnDeviceSignals.length === 0 || encodedOnDeviceSignals.length > 4 ||
      encodedOnDeviceSignals.length % 2 !== 0) {
     throw "Expected encoded signals length to be an even number in (0, 4]";

  var preparedDataForAdRetrieval = {};
  for (var i = 0; i < encodedOnDeviceSignals.length; i += 2) {
    preparedDataForAdRetrieval[encodedOnDeviceSignals[i]] = encodedOnDeviceSignals[i + 1];
  return preparedDataForAdRetrieval;

[Buyers] Sample ad retrieval UDF

In our example, the ad retrieval server sends metadata (i.e. ID for each ad in this example but can contain other data for each that can be helpful in generating bids later on) for each of the top k ad candidates.

function HandleRequest(requestMetadata, protectedSignals, deviceMetadata,
                      contextualSignals) {
 return "[{\"adId\":\"0\"},{\"adId\":\"1\"}]"

[Buyers] generateBid example

 * This script receives the data returned by the ad retrieval service
 * in the `ads` argument. This argument is supposed to contain all
 * the Protected App Signals related ads and the metadata obtained from the retrieval
 * service.
 * `preparedDataForAdRetrieval` argument contains the data returned
 * from the `prepareDataForAdRetrieval` UDF.
 * This script is responsible for generating bids for the ads
 * collected from the retrieval service and ad techs can decide to
 * run a small inference model as part of this script in order to
 * decide the best bid given all the signals available to them.
 * For the purpose of the demo, this sample script assumes
 * that ad retrieval service has sent us most relevant ads for the
 * user and this scripts decides on the ad render URL as well as
 * what value to bid for each ad based on the previously decoded
 * device signals. For simplicity sake, this script only considers
 * 2 types of app categories i.e. fitness and food.
 * Note: Only one bid is returned among all the
 * input ad candidates.
function generateBid(ads, sellerAuctionSignals, buyerSignals, preparedDataForAdRetrieval) {
  if (ads === null) {
    console.log("No ads obtained from the ad retrieval service")
    return {};
  const kFitnessAd = "0";
  const kFoodAd = "1";
  const kBuyerDomain = "";
  let resultingBid = 0;
  let resultingRender = kBuyerDomain + "/no-ad";
  for (let i = 0 ; i < ads.length; ++i) {
    let render = "";
    let bid = 0;
    switch (ads[i].adId) {
      case kFitnessAd:
        render = kBuyerDomain + "/get-fitness-app";
        bid = preparedDataForAdRetrieval[kFitnessAd];
      case kFoodAd:
        render = kBuyerDomain + "/get-fastfood-app";
        bid = preparedDataForAdRetrieval[kFoodAd];
        console.log("Unknown ad category");
        render = kBuyerDomain + "/no-ad";
    console.log("Existing bid: " + resultingBid + ", incoming candidate bid: " + bid);
    if (bid > resultingBid) {
      resultingBid = bid;
      resultingRender = render;
  return {"render": resultingRender, "bid": resultingBid};

[Buyers] reportWin example

The reportWin UDF reports to the buyer that they won the auction.

function reportWin(auctionSignals, perBuyerSignals, signalsForWinner,
                                       buyerReportingSignals, directFromSellerSignals,
                                       temporaryUnlimitedEgressPayload) {

[Seller] KV server setup

Sellers must set up a scoring signals KV server so that there is a mapping available from the ad render URLs to corresponding scoring signals, for example: if https:/ and https:/ were to be returned by the buyer, the seller can have following example scoring signals response when queried by the SFE using a GET on<render-url-returned-by-the-buyer>:

   "renderUrls" : {
      "https:/" : [
      "https:/" : [

[Seller] scoreAd example

 * This module generates a random desirability score for the Protected App
 * Signals ad in this example. In a production deployment,
 * however, the sellers would want to use all the available signals to generate
 * a score for the ad.
function getRandomInt(max) {
  return Math.floor(Math.random() * max);

function scoreAd(adMetadata, bid, auctionConfig,
                                   trustedScoringSignals, deviceSignals,
                                   directFromSellerSignals) {
  return {
    "desirability": getRandomInt(10000),
    "allowComponentAuction": false

[Seller] reportResult example

function reportResult(auctionConfig, sellerReportingSignals, directFromSellerSignals){
  let signalsForWinner = {};
    return signalsForWinner;

Sample app

As an example of how the API can be used to create an app which uses a simple flow as described above, we've created a Protected App Signals sample app which can be found in this sample repository.