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.

    Authoring API

    View source

    Campaigns live on the platform API and need a token for the https://api.<region>.luigisbox.ai/platform audience.

    POST /platform/v1/campaigns
    GET /platform/v1/campaigns
    GET /platform/v1/campaigns/{campaign_id}
    PATCH /platform/v1/campaigns/{campaign_id}
    DELETE /platform/v1/campaigns/{campaign_id}
    POST /platform/v1/campaigns/validate-targeting
    POST /platform/v1/campaigns/trigger-schema
    GET /platform/v1/campaigns/{campaign_id}/effective-scope

    The first five are the resource; the last three are authoring aids — they resolve targeting and tell you what is authorable, without persisting anything. They are covered under Targeting and Triggers.

    FieldRequiredNotes
    organization_idYesImmutable
    nameYes1–256 characters
    matchYesThe trigger tree
    rulesYes1–50 rules
    statusNodraft, active, paused. Defaults to draft.
    starts_at / ends_atNoUTC instants
    timezoneNoInformational; records the zone the schedule was authored in
    catalog_ids, channel_ids, surface_ids, intentsNoTargeting; empty means all
    catalog_tags, channel_tags, surface_tagsNoTargeting by tag

    Each rule:

    FieldRequiredNotes
    nameYes1–256 characters
    record_matchYesProduct selector. Must select something for a pin.
    effectYesOne of pin, ban, boost, bury

    The response is the created campaign with its lbm_… ID, each rule’s lbr_… ID, and audit fields. See Overview for a complete request.

    { "type": "pin", "position": 1 }
    { "type": "ban" }
    { "type": "boost", "strength_pct": 0.4, "mode": "hard" }
    { "type": "bury", "strength_pct": 0.6, "mode": "soft", "decay_window": "P7D" }

    position is a positive integer; strength_pct is between 0 and 1 inclusive; decay_window is an ISO 8601 duration, valid only with mode: "soft", and must be positive. mode applies to boost and bury and defaults to hard.

    SituationResult
    A name already used by another live campaign in the organization409
    channel_ids, surface_ids or their tags without catalog_ids / catalog_tags422, code catalog_scope_required
    intents together with surface_ids / surface_tags422, code intents_conflict
    A catalog or channel from another organization, directly or through a tag422, code catalog_not_in_organization / channel_not_in_organization
    A tag that does not exist in the organization422, code tag_not_found
    A tag of one dimension used in another, such as a channel tag in catalog_tags422, code tag_dimension_mismatch
    A surface ID that exists in none of the targeted catalogs422, code surface_not_in_catalog_scope
    A targeted catalog hosting none of the targeted surface IDs422, code catalog_without_targeted_surface
    A channel whose catalog is not among the targeted catalogs422, code channel_outside_catalog_scope
    An intent no targeted catalog serves422, code catalog_without_targeted_intent
    A pin whose record_match has no conditions422
    decay_window on mode: "hard", or ≤ 0422
    A condition tree deeper than 10 levels or over 100 conditions422
    An operator the field does not accept in some targeted catalog422
    A trigger.request:* field that is not one of the four422
    starts_at not strictly before ends_at422
    timezone with neither starts_at nor ends_at422

    Targeting problems come back in exception_details.problems, each with a stable code, its dimension and the offending values — so an authoring UI can point at the field that is wrong rather than showing one message.

    PATCH is partial: a field the body omits is left unchanged.

    Terminal window
    curl -X PATCH 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/lbm_p7q2zx' \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{"status": "paused"}'

    One exception: a rules list replaces the whole rule set. To change one rule, send them all. Read the campaign, edit the rules array, send it back. A partial rules list is not a partial update — it is a smaller rule set.

    Echo each rule’s id back. A rule sent with the id from a previous response is updated in place and keeps that ID; a rule sent without one is created fresh, and a rule missing from the list is dropped. The ID is what reporting aggregates a rule’s performance on, so recreating a rule you meant to edit splits its history in two.

    organization_id is immutable. An archived campaign rejects every update with 409, including a status change, and so does a name another live campaign already uses.

    DELETE /platform/v1/campaigns/{campaign_id} sets the status to archived rather than erasing the record, so what ran stays auditable. It responds 204.

    There is no un-archive. Use paused for anything you might want back. Archiving releases the campaign’s name, so a new campaign may reuse it.

    Terminal window
    curl -G 'https://api.eu1.luigisbox.ai/platform/v1/campaigns' \
    --data-urlencode 'organization_id=lbo_r2vn8c' \
    --data-urlencode 'status=active' \
    --data-urlencode 'intent=search' \
    --data-urlencode 'query=spring' \
    --data-urlencode 'sort=starts_at:desc' \
    -H 'Authorization: Bearer <token>'
    FilterMatches
    organization_idOwning organization
    statusLifecycle state
    queryCase-insensitive substring of the name
    effect_typeCampaigns containing a rule with this effect. Repeatable.
    intentIntents the campaign can actually serve right now. Repeatable.

    Filters combine AND across facets and OR within a repeatable one.

    intent matches against the intents of the surfaces currently in the campaign’s scope, not the stored intents array. A catalog-wide campaign matches every intent its catalogs host, and a campaign whose targeted surfaces were deleted matches none — the same answer effective scope gives.

    sort takes <field>:<direction>, with the field one of name, status, rules_count, starts_at or updated_at, and the direction asc or desc.

    The response is the standard paginated envelope plus facet_counts.status, a breakdown by status that ignores the status filter — so a UI can show “12 active, 3 paused” alongside a filtered list.

    Rules per campaign1–50
    Campaign and rule name1–256 characters
    Condition tree depth10
    Conditions per tree100