Skip to content

    Pre-launch API. The concepts described here are settled, but the API shape is not: request and response fields, parameters and defaults can still change. Build against it, and talk to your Luigi's Box contact before you put an integration into production.

    Triggers

    View source

    A campaign’s match decides whether it applies to a given request. It is a condition tree, and every leaf field carries a trigger. prefix.

    Search requests whose query contains “shoes”:

    {
    "operator": "and",
    "conditions": [
    { "field": "trigger.request:query", "operator": "matches", "value": "shoes" }
    ]
    }

    Recommendations anchored on one product:

    {
    "operator": "and",
    "conditions": [
    { "field": "trigger.request:anchor_id", "operator": "eq", "value": "product/sku-1001" }
    ]
    }

    Collection pages of one category, for a collection registered with membership_attribute lbx:category_ids, in a campaign whose intents targeting is ["collection"]:

    {
    "operator": "and",
    "conditions": [
    { "field": "trigger.lbx:category_ids", "operator": "eq", "value": "category/shirts" }
    ]
    }

    Without that targeting, the same condition also fires on recommendations anchored on a product in that category.

    The prefix is what distinguishes this tree from a rule’s record_match: trigger. fields describe the request, unprefixed fields describe a product.

    Three fields, under the reserved request: namespace:

    FieldValue
    trigger.request:queryThe search query. matches fires when the value appears in the query as whole words, in order: gift card fires on “gift card for men”, not on “card gift”, and sho does not fire on “shoes”. Case and accents are ignored. The comparison is on analyzed words, not raw text, so the catalog’s language rules apply first — shoe can fire on “shoes”, and a word the language treats as filler is dropped from both sides before the order is checked.
    trigger.request:typeThe requested object type, e.g. product. Present only when the request is scoped to a single type.
    trigger.request:anchor_idA recommender anchor’s ID in type/value form, e.g. product/sku-1001. A value without its type is refused on save: anchors are always sent typed, so it could never fire

    eq, neq, in and not_in on trigger.request:query compare the whole query, case-insensitively. Any other trigger.request:* name is rejected with 422, and so is a trigger_ref value: a trigger compares against literals only. A match with no conditions fires on every request in the targeting scope.

    No trigger field names the kind of request. To limit a campaign to search, recommendations or collections, set its intents targeting, or target only surfaces of that kind; a trigger.request:intent condition is refused with 422 (filter_unknown_field), pointing there. A trigger cannot name a kind, so a condition that can fire on several of the kinds a campaign targets fires on all of them. To give each kind its own condition, use one campaign per kind.

    Fields from the object the request is about

    Section titled “Fields from the object the request is about”

    Everything else is a catalog attribute of the object the request is about — the recommender’s anchor, or a membership collection’s membership attribute (below):

    { "field": "trigger.brand", "operator": "eq", "value": "brand/northwear" }

    That fires the campaign on any recommendation anchored on a Northwear product, or on collection:brand/northwear/member:product when the brand collection’s membership attribute is brand. It says nothing about which products the rules then affect — that is record_match.

    A recommendation with several anchors fires the campaign when any one of the first ten anchors satisfies the conditions. A membership collection exposes one attribute: its membership attribute, holding the @id the request names. A curated collection exposes each eq and in condition that its filters join with and: { "label": { "eq": "new" } } fires trigger.label eq new. An all collection exposes nothing, so no positive condition on a catalog attribute can fire on it — what a negative one does is the next paragraph. The shopper’s own filters never trigger a campaign.

    A search request has no such object. A positive condition on a catalog attribute (eq, in, a comparison) therefore never fires on search. A negative one (neq, not_in, or a not group) fires on every search request, because the attribute is missing there and a negative test on a missing attribute is true. The same holds for a recommendation with no anchor and for a collection that exposes no attributes. If the attribute condition should decide which products are affected rather than when the campaign fires, put it on the rule instead.

    POST /platform/v1/campaigns/trigger-schema takes a targeting set and returns the trigger fields authorable across it, with the exact operators each one accepts:

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/trigger-schema' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{"organization_id": "lbo_r2vn8c", "catalog_ids": ["lbc_8w3k2p"]}'

    Each entry carries a name (unprefixed — add trigger. to use it), a source of catalog or request, the operators it accepts, and a value_domain saying whether its values are a suggestion:

    value_domainMeaning
    suggestedWhat the scope carries today, but other values are accepted
    openFree input; values is empty

    values is populated for trigger.request:type (suggested); the other two request fields and every catalog attribute are open with an empty list. For a catalog attribute, read the values from catalog metadata instead.

    The response is paginated, and query narrows it to a case-insensitive substring of the field name — a large catalog can carry a lot of attributes.

    Two reasons to call this endpoint rather than assume the field names:

    • The operators it returns are the ones that will be accepted on save. If you are building an authoring UI, offer exactly these and a save can never be rejected on operator grounds. They also tell you which value input to render: range and comparison operators mean the field is numeric, membership-only means it is a keyword.
    • It answers for the whole targeted scope, conservatively. A field that is numeric in one targeted catalog and text in another is reported with the keyword-safe operator set, because that is the set that will survive validation across every catalog in scope.

    The same syntax serves match and record_match. A condition tree is operator plus conditions, nestable:

    {
    "operator": "and",
    "conditions": [
    { "field": "brand", "operator": "eq", "value": "brand/northwear" },
    {
    "operator": "or",
    "conditions": [
    { "field": "color", "operator": "eq", "value": "blue" },
    { "field": "color", "operator": "eq", "value": "navy" }
    ]
    }
    ]
    }

    Group operators are and, or, not. Leaf operators depend on the field’s type:

    FieldOperators
    Text, and any field the catalog has no type foreq, neq, in, not_in, exists
    Numericthe above plus gt, gte, lt, lte, range
    Booleaneq, neq, exists
    trigger.request:type, trigger.request:anchor_idas text
    trigger.request:queryas text, plus matches

    matches works only on trigger.request:query: not on any other trigger field, and not in a rule’s record_match. The trigger-schema endpoint reports the exact set for each field.

    A tree may hold up to 100 conditions in total, in groups nested up to 10 deep, and one in or not_in condition may name up to 100 values. Past any of these, the save is rejected; split a tree that large into several rules. Only conditions count, not the groups holding them. Both limits apply to the tree as stored, so the presence checks added below count towards them — usually one extra condition per negation, and a level when a negation under an or is wrapped with its check in an and — and a rejection says when the pairing is what put the tree over.

    The evaluation rules are the same as discovery filters, with one difference: a negation is stored with a presence check beside it, so it reads as “present, and different” rather than firing on every request that happens to lack the field. Outside a not that means neq and not_in; inside one it is the positive operators, since not (brand neq nike) already means “has a brand, and it is nike”. @id, @type and @updated_at are on every object and are left as written, as is a not around a whole group. A negation on @title is paired like any other field’s: a rule does not depend on every stored object carrying a title.

    The three trigger.request:* fields are left as written too. A recommendation request carries no trigger.request:query, so trigger.request:query neq "nike" does fire on every one of them — but that is the request being of another kind, which you target through the campaign’s targeting (intents, or surfaces of that kind) rather than through a presence check. To keep a request-field negation off a kind, name the kinds the campaign serves.

    To keep the original reading on a catalog attribute — “not nike, and brandless is fine” — write both halves out; see Keeping the original reading.

    The tree is written differently, and campaigns accept two operators discovery does not — matches and range. See Other surfaces that filter.