Skip to main content
Skip table of contents

Recommendations

Use the Recommendations API and tag to take advantage of machine learning-powered recommendations for your content. Recommendations are powered by resources, which are defined at the site level using custom schema.

Resource Filters

Learn more about calling resources directly in Look-Ups.

Resource filters are useful for narrowing your search, which returns results faster. You can use resource filters on either the resources or recommendations API calls.

Field Types

While each account has custom fields, these fields must map to one of the following field types. The Sample Values column shows how data is usually stored on the resource. The How to Filter in Payloads column shows what API callers typically send when using the simple JSON format or colon strings.

Type Name

Definition

Sample Values

How to Filter in Payloads

TEXT

Freeform string

Breaking news

JSON: string in array, e.g. ["politics"].

Colon: plain text; multiple values separated by the pipe character `

TAGSET

Set of string tags

["Sale", "Outdoor"] (conceptually a set)

Same as TEXT: each entry is a string. Matching lowercases for comparison.

FLAG

Boolean

true / false

JSON: use JSON booleans only[true] or [false] (lowercase, no quotes).

COUNT

Integer amount

42

JSON: integer in array, e.g. [20].

Colon: YAML parses numbers, e.g. 20. Not a quoted string in JSON.

USD

Currency (US dollars)

19.99

JSON: number, e.g. [19.99].

Colon: 19.99 or 10.

SCORE

Floating-point score

0.87

JSON: number.

Colon: YAML number.

DATETIME

Absolute point in time (publish time, expiry, etc.)

Stored internally as a numeric instant (typically seconds since the Unix epoch, 1970-01-01 00:00:00 UTC). APIs that expose the resource may show this as a number or as an ISO-8601 timestamp string depending on the endpoint — that is only the stored value, not the colon-filter syntax.

Compact filters and simple JSON (ApiFilter): only ISO-8601 duration strings in values, combined with AFTER, BEFORE, or BETWEEN

Advanced JSON: use recency for the same duration-based logic, or range with amt_type: "SECONDS" for an absolute window in epoch seconds.

URI

String URI

https://example.com/a

JSON: string(s) in values.

Colon: string segment(s).

GEO_POINT

Latitude / longitude

JSON object {"lat": 40.7, "lon": -74.0} on the resource

The simple name/operator/values API is built for overlap/range/existence patterns on text, tags, amounts, and times. For GEO_POINT, only EXISTS is practical here. Point equality or radius is not expressed as a comma-separated pair in this format; use the advanced JSON model if your pipeline supports it, or filter outside this API.

Your site’s schema decides the type of each field name. If an operator is not valid for that field’s type, the API returns an error.

Preferred Filter Syntax

There are three ways to represent filters in the Recommendations API — simple JSON array, colon format, and advanced JSON object (thrift/schema style) — but we highly recommend using a simple JSON array because it’s easy to generate, read, and parse differences.

Format

Definition

Sample Values

Simple JSON array

 

Send an array of objects. Each object has the same three logical fields as ApiFilter: name, operator, values.

  • When filters or query (Candidates resource search) or filters (Recommendations section) is an array, each element may be either a string (colon format) or an object {"name","operator","values"}.

  • Multiple objects in the array are combined with an implicit AND: every condition must pass.

  • operator may be omitted; it defaults to OR (same as colon format).

CODE
{
  "data": {
    "query": [
      {
        "name": "product_code",
        "values": ["SKU-001"],
        "operator": "="
      },
      {
        "name": "hierarchy_key",
        "values": ["ROOT"],
        "operator": "="
      },
      {
        "name": "primary_image_url",
        "values": [true],
        "operator": "EXISTS"
      },
      {
        "name": "category_display_name",
        "values": [true],
        "operator": "EXISTS"
      }
    ]
  }
}

Compact filter strings (colon format)

Used by the compact parser (ApiFilter in bt_site_configuration.api_filter) for either GET or POST calls.

Format: exactly three segments separated by colons (the field name must not contain a colon):

CODE
fieldName:OPERATOR:valueSegment
CODE
genre:OR:comedy|drama
list_price:BETWEEN:10|99.99
pubDate:AFTER:-P7D
in_stock:EQUAL:true

Advanced JSON nested object

A single JSON object whose top-level keys are logical operators or filter kinds (and, or, overlap, range, recency, existence, named, any) is parsed by Filter.from_json. It maps to the Thrift TFilter union.

That form is verbose and is what you usually see inside named filter definitions in the site schema. The public APIs allow a single object instead of an array in some places (Recommendations filters can be an object or an array), but typical client code uses the array of simple objects.

CODE
{
  "named_filters": {
    "fishing_demo": {
      "and": [
        {
          "overlap": {
            "field": "category",
            "min": 1,
            "values": ["Fishing"],
            "match_type": "EXACT"
          }
        },
        {
          "existence": {
            "field": "in_stock",
            "exists": true
          }
        },
        {
          "range": {
            "field": "price",
            "min": 76.0,
            "amt_type": "USD"
          }
        },
        {
          "existence": {
            "field": "thumbnail",
            "exists": true
          }
        }
      ]
    }
  }
}

A production schema contains many more keys; only the structure above is required for each named filter value.

Operators

Operator names are case-insensitive. = and EQUAL mean the same as OR with a single value.

Operator

Definition

Field Types

Limitations

Simple JSON Array Sample

OR / = / EQUAL

Exact match

Text, Tagset, Flag, Count, USD, Score, URI

 

CODE
{"name": "product_code", "values": ["SKU-001"], "operator": "="}

NOT

Must not match any listed value

Text, Tagset

For booleans, use EXISTS; for numbers and times, use range operators or advanced JSON.

CODE
{"name": "status", "values": ["draft"], "operator": "NOT"}

CONTAINS

Substring match

Text only

 

CODE
{"name": "title", "values": ["sale", "clear"], "operator": "CONTAINS"}

EXISTS

Field present or absent

Text, Tagset, Flag, Count, USD, Score, Datetime, URI, Geo Point

Must use real, unquoted JSON booleans: true and false. The literal strings "true", "True", "1" in the values array are strings, not booleans. Similarly, the numbers 1 and 0 are integers, not booleans. All of these will be rejected by the validation if they use the EXISTS operator.

CODE
{"name": "category_display_name", "values": [true], "operator": "EXISTS"}

AFTER / BEFORE / BETWEEN

Bounds

Count, USD, Score, Datetime

Requires exactly 2 values.

Datetime supports ISO-8601 durations only (e.g., -P7D)

Each bound is checked with “greater than or equal” on the lower side and “less than or equal” on the upper side. So both endpoints are inclusive: a value equal to the minimum or maximum still matches.

CODE
{ "name": "pubDate", "values": ["-P5D"], "operator": "AFTER" }
{ "name": "pubDate", "values": ["P3D"], "operator": "BEFORE" }
{ "name": "pubDate", "values": ["-P30D", "-P7D"], "operator": "BETWEEN" }

Recommendations

For API documentation, visit Recommendations (Basic).

Recommendations Tag

{% recommendation articles | count: 3 %}

This tag creates a variable called articles and assigns it an array of resource objects based on the entered count (in this example, 3). The number of recommendations you retrieve should be the number of resources you will utilize in your content.

You can add the tag and the array created anywhere in your content. The contents of the returned array can be viewed in content using {{articles}}, or you can populate the contents of the resources with the syntax {{articles[0].title}}. To narrow recommendations to specific types of resources or based on other metadata, you can add filters.

Recommendations trigger events when a recommendation or recommendation set is requested, served, viewed, or clicked. You can easily access them in Report Builder as an Events report, where you'd find any recommendation events with the following event types:

image-20260605-072509.png

Filtered Recommendation Tag

{% recommendation articles | count: 3 | filter: 'resource-type', '', 'article' %}

This tag includes a filter for the metadata of resources where the resource type is an article.

Additional filters, such as user properties, can be added by separating them with a pipe.

For example:

{% recommendation dogs_and_more | count: 5 | filter: 'keywords', '=', 'dogs' | filter: 'keywords', '=', '{{animal_preference_2}}' %}

You can swap out the filter operator for any valid operator (=, CONTAINS, NOT, AFTER, BEFORE, BETWEEN). An empty string (no operator specified) is treated the same as having equality (=):

{% recommendation dogs_and_more | count: 5 | filter: 'keywords', '', 'dogs' | filter: 'keywords', '', '{{animal_preference_2}}' %}

This will filter to resources that contain either “dogs” or “cats” as a keyword:

{% recommendation dogs_or_cats | count: 3 | filter: 'keywords', '=', 'dogs|cats' %} 

This will filter to resources with the color field set to “red”:

{% recommendation only_red | count: 3 | filter: 'color', '=', 'red' %} 

To include multiple recommendations in an email without clipping, avoid using pipes ("|") between resource IDs in dynamic values. Instead, enter the resource IDs one by one by clicking the "+" sign in the field. The system is designed to handle values individually, so adding 3-4 IDs at once should be done by entering them one at a time.

Filtered Contains Tag

For example:

{% recommendation example-cars | count: 3 | filter: 'resource-type', '=', 'product' | filter: 'brand', 'CONTAINS', 'General' %}

For a list of resources, it's similar:

{% resources example-autos | count: 3 | filter: 'resource-type', '=', 'product' | filter: 'brand', 'CONTAINS', 'Motors' | sort_field: 'start_date' | sort_order: 'asc' %}

The available operators for both of these in email are documented in the table below:

Operator

Description

'=' (EQUALS)

Can also be empty quotes ''

'CONTAINS'

Includes the provided value as a portion of the field value.

'NOT'

Does not equal

'BEFORE'

Prior to the time period relative to the current date (3 days ago, 7 days from now, etc.)

Only works with valid date fields

'AFTER'

After a time period relative to the current date (3 days ago, 7 days from now, etc.).

Only works with valid date fields

  • Filtering could reduce the quality of personalization. The Recommendations engine will override the filter if it cannot retrieve the requested number of recommendations.

  • The filter names are all dependent on the available resource fields. If a resource has an author, you can filter by it. If not, it doesn't work.

  • Filters have very specific formats that must be respected when building your query. For example, sku_last_updated GREATER THAN 13 hours ago won’t return any results because it’s not a valid format; instead, use  'sku_last_updated', 'BEFORE', '-PT13H'.

  • Filters for bt_created_at and bt_updated_at are only supported for resources uploaded after .

  • It's important to note that the image URLs are hosted on the client's server, not on Zeta's infrastructure.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.