What is AdQuery?

AdQuery is a Decision API feature that filters which ads get selected based on properties of the ads. It provides fine-grained controls on the ad request side - think of AdQuery as querying over ads in a database and returning selections that match your query.

Say a marketplace displays sponsored listings in search results. When the marketplace's users select a price range and a category in the search (like a price between 0 and 200 and a category "lawn-care"), the marketplace makes a Decision API request that queries for those prices and category. The response will only contain ads that meet those criteria. The marketplace then integrates those ads into the search as sponsored results.

How AdQuery Works

AdQuery uses creative template field values as queryable properties. These values must be strings ("lawn-care"), numbers (24, 26.72 etc.), or arrays (["lawn-care", "tools"]).

In order to be used for AdQuery, a creative template field must have "AdQuery": true set while creating or updating the creative template.

A Kevel user sets values for those fields when creating a creative template ad in the API or UI. Using the marketplace example, each seller's product would be given a creative, and queryable details from that product would be set on the creative as values.

Once an ad is live, a Decision API request makes an ad query on a placement to return any eligible ads for that query.

Setting Up AdQuery Templates and Ads

Create a creative template with AdQuery enabled fields. OR update an existing template with new AdQuery enabled fields.

To make a field enabled for AdQuery, include the parameter "AdQuery" set to true. The Variable of the field will become the key used for the AdQuery.

Here's an example creative template with three enabled fields:

{
    "Name": "AdQuery Is Cool",
    "Description": "v1",
    "IsArchived": false,
    "Fields": [
        {
            "Name": "Product Price",
            "Description": "Product Price",
            "Required": true,
            "Variable": "ctPrice",
            "AdQuery": true,
            "Type": "Number"
        },
        {
            "Name": "Product Size",
            "Description": "Product Size",
            "Required": false,
            "Variable": "ctSize",
            "AdQuery": true,
            "Type": "String"
        },
        {
            "Name": "Product Categories",
            "Description": "Product Categories",
            "Required": false,
            "Variable": "ctCategories",
            "AdQuery": true,
            "Type": "Array"
        }
    ],
    "Contents": [
        {
            "Type": "Raw",
            "Body": "."
        }
    ]
}
curl -X POST \
     -H "X-Adzerk-ApiKey: $ADZERK_API_KEY" \
     -H "Content_Type: application/json" \
     -d '{
        "Name": "AdQuery Is Cool",
        "Description": "v1",
        "IsArchived": false,
        "Fields": [
          {
          "Name": "Product Price",
          "Description": "Product Price",
          "Required": true,
          "Variable": "ctPrice",
          "AdQuery": true,
          "Type": "Number"
          },
          {
          "Name": "Product Size",
          "Description": "Product Size",
          "Required": false,
          "Variable": "ctSize",
          "AdQuery": true,
          "Type": "String"
          },
          {
          "Name": "Product Categories",
          "Description": "Product Categories",
          "Required": false,
          "Variable": "ctCategories",
          "AdQuery": true,
          "Type": "Array"
          }
        ],
        "Contents": [
          {
          "Type": "Raw",
          "Body": "."
          }
        ]    
     }' \
     "https://api.kevel.co/v2/creative-templates"
const Adzerk = require('@adzerk/management-sdk');
const apiKey = process.env.ADZERK_API_KEY;

async function createCreativeTemplate() {
  let specifications = await Adzerk.fetchSpecifications();
  let client = await Adzerk.buildClient({apiKey, specifications});

  let creativeTemplate = await client.run('creativeTemplate', 'create', {
    name: 'AdQuery Is Cool',
    description: 'v1',
    isArchived: false,
    fields: [{
      name: 'Product Price',
      description: 'Product Price',
      required: true,
      variable: 'ctPrice',
      adQuery: true,
      type: 'Number'
    }, {
      name: 'Product Size',
      description: 'Product Size',
      required: false,
      variable: 'ctSize',
      adQuery: true,
      type: 'String'
    }, {
      name: 'Product Categories',
      description: 'Product Categories',
      required: false,
      variable: 'ctCategories',
      adQuery: true,
      type: 'Array'
    }],
    contents: [{
      type: 'Raw',
      body: '.'
    }]
  });

  return creativeTemplate;
};
require "adzerk"

def create_creative_template()
  client = Adzerk::Client.new(ENV["ADZERK_API_KEY"])
  data = {
    name: 'AdQuery Is Cool',
    description: 'v1',
    is_archived: false,
    fields: [{
      name: 'Product Price',
      description: 'Product Price',
      required: true,
      variable: 'ctPrice',
      ad_query: true,
      type: 'Number'
    }, {
      name: 'Product Size',
      description: 'Product Size',
      required: false,
      variable: 'ctSize',
      ad_query: true,
      type: 'String'
    }, {
      name: 'Product Categories',
      description: 'Product Categories',
      required: false,
      variable: 'ctCategories',
      ad_query: true,
      type: 'Array'
    }],
    contents: [{
      type: 'Raw',
      body: '.'
    }]
  }

  client.creative_templates.create(data)
