Decision Explainer


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.


Debugging Tool Only

Decision Explainer requests should be used ONLY 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 our many Decision SDKs they handle this feature for you automatically.

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


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"]}' \
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,

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 = 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)
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()
      .keywords(Arrays.asList("keyword1", "keyword2"))

    AdditionalOptions options = new AdditionalOptions()

    DecisionResponse response = client.decisions().get(request, options);
(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 the Decision Explainer supports all features of the Decision API -- single placement, multiple placements, multi-winner, etc.


Mobile SDKs are NOT Supported

The iOS and Android SDKs do not support the Decision Explainer request mode. We encourage mobile developers to get explainer output via our 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 allows 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"]}' \

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 get 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 and priorities that might contain candidates, and an optional RTB Log array.


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": [
      "zoneIds": [],
      "properties": {},
      "contentKeys": {},
      "divName": "div0",
      "networkId": 10424,
      "siteId": 1117631,
      "eventMultiplier": 1,
      "contentProperties": {
        "schemas": []
    "buckets": [],
    "rtb_log": [],
    "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,
        "info": "not selected to serve"
        "phase": "selection",
        "channel": 44840,
        "priority": 180733,
        "advertiser": 737031,
        "campaign": 1389814,
        "flight": 11168241,
        "ad": 19230089,
        "ecpm": 0,
        "weight": 1,
        "info": "selected to serve"




Refers 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


The integer channel ID


The integer priority ID


The integer advertiser ID


The integer campaign ID


The integer flight ID


The integer ad ID


(effective Cost-Per-Mille) the value for this ad if used in an auction priority


How much the ad wants to serve to make its goal; higher weight ads are more likely to win in a lottery priority


Explanation about why the ads did or did not serve

Typical Explainer Info MessagesTypical Explainer Info Messages

Typical Explainer Info Messages


Output Subject to Change

We are constantly improving our 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 on for anything other than manual debugging. Info messages especially are meant to be human-readable and not computer-readable.


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 stand for Real Time Bidding logs and returns information about any RTB bid traffic resulting from your decision request. Your account must be RTB enabled to use these features.


Here are some reasons an ad could lose a decision request.

My favorite ad:

  • Was disabled and needs to be enabled
  • Had a start date in the future
  • Had an end date in the past
  • Had a really low impression goal and a very long time frame to serve the impressions in (i.e. very low weight)
  • Was in a lower priority, and a higher priority ad was misconfigured to grab all traffic