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.

    Rules and effects

    View source

    Each rule pairs a record_match — which products it applies to — with one effect.

    {
    "name": "Bury clearance stock",
    "record_match": {
    "operator": "and",
    "conditions": [
    { "field": "clearance", "operator": "eq", "value": true }
    ]
    },
    "effect": { "type": "bury", "strength_pct": 0.6, "mode": "hard" }
    }

    A rule’s name is required and is for you: it is what someone reads in six months when they find results they cannot explain.

    EffectWhat it doesFields
    pinPlace the product at a fixed positionposition (1-indexed), mode, decay_window
    banRemove the product from results entirely
    boostRaise the product’s rankingstrength_pct (0–1), mode, decay_window
    buryLower the product’s rankingstrength_pct (0–1), mode, decay_window

    They divide into two kinds:

    • pin and ban are absolute. A pin puts a product at a position; a ban removes it. Both are decisions about the output rather than about how strongly a product scores.
    • boost and bury are relative. They change how strongly a product scores and let the results re-sort. Both are decisions about tendency.

    position is 1-indexed: 1 is the top slot. The product is inserted there and everything below shifts down — nothing is dropped.

    A pin naming one product places that product. A pin whose selector names several products — a list of IDs, or a criterion like brand eq "northwear" — places whichever of them ranks highest, and several such pins fill their positions with distinct products. So “pin our best Northwear product to slot 1” is one rule, not a lookup you have to do yourself.

    A pinned product does not have to match the query. A campaign triggered by the query “wine” can pin a box of chocolates for Valentine’s Day: the product is fetched and placed even though the search would never return it. A pin naming one product has nothing to place when that product does not exist or a ban removes it. A pin selecting by criteria, or by a list of IDs, moves down its matches instead: a ban takes its top match out of the running and the next one takes the slot, so it places nothing only once no match is left.

    A pin naming exactly one product also ignores the shopper’s filters by default: it shows the product even when the active filters would exclude it. A pin selecting by criteria, or by a list of IDs, only places a product that passes those filters.

    Set respect_user_filters to true on the effect to turn that off, and the pin is filtered like any other product — it places nothing when the shopper’s filters exclude what it names. Use it when a product must not be shown past a filter: an age-restricted or regulated item the shopper has filtered away should stay away.

    { "type": "pin", "position": 1, "respect_user_filters": true }

    strength_pct is a strength between 0 and 1, not a number of positions. How far a product actually moves depends on how tightly packed the results around it are, so a boost does not lift a product over results that match the query substantially better.

    Start around 0.3 for a nudge and 0.7 for a strong push, then look at the result.

    Boost and bury act on products the results already contain. A product that would not have been retrieved for this request is not boosted into it — a boost changes the order of an answer, it does not change what the answer is about. If you need a product to appear on a request it does not match, that is a pin.

    Boost and bury never cancel out: when both would apply to one product, one wins outright and the other is discarded. At equal specificity the bury wins — see Resolving conflicts.

    pin, boost and bury can fade. mode: "hard" (the default) applies the effect at full strength for the campaign’s whole run. mode: "soft" fades it out — full strength at the start, tapering to nothing. A ban is always hard: there is no half of a product removed.

    { "type": "boost", "strength_pct": 0.5, "mode": "soft", "decay_window": "P3D" }

    What fading means depends on the effect. A soft boost or bury weakens its score modifier until it stops moving the product at all. A soft pin loosens its hold on the position: the product starts at the slot the pin names and drifts back toward where it would have ranked on its own, instead of dropping back there in one step when the campaign ends.

    { "type": "pin", "position": 1, "mode": "soft", "decay_window": "P3D" }

    decay_window is an ISO 8601 duration: P3D is three days, PT12H twelve hours. It is only valid with mode: "soft", and must be positive. Leave it out and the fade lasts the whole campaign.

    The fade counts from starts_at and needs both starts_at and ends_at: a soft effect in a campaign without both bounds behaves like a hard one and never fades. Once the fade reaches zero the rule stops applying for the rest of the campaign.

    Soft mode is how you launch a new product hard and let it earn its own place, instead of ending a campaign with a cliff.

    Pins are placed after scores are adjusted, so a pin overrides any movement a boost gave the same product. A product boosted to position 2 and then pinned to 1 ends up at 1 — and a product boosted upward while another product is pinned above it lands one slot lower than the boost alone would suggest.

    Pin for a position, boost for a tendency. When both apply to one product, the pin decides.

    record_match uses the same condition syntax as a trigger, over catalog fields rather than trigger. fields. It selects by identity or by criteria, and the difference decides who wins a conflict:

    By identity — the most specific kind of rule:

    { "field": "@id", "operator": "in", "value": ["product/sku-4410", "product/sku-4411"] }

    By criteria — matches whatever fits, now and later:

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

    Prefer criteria. A rule on clearance = true keeps meaning the right thing as stock turns over; a list of IDs has to be maintained as stock changes.

    “Product” here means any catalog object the surface serves. The same selectors and effects apply to categories, brands or articles, and the ID carries the type: category/shoes works like product/sku-1001.

    For a pin, a list of several IDs counts as criteria, not identity: a pin claims one position, so several IDs mean “whichever of these ranks highest”. Pin a single product when it must win a conflict on specificity.

    Operators follow the field’s type: gt, gte, lt, lte and range need a numeric field; eq, neq, in, not_in and exists work on any field, as long as the literal matches that field’s type. Break either rule — an operator the type does not support, or a literal of the wrong type — and the save fails with 422. matches is not available in record_match at all.

    A selector is a little more permissive than a trigger, so read the operator table as the trigger’s set rather than this one’s: membership works on a boolean here, where a trigger admits only eq, neq and exists. {"field": "clearance", "operator": "in", "value": [true]} is accepted in a selector and rejected in a trigger.

    The missing-attribute rule bites here more than anywhere: a rule matching {"field": "@category", "operator": "neq", "value": "category/shoes"} also fires on every product with no category at all.

    For ban, boost and bury, a selector with no conditions is a scope-wide effect — blunt, but coherent, and occasionally what you want. For pin it is rejected: a pin places one product at one position, so a pin that names no product asks for a placement without saying what to place.

    A rule’s value can reference the trigger instead of a literal — “products whose brand is the same as the anchor’s brand”:

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

    That is one rule that works for every brand, instead of one rule per brand. The operator has to match the reference’s cardinality: a reference resolving to a single value takes eq or neq, one resolving to a list takes in or not_in. On a search request there is no anchor to read the attribute from, so a rule referencing trigger.brand matches no product there.

    For a list-valued trigger attribute, pick which elements to use with path and selectfirst, last, all, first_n, last_n, at_index, all_but_first, all_but_last. first_n and last_n need an n; at_index needs an index. The default is the first element of the outer list and all elements within it.

    • Resolving conflicts — who wins when rules collide
    • Triggers — the condition syntax, and which requests fire
    • Filters — the operator vocabulary and the missing-attribute rule