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

```curl
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"
```

```javascript
import { Client } from "@adzerk/decision-sdk";

const apiKey = process.env.ADZERK_API_KEY;

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})
});
```

```ruby
require "adzerk_decision_sdk"

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)
```

```python
import adzerk_decision_sdk
import os

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)
```

```java
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 {
    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());
  }
}
```

```clojure
(ns readme-explainer
  (:import (com.adzerk.sdk AdditionalOptions Client ClientOptions)
           (com.adzerk.sdk.generated.model DecisionRequest Placement User)))

(defn -main []
  (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
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:

```json
{
  "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.

```json
{
  "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.

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

| Property   | Description |
| ---        | ---         |
| phase      | Refers to how long into the ad serving process this ad was still considered a candidate:<br>`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).<br>`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. |
| flightWeight | How much the ad wants to serve to make its goal; higher weight ads are more likely to win in a lottery priority. |
| useFlightWeight | When `useFlightWeight` is true, the ad will use the flightWeight instead of the weight of the ad. |
| info       | Explanation about why the ads did or did not serve. |

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.

```json
{
  "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 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

Updated 10 months ago

* * *

Copy Page
