The Kevel Developer Hub

Welcome to the Kevel developer hub. You'll find comprehensive guides and documentation to help you start working with Kevel as quickly as possible, as well as support if you get stuck. Let's jump right in!

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.

❗️

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. Willful use at scale could be considered abuse of the Kevel platform.

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.

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 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.

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

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.

{
  "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"
      }
    ]
  }
}

Property

Description

phase

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

channel

The integer channel ID

priority

The integer priority ID

advertiser

The integer advertiser ID

campaign

The integer campaign ID

flight

The integer flight ID

ad

The integer ad ID

ecpm

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

weight

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

info

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.

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 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.

Troubleshooting

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

Updated 2 months ago

Decision Explainer


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.