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.

    Targeting

    View source

    Targeting decides where a campaign can act. It says nothing about which requests fire it (that is the trigger) or which products it affects (that is the rule).

    Each dimension can be listed directly or by tag:

    DimensionFields
    Catalogscatalog_ids, catalog_tags
    Channelschannel_ids, channel_tags
    Surfacessurface_ids, surface_tags
    Intentsintentssearch, recommender, collection

    Values within one dimension are OR-ed; dimensions are AND-ed. So this targets either catalog, but only on search surfaces:

    {
    "catalog_ids": ["lbc_8w3k2p", "lbc_4hj9tv"],
    "intents": ["search"]
    }

    An empty dimension means all of it, not none. Leaving intents out targets every intent; a campaign with no targeting at all covers the whole organization. That default is deliberate — most campaigns want to be broad — but it also means an accidentally empty dimension widens a campaign rather than disabling it.

    Three rules:

    • Narrowing by channel or surface needs a catalog scope. Surface IDs are only unique within a catalog and a channel belongs to one, so channel_ids, surface_ids or their tags without catalog_ids or catalog_tags is rejected (catalog_scope_required). intents alone is fine.
    • intents and surfaces are mutually exclusive. An intent already selects whole families of surfaces, so combining the two is rejected (intents_conflict). Pick one level of precision.
    • Every targeted catalog has to be able to fire. Targeting an intent no catalog in scope serves is rejected (catalog_without_targeted_intent), and so is a catalog that hosts none of the targeted surface IDs (catalog_without_targeted_surface): catalogs A and B with surface_ids: ["lbs_recommender_homepage"] fail when B has no such surface.

    The two-catalog example above therefore saves only if both catalogs host a search surface.

    A tag is resolved to its members when the campaign is saved, not when a request is served. Adding a catalog to a tag the campaign targets does not change what the campaign serves until the campaign is saved again; an update to the campaign, even one that changes nothing else, picks up the new members. A deleted surface receives no requests, so the campaign stops serving there on its own.

    That freeze applies only to the dimensions you narrowed. A dimension you left open is stored as “any”, not as a list of what existed at the time, so it keeps up on its own: a campaign that names no surfaces serves a surface added to its catalog tomorrow, with nothing to re-save. It is the narrowed dimensions — the channels and surfaces a tag resolved to when you last saved — that stay as they were.

    A campaign for “all German storefronts” therefore keeps meaning that as storefronts come and go, as long as it is saved after the tag changes. The stored targeting is not the whole answer to “what does this campaign cover”; two endpoints resolve the tags as they are right now.

    Tag edits are checked against the campaigns that use the tag. Changing a tag’s members or deleting it fails with 409, naming the campaign, when the change would leave a live campaign with targeting the campaign API would refuse: adding a catalog without lbs_search_main to a tag used by a campaign that targets that surface, for example. Archived campaigns never block a tag edit.

    POST /platform/v1/campaigns/validate-targeting validates a targeting set without persisting anything, and returns what it resolves to right now:

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/validate-targeting' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{
    "organization_id": "lbo_r2vn8c",
    "catalog_tags": ["lbt_k4d7rq"],
    "intents": ["search"]
    }'

    A 200 means the targeting would be accepted, and carries the enumerated scope — every catalog, channel, surface and intent it can serve. A 422 lists every problem in exception_details.problems, each with a stable code, the dimension it concerns and the offending values. The Authoring API lists every code.

    It checks targeting only: match and rules are not validated here.

    GET /platform/v1/campaigns/{campaign_id}/effective-scope answers the same question for a campaign that already exists: every catalog, channel, surface and intent its targeting resolves to right now.

    It reads the tags as they stand now, while a narrowed dimension serves what the tag resolved to at the last save. After a tag edit the two disagree: the scope lists the catalog just added to the tag, which is not yet being served. Save the campaign and they agree again.

    When a campaign is not firing, read it as “what this campaign is meant to cover”, not “what it is covering”. An empty list in a dimension usually means the campaign targets nothing there — a deleted surface, or a tag that no longer matches anything.

    channel_ids is the exception: it lists the channels you created, so a catalog you have not added one to reports an empty list while the campaign still serves on it. Read an empty channel_ids as “not narrowed to a channel”, and check the other three dimensions for why a campaign is not firing.

    When a campaign is not doing what you expect, check in this order — each step rules out everything below it:

    1. Status and schedule. Is it active, and is now inside its window? Only active campaigns are served at all.
    2. Saved since the last tag change. A tag edit reaches the campaign only when the campaign is saved again — and do this before step 3, which cannot tell you it is pending.
    3. Effective scope. Does it cover the catalog, channel and surface you are testing?
    4. Trigger. Does match accept the request you are making?
    5. Rule selector. Does record_match select the products you expect?
    6. Conflicts. Is another rule beating this one? See Resolving conflicts.