end

Once the creative template is created:

API

  1. Create creatives that supply values for the AdQuery enabled fields. This creative should include the TemplateId, and the AdQuery values should go in the TemplateValues. Refer to the creative endpoint docs for details.
{
    "AdvertiserId": 12345,
    "AdTypeId": 5,
    "Title": "Price: 100 Dollars!!!",
    "IsActive": true,
    "ScriptBody": "Come buy this cool thing for a Benjamin",
    "IsHTMLJS": true,
    "TemplateId": 12345,
    "TemplateValues": "{\"ctPrice\":100}"
}
curl -X POST \
     -H "X-Adzerk-ApiKey: $ADZERK_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
        "AdvertiserId": 12345,
        "AdTypeId": 5,
        "Title": "Price: 100 Dollars!!!",
        "IsActive": true,
        "ScriptBody": "Come buy this cool thing for a Benjamin",
        "IsHTMLJS": true,
        "TemplateId": 12345,
        "TemplateValues": "{\"ctPrice\":100}"
     }' \
     "https://api.kevel.co/v1/creative"
const Adzerk = require('@adzerk/management-sdk');
const apiKey = process.env.ADZERK_API_KEY;

async function createCreative() {
  let specifications = await Adzerk.fetchSpecifications();
  let client = await Adzerk.buildClient({apiKey, specifications});

  let creative = await client.run('creative', 'create', {
    advertiserId: 12345,
    adTypeId: 5,
    title: 'Price: 100 Dollars!!!',
    isActive: true,
    scriptBody: 'Come buy this cool thing for a Benjamin',
    isHtmljs: true,
    templateId: 12345,
    templateValues: `{"ctPrice":100}`
  });

  return creative;
}
require "adzerk"

def create_creative_template()
  client = Adzerk::Client.new(ENV["ADZERK_API_KEY"])
  data = {
    advertiser_id: 12345,
    ad_type_id: 5,
    title: "Price: 100 Dollars!!!",
    is_active: true,
    script_body: "Come buy this cool thing for a Benjamin",
    is_htmljs: true,
    template_id: 12345,
    template_values: '{"ctPrice": 100}'
  }

  client.creatives.create(data)
end
  1. Create ads for those creatives to associate them with flights.

UI

You can supply values via the UI by creating an ad on a flight:

  1. In the creative settings, choose the Format "Custom Template".
  2. Select the name of the Template you created earlier.
  3. Enter values for the Template Fields and any other creative settings.
  4. Save the ad.

Setting Up AdQuery Decision API Requests

Make your ad queries in the adQuery key on the placements object. The key for the query is the Variable name of the creative template field.

You can request one or more fields to query:

{
    "placements": [
        {
            "networkId": 12345,
            "siteId": 12345,
            "adTypes": [
                5
            ],
            "adQuery": {
                "ctPrice": {
                    "min": 50,
                    "max": 250,
                    "nullValuesMatch": false
                },
                "ctSize": {
                    "eq": "large",
                    "nullValuesMatch": false
                },
                "ctCategories": {
                    "eq": 136933,
                    "nullValuesMatch": false
                }
            }
        }
    ]
}
curl -H "Content-Type: application/json" \
     -d '{
        "placements": [{
          "networkId": 12345,
          "siteId": 12345,
          "adTypes": [5],
          "adQuery": {
            "ctPrice": {
              "min": 50,
              "max": 250,
              "nullValuesMatch": false
            },
            "ctSize": {
              "eq": "large",
              "nullValuesMatch": false
            },
            "ctCategories": {
              "eq": 136933,
              "nullValuesMatch": false
            }
          }
        }]
     }'\
     "https://e-12345.adzerk.net/api/v2"
const Adzerk = require('@adzerk/decision-sdk');

let client = new Adzerk.Client({networkId: 12345, siteId: 12345});

async function getDecision() {
  let request = {
    placements: [{
      adTypes: [5],
      adQuery: {
        ctPrice: {
          min: 50,
          max: 250,
          nullValuesMatch: false
        },
        ctSize: {
          eq: "large",
          nullValuesMatch: false
        },
        ctCategories: {
          eq: 136933,
          nullValuesMatch: false
        }
      }
    }]
  };

  return await client.decisions.get(request);
}

