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.

    Recommendations

    View source

    A recommender takes anchors — the products the current context is about — and returns items related to them. With no anchors it still answers, from the session and from what is popular.

    POST /discovery/v1/recommender serves every placement: the product page strip, the cart cross-sell, the homepage carousel, the abandoned-basket email.

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/discovery/v1/recommender?channel_id=lbn_4hj9tv&surface_id=lbs_recommender_pdp_similar' \
    -H 'Authorization: Bearer <token>' \
    -H 'X-Lbx-Visitor-Id: 8f14e45f-ea0f-4b5c-9a1d-2b3c4d5e6f70' \
    -H 'Content-Type: application/json' \
    -d '{
    "type": "product",
    "anchor_ids": ["product/sku-1001"],
    "size": 8
    }'
    Query parameterRequiredNotes
    channel_idYesServing destination
    surface_idYesMust be a recommender surface
    Body fieldRequiredDefaultNotes
    typeYesUsually product
    anchor_idsNononeUp to 50 object identities the recommendation is based on
    sizeNo101–100
    filtersNononeFilter tree
    dedup_optionsNononeUp to 20 deduplication hints
    return_fieldsNosurface defaultUp to 100 fields — see Selecting fields
    user_id / personalizeNoanonymous / truePersonalization

    Anchors are full typed identities, exactly as they appear in @id, in one array:

    { "anchor_ids": ["product/sku-1001", "product/sku-2087"] }

    There is no sort and no cursor: a recommender returns one fixed-size slate.

    {
    "hits": [
    { "@id": "product/sku-2087", "@type": "product", "@title": "Blue Linen Shirt", "price": 44.9 }
    ],
    "total": 8
    }

    Recommendations are a fixed-size slate, not a browsable result set, so the response has no guid and no cursor: there is no page two of a carousel, and there are no facets over one. total is the number of items in the slate.

    Which model runs, what it considers related, and how it treats the anchors are all the surface’s configuration — not request fields. So one placement is one surface:

    PlacementAnchors you sendSurface, by example
    Product page — similar itemsThe product being viewedlbs_recommender_pdp_similar
    Product page — bought togetherThe product being viewedlbs_recommender_pdp_cross_sell
    CartEvery product in the cartlbs_recommender_basket
    HomepageNonelbs_recommender_homepage
    Post-purchase emailThe products orderedlbs_recommender_post_purchase
    Empty search resultsThe query’s best guess, or nonelbs_recommender_zero_results

    Two placements sharing a surface share its tuning: retuning it for the homepage retunes it in the cart too. Each placement is still measured separately.

    Homepage and landing-page strips have nothing to anchor on. Send no anchor_ids and the surface falls back to what it knows: the session’s behaviour, and what is popular in the channel.

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/discovery/v1/recommender?channel_id=lbn_4hj9tv&surface_id=lbs_recommender_homepage' \
    -H 'Authorization: Bearer <token>' \
    -H 'X-Lbx-Visitor-Id: 8f14e45f-ea0f-4b5c-9a1d-2b3c4d5e6f70' \
    -H 'Content-Type: application/json' \
    -d '{ "type": "product", "size": 12 }'

    Keep X-Lbx-Visitor-Id stable so the surface can recognise a returning shopper.

    filters applies to recommendations as on the other endpoints. Two common uses:

    // Never recommend what cannot be bought
    { "availability": { "eq": 1 } }
    // Never recommend across a boundary that would confuse the shopper
    { "gender": { "in": ["women", "unisex"] } }

    To exclude what is already in the cart, build the tree from the cart contents rather than post-filtering the response, so the slate comes back full.

    const filters = {
    availability: { eq: 1 },
    '@id': { not_in: cart.map((line) => line.id) },
    };

    Send not_in only when the cart is non-empty — an empty array is a 422.

    dedup_options carries hints about what counts as a duplicate within one slate — most usefully, collapsing several variants of the same product into one recommendation. The accepted values depend on your surface’s configuration; ask your Luigi’s Box contact which apply to yours.

    If your catalog uses variant groups, collapsing is usually configured on the surface instead, and needs nothing on the request. See Product variants.

    The slate is recorded when the request is served, including which surface answered and which anchors you sent. Report what the shopper does with it — clicks, cart additions, purchases. See Sending events.