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.
const url = 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/trigger-schema?page=1&size=20';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"catalog_ids":["example"],"catalog_tags":["example"],"channel_ids":["example"],"channel_tags":["example"],"intents":["search"],"organization_id":"example","surface_ids":["example"],"surface_tags":["example"]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/trigger-schema?page=1&size=20' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "catalog_ids": [ "example" ], "catalog_tags": [ "example" ], "channel_ids": [ "example" ], "channel_tags": [ "example" ], "intents": [ "search" ], "organization_id": "example", "surface_ids": [ "example" ], "surface_tags": [ "example" ] }'Project the trigger fields authorable for a targeting set without persisting anything. Resolves the same effective scope a write would (422 on an invalid targeting set, problems in exception_details.targeting_problems), then returns, per unprefixed trigger field, the operators a write accepts for it across that whole scope. A field numeric in one scoped catalog but string (or absent) in another is reported with the string operator set, since that is what a write accepts for it. Each item carries its source (catalog attribute vs the request). The three request-owned fields (request:query, request:type, request:anchor_id) are always included; add the trigger. prefix to a name to build a leaf. Items are sorted by name; query narrows to a case-insensitive substring on the name, and the result is paginated. Authorized against the organization_id in the body: credentials granted on that organization are enough, while a grant on one of its catalogs is not.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Query Parameters
Section titled “Query Parameters ”Request Body required
Section titled “Request Body required ”A campaign’s targeting on its own — no name, no match, no rules, no persistence.
Ids are canonicalized, deduped and format-checked exactly as they are on a real
write, and every targeting violation is reported uniformly as a problems entry.
object
Catalogs this campaign narrows to, by id. catalog_tags narrows the same axis by contributing its members, so the scope is the union of the two. An axis nothing narrows is an axis the campaign does not restrict, so empty means every one of them rather than none. A catalog outside the organization is rejected with catalog_not_in_organization.
Catalog tags whose members join catalog_ids on the catalog axis. Resolved at read time, so editing a tag reflows into every campaign referencing it. A tag that does not exist in the organization is rejected with tag_not_found, and one carrying another dimension with tag_dimension_mismatch.
Channels this campaign narrows to, by id, jointly with channel_tags. An axis nothing narrows is an axis the campaign does not restrict, so empty means every one of them rather than none. Narrowing by channel requires a catalog scope (catalog_scope_required), the channel must be in the organization (channel_not_in_organization) and owned by a scoped catalog (channel_outside_catalog_scope).
Channel tags whose members join channel_ids on the channel axis, under the same resolution and the same tag_not_found / tag_dimension_mismatch rejections.
Surface Intents this campaign targets — the kind of discovery it applies to. Empty means every Intent, not none. Narrowing by Intent is mutually exclusive with narrowing by surface_ids / surface_tags: an Intent targets whole families of surfaces, so combining the two is rejected with intents_conflict. A targeted Intent no scoped catalog hosts a surface for is rejected with catalog_without_targeted_intent — it could never trigger.
Surfaces this campaign narrows to, by id, jointly with surface_tags. An axis nothing narrows is an axis the campaign does not restrict, so empty means every one of them rather than none. Mutually exclusive with intents (intents_conflict), requires a catalog scope (catalog_scope_required), and each surface must sit under a scoped catalog (surface_not_in_catalog_scope).
Surface tags whose members join surface_ids on the surface axis, under the same resolution and rejections. A member whose own catalog is outside the catalog scope is tolerated rather than rejected, which keeps a surface tag reusable across catalogs.
Responses
Section titled “ Responses ”Successful Response
object
The items on this page.
One trigger field authorable across a campaign’s scope.
name is the unprefixed field name (add trigger. to build a match
leaf). source is where the value comes from (catalog attribute vs the
request), and operators is the set a write accepts for the field anywhere
in the scope. values lists what the scope carries today and value_domain
says whether that list is a suggestion or there is none.
object
Unprefixed trigger field name. Add the trigger. prefix to build a match leaf — brand here is authored as trigger.brand.
Every operator a write accepts for this field anywhere in the scope. Range and comparison operators are offered on a numeric field; a keyword field offers membership operators only.
Where the value comes from when a campaign is matched: a catalog attribute of the matched anchor/collection document, or the request itself. Derived from the name — the reserved request: prefix cannot be occupied by catalog data.
suggested: values lists what the scope carries today, and a value outside it is still accepted. open: free input, with values empty.
What the scope carries today, sorted; other values are accepted. Populated only for request:type, with the object types the scope’s catalogs hold; every other field, including a catalog attribute, reports [] — for a catalog attribute the values are catalog data, not schema: read them from GET /catalog/v1/{catalog_id}/types/{object_type}/attribute-values?attribute_name=.... An empty list never means the field accepts nothing.
1-based page number.
Number of items per page.
Total number of items across all pages.
Example
{ "items": [ { "operators": [ "eq" ], "source": "catalog", "value_domain": "suggested" } ]}Missing or invalid credentials
Canonical error body — every non-2xx response uses this shape.
object
Machine-readable error context; empty object when there is nothing to add.
object
Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. filter_unknown_field or cursor_mismatch. Safe to branch on; reason is not. Absent when the status code says all there is to say.
Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value.
One field-level reason a request was rejected.
object
Path to the offending value from the request root, e.g. ["body", "filters", 0, "operator"].
What is wrong with the value at loc.
Stable machine code for the problem, e.g. missing or string_too_short.
Human-readable, actionable error message.
Example generated
{ "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example"}Authenticated but not permitted
Canonical error body — every non-2xx response uses this shape.
object
Machine-readable error context; empty object when there is nothing to add.
object
Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. filter_unknown_field or cursor_mismatch. Safe to branch on; reason is not. Absent when the status code says all there is to say.
Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value.
One field-level reason a request was rejected.
object
Path to the offending value from the request root, e.g. ["body", "filters", 0, "operator"].
What is wrong with the value at loc.
Stable machine code for the problem, e.g. missing or string_too_short.
Human-readable, actionable error message.
Example generated
{ "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example"}Request validation failed. A targeting rejection lists every problem at once in exception_details.targeting_problems.
The canonical error body of a rejected targeting set, with problems typed.
reason and request_id are the canonical fields; exception_details carries
the typed problems list.
object
The exception_details of a rejected targeting set: every problem found at once.
object
Every targeting violation found in one pass; never truncated. Named for what it carries, so it sits beside filter_problems on the same 422 without a client having to work out which of the two it was given. A 422 carries one list or the other, never both: the first check to refuse ends the request.
One targeting violation: a stable machine code plus the offending values.
object
Stable machine code of the violated rule.
The targeting axis the violation is about.
Human-readable explanation of the violation.
The offending values on that axis.
Human-readable, actionable error message.
Canonical error body — every non-2xx response uses this shape.
object
Machine-readable error context; empty object when there is nothing to add.
object
Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. filter_unknown_field or cursor_mismatch. Safe to branch on; reason is not. Absent when the status code says all there is to say.
Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value.
One field-level reason a request was rejected.
object
Path to the offending value from the request root, e.g. ["body", "filters", 0, "operator"].
What is wrong with the value at loc.
Stable machine code for the problem, e.g. missing or string_too_short.
Human-readable, actionable error message.
Example
{ "exception_details": { "targeting_problems": [ { "code": "catalog_scope_required", "dimension": "catalog" } ] }}Temporarily unable to serve the request; retry it unchanged after the Retry-After delay.
Canonical error body — every non-2xx response uses this shape.
object
Machine-readable error context; empty object when there is nothing to add.
object
Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. filter_unknown_field or cursor_mismatch. Safe to branch on; reason is not. Absent when the status code says all there is to say.
Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value.
One field-level reason a request was rejected.
object
Path to the offending value from the request root, e.g. ["body", "filters", 0, "operator"].
What is wrong with the value at loc.
Stable machine code for the problem, e.g. missing or string_too_short.
Human-readable, actionable error message.
Example generated
{ "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example"}Was this page helpful?
Thanks.