getDecision();
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(12345).siteId(12345));

    Map<String, Object> ctPrice = new HashMap<String, Object>();
    ctPrice.put("min", 50);
    ctPrice.put("max", 250);
    ctPrice.put("nullValuesMatch", false);

    Map<String, Object> ctSize = new HashMap<String, Object>();
    ctSize.put("eq", "large");
    ctSize.put("nullValuesMatch", false);

    Map<String, Object> adQuery = new HashMap<String, Object>();
    adQuery.put("ctPrice", ctPrice);
    adQuery.put("ctSize", ctSize);

    Placement placement = new Placement()
      .adTypes(Arrays.asList(5))
      .adQuery(adQuery);

    DecisionRequest request = new DecisionRequest()
      .placements(Arrays.asList(placement));

    DecisionResponse response = client.decisions().get(request);
  }  
}
import adzerk_decision_sdk

client = adzerk_decision_sdk.Client(123456, site_id=12345)

request = {
  "placements": [{
    "adTypes": [5],
    "adQuery": {
      "ctPrice": {
        "min": 50,
        "max": 250,
        "nullValuesMatch": False
      },
      "ctSize": {
        "eq": "large",
        "nullValuesMatch": False
      }
    }
  }]
}

decision = client.decisions.get(request)
require "adzerk_decision_sdk"

client = AdzerkDecisionSdk::Client.new(network_id: 12345, site_id: 12345)

request = {
  placements: [{
    ad_types: [5],
    ad_query: {
      ct_price: {
        min: 50,
        max: 250,
        null_values_match: false
      },
      ct_size: {
        eq: "large",
        null_values_match: false
      }
    }
  }]
}

decision = client.decisions.get(request)
import AdzerkSDK

// Demo network, site, & ad type IDs; find your own via the Adzerk UI!
DecisionSDK.defaultNetworkId = 12345
DecisionSDK.defaultSiteId = 12345

let client = DecisionSDK()

var ctPrice: [String: AnyCodable] = [
  "min": .int(50),
  "max": .int(250),
  "nullValuesMatch": .bool(false)
]

var ctSize: [String: AnyCodable] = [
  "eq": .string("large"),
  "nullValuesMatch": .bool(false)
]

var adQuery: [String: AnyCodable] = [
  "ctPrice": ctPrice,
  "ctSize": ctSize
]

var p = Placements.custom(divName: "div0", adTypes: [5])

var reqOpts = PlacementRequest<StandardPlacement>.Options()
reqOpts.userKey = "abc"
reqOpts.additionalOptions = [
  "adQuery": adQuery
]

client.request(placements: [p], options: reqOpts) {response in
  dump(response)
}
AdzerkSdk sdk = new AdzerkSdk.Builder().networkId(12345L).build();

val ctPrice = HashMap<String, Any>()
ctPrice.put("min", 50)
ctPrice.put("max", 250)
ctPrice.put("nullValuesMatch", false)

val ctSize = HashMap<String, Any>()
ctPrice.put("eq", "large")
ctPrice.put("nullValuesMatch", false)

val adQuery = HashMap<String, Any>()
adQuery.put("ctPrice", ctPrice)
adQuery.put("ctSize", ctSize)

val reqeust = new Request.Builder()
    .addPlacement(Placement("div0", 12345, (5)))
    .addAdditionalOption("adQuery", adQuery)
    .build()

sdk.requestPlacement(request, object : AdzerkSdk.DecisionListener {
  override fun success(response: DecisionResponse?) {

  }
  override fun error(error: AdzerkSdk.AdzerkError?) {
    
  }
})

👍

You can also use AdQuery with multi-winner placements to return multiple ads per placement.

Using Arrays

Kevel customers may have Creative Template fields that have multiple sets (or ‘arrays’) of product data (eg SKU, price, brand, category). One product may belong to multiple categories in the customer's catalog and is often represented as an array of category ids.

For example, a Creative's data may be "ctCategories": ["cat1","cat7","cat8"].

In an ad decision request, a Kevel customer will pass the current category of the page which ads are to be shown. AdQuery can also support returning ads for products when the category is not the only category the product belongs to.

For example, on a product listing page for ‘trousers’, AdQuery can support returning ads for ‘trousers’ OR ‘shorts’ OR ‘socks’.

Here’s an example AdQuery query using an array of IDs:

"adQuery": {
 "ctCategories": {
  "in": ["cat7", "cat91"],
  "nullValuesMatch": false
 }
}

Operators

KeyType(s)UsageExample ValueDefault
minnumberThe minimum of a range.0-
maxnumberThe maximum of a range.2000-
eqstring, numberAn exact match. Wildcard characters aren't supported."Honda"-
inarray of strings or numbersMatch on any of the values in the array.["Honda", "Toyota", "Subaru"]-
notobjectDo not match on the query provided. Is used with another operator - currently in is supported.{ "in": [ "Chevrolet" ] }-
nullValuesMatchbooleanWhether to include ads where the queried field has no valuetruefalse

All operators are optional. For instance, a query with a min value but no max would have have an infinite upper limit for that field.

🚧

If an ad query isn't present on a Decision API request, and ad query eligible ads are candidates for that decision, any of the ad query eligible ads will be returned.