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.
A catalog object is the canonical record Luigi’s Box AI indexes after mapping a source feed or API request. Source field names and catalog field names are separate concerns.
Registered fields
Section titled “Registered fields”Fields beginning with @ come from a closed registry. Customers cannot define arbitrary @ fields, and an unrecognized @ field is rejected during ingestion.
The currently supported registered fields are:
| Field | Authority | Requirement | Purpose |
|---|---|---|---|
@id | Client | Required | Stable typed identifier, such as product/123. |
@type | Client | Required | Object type. A product uses exactly product. |
@title | Client | Required | Human-readable object name. Send it on every object you upsert, or let the catalog’s mapping produce it from one of your attributes; an object whose title is missing, null or blank after mapping is rejected. |
@category | Client | Optional | Product-to-category relationship using typed category IDs, such as category/cat-t-shirts, or category aliases. |
@brand | Client | Optional | Product-to-brand relationship using a typed brand ID. |
@parent | Client | Optional | The object’s immediate parent, as a typed ID or a category alias. Builds category trees — see Hierarchies. |
@aliases | Client | Optional | Further names a category can be referenced by, such as one built from its name path. Categories only — see Category aliases. |
@group_id | Client | Optional | Non-empty string shared by all products in one variant group; omitted for standalone products. |
@group_primary | Client | Optional | Boolean set to true for the primary product in a group; omitted together with @group_id for standalone products. |
@updated_at | System | System-managed | Last update timestamp maintained by Luigi’s Box AI. Do not send this field in client feeds or API requests. |
Registered fields are read only under their @ name. Unless your catalog’s mapping renames
it, an attribute named group_id, title or type is stored as your own attribute; it does
not set @group_id, @title or @type.
A complete product object can combine registered @ fields with ordinary public attributes:
{ "@id": "product/sku-1001", "@type": "product", "@title": "Blue Cotton T-Shirt", "@category": ["category/cat-t-shirts"], "@brand": "brand/northwear", "@group_id": "northwear-t-shirt-1001", "@group_primary": true, "url": "https://example.com/products/blue-cotton-t-shirt", "image_url": "https://cdn.example.com/products/sku-1001.jpg", "price": 29.90, "list_price": 39.90, "availability": 1}Canonical object examples are shown as JSON because this is the post-mapping representation. XML, JSON, JSON Lines, and CSV are source feed formats; see the feed type references for examples.
Types and IDs
Section titled “Types and IDs”Use the configured type exactly. A product object uses product.
The source value used as identity must include the object type prefix:
product/123For a product, the prefix must be product/ and must match the configured @type:
{ "@id": "product/123"}For example, item/123 is rejected when the object type is product; it is not silently rewritten.
Relationships
Section titled “Relationships”Two registered fields link one object to another. Both hold the target’s typed identity — never a copy of the target itself.
@category connects a product to categories by typed category ID:
{ "@id": "product/123", "@type": "product", "@title": "Blue Cotton T-Shirt", "@category": [ "category/shirts", "category/summer" ]}The category objects use the corresponding IDs:
{ "@id": "category/shirts", "@type": "category", "@title": "Shirts"}The target category does not need to exist when the product is ingested. Product and category feeds can therefore be processed independently.
@brand connects a product to a brand by typed brand ID:
{ "@id": "product/123", "@type": "product", "@title": "Blue Cotton T-Shirt", "@brand": "brand/northwear"}The brand object uses the corresponding ID:
{ "@id": "brand/northwear", "@type": "brand", "@title": "Northwear"}The target brand does not need to exist when the product is ingested. Product and brand feeds can therefore be processed independently.
Hierarchies
Section titled “Hierarchies”@parent expresses a category tree: each category names its immediate parent, and
Luigi’s Box assembles the tree from those edges. Do not send a whole path on a category.
{ "@id": "category/clothing", "@type": "category", "@title": "Clothing"}A child names its parent:
{ "@id": "category/shirts", "@type": "category", "@title": "Shirts", "@parent": ["category/clothing"]}And a grandchild names only its own parent, not the whole chain:
{ "@id": "category/dress-shirts", "@type": "category", "@title": "Dress shirts", "@parent": ["category/shirts"]}The rules:
@parentis an array of typed identities — the object’s immediate parents. A category that sits at several places in the tree names each of them.- A root category omits it, or sends an empty array.
- The parent need not exist yet. Like
@categoryand@brand, the link is stored as sent; ordering between feeds does not matter. - Name immediate parents only. Naming a grandparent as well has no defined resolution.
- It is not restricted to categories — any object type may carry it.
Category aliases
Section titled “Category aliases”A category can declare further names in @aliases, and @parent and @category can reference
it by any of them. A feed that names categories by path links them this way, without knowing
their IDs — see Category name paths.
{ "@id": "category/shirts", "@type": "category", "@title": "Shirts", "@aliases": ["category/~clothing/shirts"]}A product naming the alias sits in that category:
{ "@id": "product/sku-1001", "@type": "product", "@title": "Blue Cotton T-Shirt", "@category": ["category/~clothing/shirts"]}The rules:
@aliasesis an array ofcategory/<value>references, accepted on categories only.- An
@idtakes precedence. A reference names the category whose@idit is. Only when no category has ever been sent with that@iddoes it name the category declaring it as an alias. Aliases built from name paths start with~aftercategory/, so they never equal an ID that does not. - An alias two categories declare names neither. References to it link nothing until one of them drops it.
- Deleting a category releases its aliases.
- References are kept as sent.
@parentand@categoryhold the alias you sent; the IDs it resolves to are inlbx:category_idsandlbx:category_hierarchy. A reference that names no category yet is listed there as itself, and links once such a category arrives. - Aliases are for linking only. Search does not match them.
Variant groups
Section titled “Variant groups”Every variant is a complete product with its own stable @id. Products in the same
variant group carry the same string in @group_id; the value is an opaque group
identifier and does not need to be a UUID or the @id of another product.
Use @group_primary: true for the primary product in a group. Both fields are
optional. When a group has no primary product, set it to false on all members or
omit it. A standalone product omits
both fields. Catalog ingestion preserves the optional @ fields exactly as sent; it
does not add grouping defaults to the client-owned slots. In a partial update, omitting
a field leaves its stored value unchanged; send false explicitly to demote an existing
primary product. A partial update can change @group_primary without restating an
unchanged group ID. Send @group_id: null to remove a product from its current group;
its derived group ID then falls back to its own identity and its derived primary flag
becomes false.
Catalog ingestion validates each product independently and does not enforce uniqueness
across a group. Clients are responsible for sending at most one primary product per group.
Luigi’s Box derives two search attributes for every product: lbx:group_id copies
@group_id or falls back to the untyped identity part of the product’s own @id
(product/prod_123 becomes prod_123), and lbx:group_primary copies
@group_primary or falls back to false. The fields you supplied are stored unchanged.
Every group member is stored independently as a complete product. Catalog ingestion does not copy attributes from a primary product onto its variants.
{ "@id": "product/t-shirt-blue-m", "@type": "product", "@title": "Blue Cotton T-Shirt, M", "@group_id": "northwear-t-shirt-1001", "@group_primary": true, "color": "blue", "size": "M"}@group_id must be a non-empty string and @group_primary must be a JSON
boolean, not the strings "true" or "false".
Suitability attributes
Section titled “Suitability attributes”Catalogs with suitability extraction enabled can expose lbx:suitable_for and
lbx:not_suitable_for as arrays of audience tags, read from the product descriptions.
Supported tags are pregnancy and breastfeeding.
lbx:not_suitable_for records an unconditional statement that the product is not
suitable for, not intended for or not recommended for an audience, including a
contraindication list that names pregnancy or breastfeeding. A restriction limited by a
condition, such as a trimester, a dose or prior medical consultation, adds no tag.
lbx:suitable_for records an unconditional statement that the product is suitable for an
audience. Suitability for women planning a pregnancy does not count as suitability during
pregnancy, and conditional wording, such as use after medical consultation, does not
establish suitability.
An empty array means no recognized statement, not that the product is suitable for that audience. A recognized exclusion takes precedence over a positive statement for the same audience. Restriction wording that is not recognized as an exclusion still withholds the positive tag. Description changes can replace or clear the tags.
These are Luigi’s Box-owned attributes; do not send them through the Content API. The tags alone do not exclude products from results. Exclusion requires a matching filter or business rule.
Field namespaces
Section titled “Field namespaces”An object is a flat set of fields. A field’s name says who wrote it and who may read it, through a namespace: a prefix ending in a colon, in front of the field name.
color no prefixprivate:margin the private: namespacelbx:color the lbx: namespaceThere are no per-field visibility settings. The prefix decides, so a field’s name says what it is in a feed, in an API payload and in a discovery response.
| Form | Example | Written by | Who can read it |
|---|---|---|---|
| No prefix | color, price | You | Everyone — including the browser |
@ slot | @id, @title, @category | You (a closed set) | Everyone |
private: | private:margin | You | Server-side integrations only |
lbx: | lbx:color, lbx:group_id | Luigi’s Box | Everyone |
Three rules hold across all of them:
- One prefix, never two. Namespaces do not stack: there is no
private:lbx:something. @is not a namespace. It marks a registered structural slot from a closed set, not a prefix you can extend.- An unrecognized prefix is rejected at ingest. A colon in a field name that is not a
registered namespace fails the object rather than being stored as a literal name — so a
field called
size:euis an error, not an attribute.
What you can write
Section titled “What you can write”You own the fields with no prefix, the registered @ slots, and everything under
private:. Luigi’s Box owns the rest, and sending a field in one of its namespaces is
rejected — including lbx: fields derived from what you sent, like
lbx:group_id.
How to structure your data
Section titled “How to structure your data”Send what you already have, under the names you already use.
Beyond the three required slots — @id, @type, @title — there is no prescribed field
list. Your attributes keep your names, in your language:
{ "@id": "product/sku-1001", "@type": "product", "@title": "Blue Cotton Dress Shirt", "@category": ["category/dress-shirts"], "@brand": "brand/northwear", "url": "https://example.com/p/blue-cotton-dress-shirt", "image_url": "https://cdn.example.com/sku-1001.jpg", "price": 29.9, "list_price": 39.9, "availability": 1, "color": "blue", "size": "M", "material": "cotton", "sleeve_length": "long", "collar": "spread", "care": ["machine wash 30", "iron medium"], "private:margin": 8.4}color, sleeve_length and collar are not predefined fields. They are attributes this
shop has, and they are searchable because they were sent.
What to include
Section titled “What to include”Include an attribute if you can answer yes to any of these:
| Ask | Example |
|---|---|
| Would a shopper filter or facet on it? | color, size, material, availability |
| Would a shopper sort by it? | price, rating, created_at |
| Does the storefront display it? | title, image_url, url, list_price |
| Does it describe what the thing is? | description, brand, params, specifications |
Ranking works from the text and attributes you provide. Descriptions, specifications and category placement are what let a search for “long sleeve cotton office shirt” find the right product.
Leave out presentation markup, internal keys nobody will query, and anything personal.
What matters more than completeness
Section titled “What matters more than completeness”- Consistent names. The same attribute keeps the same name across every record and every
export.
colorin one product andcolourin another are two attributes. - Consistent types. The first value a field receives fixes its type for the catalog, and
a later conflicting type is rejected rather than coerced.
29.9is a number;"29.90"is text, and a text price can never be range-filtered or shown as a price slider. See Catalog metadata. - Omit rather than fake. A missing value should be absent, not
"",0or"N/A"— those become real values that show up in facets and match filters.
Nesting
Section titled “Nesting”Objects are allowed, and nested fields are addressed by dot-path in filters, facets and field projections:
{ "params": { "material": "cotton", "fit": "regular" }}That field is params.material in filters, facets and field projections. Prefer flat
attributes unless your source data is nested.
Keeping data out of the browser
Section titled “Keeping data out of the browser”Fields with no namespace are public: they are eligible to appear in browser-facing discovery responses, depending on the surface configuration. Because browser tokens and their requests are visible in developer tools, a public field must not contain anything a storefront visitor should not be able to read.
Use the private: namespace for anything that must reach only backend integrations
authenticated with a machine-to-machine token — cost, margin, supplier codes, internal
notes:
{ "color": "blue", "price": 29.9, "private:margin": 8.4}Private fields are never returned to a request authenticated with a browser token. Keep machine-to-machine credentials secret; leaking those credentials can expose private fields.
Which fields a given request actually receives also depends on what it asks for — see Selecting fields.
See also
Section titled “See also”- Object types and relationships — what a catalog can hold
- Selecting fields — which fields a discovery request receives
- Feeds overview
- Product feeds
- Category feeds
- Feed troubleshooting
Was this page helpful?
Thanks.