Decision Explainer

Overview

The Decision Explainer is a mode of invoking the Decision API that returns information explaining why each candidate ad was or was not chosen. The most common use is to investigate why an ad did not serve.

❗️

Warning

Decision Explainer requests should ONLY be used as a debugging and development tool. Explainer requests add significant overhead to latency, bandwidth, and compute resources. As such, you should NOT use them in your production application.

Making an Explainer Request

To make a Decision Explainer request, you will need your NetworkId, SiteId, API Key, and AdType. You may make a call to the Decision API directly as usual, but set the X-Adzerk-Explain header with your API key. If you are using one of Kevel's many Decision SDKs they handle this feature for you automatically.

You may also want to explore Kevel's Decision Explainer UI which is a user interface built to quickly and easily make explainer requests.

export API_KEY="YOUR_API_KEY"

curl -X POST \
  -H "X-Adzerk-Explain:${API_KEY}" \
  -H "Content-Type:application/json" \
  -d '{"placements": [{"divName": "div0", "networkId": 23, "siteId": 667480, "adTypes": [5]}], "user": {"key": "abc"}, "keywords": ["keyword1", "keyword2"]}' \
  "https://e-23.adzerk.net/api/v2"
import { Client } from "@adzerk/decision-sdk";

const apiKey = process.env.ADZERK_API_KEY;

// Demo network, site, and ad type IDs; find your own via the Adzerk UI!
let client = new Client({ networkId: 23, siteId: 667480 });

let request = {
  placements: [{adTypes: [5]}],
  user: {key: "abc"},
  keywords: ["keyword1", "keyword2"]
};

const options = {
  includeExplanation: true,
  apiKey
};

client.decisions.get(request, options).then(response => {
  console.dir(response, {depth: null})
});
require "adzerk_decision_sdk"

# Demo network, site, and ad type IDs; find your own via the Adzerk UI!
client = AdzerkDecisionSdk::Client.new(network_id: 23, site_id: 667480)

request = {
  placements: [{ adTypes: [5] }],
  user: { key: "abc" },
  keywords: ["keyword1", "keyword2"],
}

options = {
  include_explanation: true,
  api_key: ENV["ADZERK_API_KEY"]
}

pp client.decisions.get(request, options)
import adzerk_decision_sdk
import os

# Demo network, site, and ad type IDs; find your own via the Adzerk UI!
api_key = os.environ.get("ADZERK_API_KEY")
client = adzerk_decision_sdk.Client(23, site_id=667480)

request = {
  "placements": [{"adTypes": [5]}],
  "user": {"key": "abc"},
  "keywords": ["keyword1", "keyword2"],
}

options = {
  "include_explanation": True,
  "api_key": api_key
}

response = client.decisions.get(request, **options)
print(response)
import java.util.*;
import com.adzerk.sdk.*;
import com.adzerk.sdk.generated.ApiException;
import com.adzerk.sdk.generated.model.*;
import com.adzerk.sdk.model.DecisionResponse;

public class FetchAds {
  public static void main(String[] args) throws ApiException {
    // Demo network, site, and ad type IDs; find your own via the Adzerk UI!
    Client client = new Client(new ClientOptions(23).siteId(667480));
    Placement placement = new Placement().adTypes(Arrays.asList(5));
    User user = new User().key("abc");

    DecisionRequest request = new DecisionRequest()
      .placements(Arrays.asList(placement))
      .keywords(Arrays.asList("keyword1", "keyword2"))
      .user(user);

    AdditionalOptions options = new AdditionalOptions()
      .includeExplanation(true)
      .apiKey("YOUR_API_KEY");

    DecisionResponse response = client.decisions().get(request, options);
    System.out.println(response.toString());
  }  
}
(ns readme-explainer
  (:import (com.adzerk.sdk AdditionalOptions Client ClientOptions)
           (com.adzerk.sdk.generated.model DecisionRequest Placement User)))

(defn -main []
  ; Demo network, site, and ad type IDs; find your own via the Kevel UI!
  (let [client (Client. (doto (ClientOptions. (int 23)) (.siteId (int 667480))))
        options (doto (AdditionalOptions.)
                  (.includeExplanation true)
                  (.apiKey (System/getenv "ADZERK_API_KEY")))
        request (doto (DecisionRequest.)
                      (.placements [(doto (Placement.) (.adTypes [5]))])
                      (.keywords ["keyword1" "keyword2"])
                      (.user (doto (User.) (.key "abc"))))]
    (print (-> client (.decisions) (.get request options)))))

Note that the Decision Explainer supports all features of the Decision API -- single placement, multiple placements, multi-winner, etc.

📘

Note

The iOS and Android SDKs do not support the Decision Explainer request mode. Kevel encourages mobile developers to retrieve explainer output via Kevel's Decision Explainer UI , via a Decision SDK request, or via a direct Decision API request.

Requesting information about Desired Ads

Kevel's Decision Explainer also supports requesting information about specific ads. This enables you to gather detailed information about why a certain ad was filtered out of the results. To include desired ad IDs in the request, the X-Adzerk-Explain header needs to be modified to contain a stringified JSON object containing both the API Key and the desired ad IDs.

curl -X POST \
  -H "X-Adzerk-Explain:{\"apiKey\":\"$API_KEY\",\"desiredAdMap\":{\"div0\":[123,456]}}" \
  -H "Content-Type:application/json" \
  -d '{"placements": [{"divName": "div0", "networkId": 23, "siteId": 667480, "adTypes": [5]}], "user": {"key": "abc"}, "keywords": ["keyword1", "keyword2"]}' \
  "https://e-23.adzerk.net/api/v2"

The format of the JSON inside of the header should follow the following format:

// Each placement in the Decision Request should have a corresponding entry.
// in the desiredAdMap. If you want results for a given ad id for multiple
// placements, you need to ensure that id is included in each array in the map.
{
  "apiKey": "YOUR_API_KEY",
  "desiredAdMap": {
    "div0": [123, 456],
    "div1": [789, 1001]
  }
}

API Response

In the response, you will receive an object per Decision. Each Decision object contains a Placement object, which contains the normal decision response, a Results array with information per candidate, a Buckets array with information for the channels, priorities that might contain candidates, and an optional RTB Log array.

Placement

Placement represents a decision in which the ad may be served and contains information about the ad relative to the placement. This section follows the same rules as a typical Decision API response.

{
  "div0": {
    "placement": {
      "id": "fb399927ba14477ab129fcaa32cf0ed8",
      "adTypes": [
        5
      ],
      "zoneIds": [],
      "properties": {},
      "contentKeys": {},
      "divName": "div0",
      "networkId": 10424,
      "siteId": 1117631,
      "eventMultiplier": 1,
      "contentProperties": {
        "schemas": []
      }
    },
    "buckets": [],
    "rtb_log": [],
    "results": []
  }
}

Results

The Results array contains your list of ads and information on why they were or were not selected to serve. If Desired Ads were requested, you will also have a desiredAds array matching the same format as the Results array.

{
  "div0": {
    "placement": {},
    "buckets": [],
    "rtb_log": [],
    "results": [
      {
        "phase": "selection",
        "channel": 44840,
        "priority": 180733,
        "advertiser": 737031,
        "campaign": 1390404,
        "flight": 11169884,
        "ad": 19233247,
        "ecpm": 0,
        "weight": 1,
        "flightWeight": 1,
        "useFlightWeight": false,
        "info": "not selected to serve"
      },
      {
        "phase": "selection",
        "channel": 44840,
        "priority": 180733,
        "advertiser": 737031,
        "campaign": 1389814,
        "flight": 11168241,
        "ad": 19230089,
        "ecpm": 0,
        "weight": 1,
        "flightWeight": 1,
        "useFlightWeight": false,
        "info": "selected to serve"
      }
    ]
  }
}
PropertyDescription
phaseRefers to how long into the ad serving process this ad was still considered a candidate:

targeting means the ad declined to be a candidate for this request because one or more of its targeting rules were not met (i.e. it was filtered out).

selection means the ad had all its targeting rules satisfied, and it made it to the Priority's auction or lottery.
channelThe integer channel ID
priorityThe integer priority ID
advertiserThe integer advertiser ID
campaignThe integer campaign ID
flightThe integer flight ID
adThe integer ad ID
ecpm(effective Cost-Per-Mille) the value for this ad if used in an auction priority.
weightHow much the ad wants to serve to make its goal; higher weight ads are more likely to win in a lottery priority.
flightWeightHow much the ad wants to serve to make its goal; higher weight ads are more likely to win in a lottery priority.
useFlightWeightWhen useFlightWeight is true, the ad will use the flightWeight instead of the weight of the ad.
infoExplanation about why the ads did or did not serve.
898

Typical Explainer Info Messages

❗️

Warning

Kevel is constantly improving the Decision API and its internals, and this can directly affect Explainer output. As such, the Decision Explainer output is subject to change without notice and should not be relied upon for anything other than manual debugging. Info messages especially are meant to be human-readable and not computer-readable.

Buckets

The Buckets array contains objects with Channel and Priority information. Each bucket is a Channel and Priority pair, sorted by highest Channel weight and then by highest Priority weight. The ad server logically looks at each bucket for a possible ad decision before moving on to the next bucket.

{
  "div0": {
    "placement": {},
    "buckets": [
      {
        "channel": {
          "id": 44840,
          "adTypes": [4,5,6,9],
          "votingEnrollmentPercentage": 100,
          "payoutType": 0,
          "impressionType": 1,
          "customTargetingCompiled": "",
          "affinity": 10,
          "priorities": [...]
        },
        "priority": {
          "id": 180730,
          "order": 1,
          "type": "lottery",
          "isKeywordOptimized": false,
          "isSecondPricing": false,
          "isDeleted": false,
          "minBidIncrement": 0.01
        }
      },
      {
        "channel": {...},
      	"priority": {...}
      }
    ],
    "rtb_log": [],
    "results": []
  }
}

RTB Logs

RTB Logs are defined as Real Time Bidding logs and return information about any RTB bid traffic resulting from your decision request. Your account must be RTB-enabled to use these features.

Troubleshooting

Here are a few reasons why an ad may lose a decision request.

  • The Ad was disabled and needs to be enabled
  • The Ad has a start date in the future
  • The Ad has an end date in the past
  • The Ad may have targeting rules applied to it which makes it ineligible to serve for the given request
  • The Ad has a really low impression goal and a very long time frame to serve the impressions in (i.e. very low weight)
  • The Ad is in a lower priority, and a higher priority ad has been selected to serve instead