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.
Facets are the filter sidebar: the brands present in these results, the price range they
span, the category tree they sit in. POST /discovery/v1/facets computes them for a result
set you already fetched, rather than re-running the query.
The request takes a guid rather than a query, so facets and results always describe the
same result set.
Request
Section titled “Request”curl -X POST 'https://api.eu1.luigisbox.ai/discovery/v1/facets?channel_id=lbn_4hj9tv' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -d '{ "guid": "3ab549eb64a20a0c", "facets": [ { "field": "brand", "size": 20 }, { "field": "color" }, { "field": "price" } ] }'| Query parameter | Required | Notes |
|---|---|---|
channel_id | Yes | Serving destination |
| Body field | Required | Notes |
|---|---|---|
guid | Yes | From a previous /search or /collections response |
facets | Yes | 1–100 entries of { "field": …, "size": … } |
hierarchy_anchors | No | Which branches of a hierarchy to expand. Up to 50. |
hierarchy_depths | No | How many child levels to expand, per field. Up to 50. |
There is no surface_id: the guid already identifies the result set, and with it the
surface that produced it. A token authorized for either search or collections may call this
endpoint.
Capping values
Section titled “Capping values”size limits how many values a field returns:
{ "field": "brand", "size": 20 }Omit it for the surface’s default. Cap long-tail fields such as brand.
Response
Section titled “Response”Facets come back keyed by field name, each tagged with its type:
{ "guid": "3ab549eb64a20a0c", "facets": { "brand": { "type": "terms", "field": "brand", "values": ["northwear", "atlas", "kestrel"] }, "price": { "type": "range", "field": "price", "min": 12.5, "max": 249.0 }, "lbx:category_hierarchy": { "type": "hierarchy", "field": "lbx:category_hierarchy", "values": [ { "value": "category/clothing", "title": "Clothing", "has_children": true, "children": [] } ] } }}Switch on type to pick a control:
type | Fields | Render as |
|---|---|---|
terms | values — the distinct values present | Checkboxes or a multi-select |
range | min, max — the bounds in these results | A slider or two number inputs |
hierarchy | values — a tree of nodes | An expandable tree |
function renderFacet(name, facet) { switch (facet.type) { case 'terms': return checkboxes(name, facet.values); case 'range': return slider(name, facet.min, facet.max); case 'hierarchy': return tree(name, facet.values); }}Hierarchy facets
Section titled “Hierarchy facets”Which fields a surface can facet hierarchically is part of its configuration — ask your Luigi’s Box contact to set one up.
A hierarchy node’s value is its fully expanded path, with segments joined by >:
category/clothing > category/shirtstitle is the node’s own display name, and has_children tells you whether it can be
expanded. By default only the roots come back, with children empty.
Expanding
Section titled “Expanding”Two fields open the tree. hierarchy_depths expands from the roots downwards:
{ "hierarchy_depths": [ { "field": "lbx:category_hierarchy", "depth": 2 } ]}depth must be between 1 and 5, and each field may appear at most once. Anything else is
a 422.
hierarchy_anchors opens one specific branch, naming the full node path:
{ "hierarchy_anchors": [ { "field": "lbx:category_hierarchy", "path": "category/clothing > category/shirts" } ]}List several entries to open several branches, across several fields. The path is a JSON
string, so the > separator needs no encoding.
const body = { guid, facets: [{ field: 'lbx:category_hierarchy' }], hierarchy_anchors: openBranches.map((path) => ({ field: 'lbx:category_hierarchy', path })),};Hold the set of open branches in your UI state and send it whole on each request, so the tree renders in the state the shopper left it in.
The loop
Section titled “The loop”Facets and results move together. Each new filter combination is a new result set:
POST /search→hits,guid.POST /facetswith thatguid→ the sidebar.- Shopper checks northwear →
POST /searchagain with"filters": { "brand": { "in": ["northwear"] } }→ newhits, newguid. POST /facetswith the newguid→ the sidebar, now narrowed to what is still reachable.
Never reuse a guid after changing filters. The facets you get back would describe the
previous result set, and the sidebar would offer values that no longer match anything.
Calling /facets also computes an exact count, so a total that came back null from the
search becomes a real number.
Choosing what to facet
Section titled “Choosing what to facet”- Facet on what a shopper filters by, not on everything you index. Brand, price, colour, size, availability.
- Cap the long tail with
size, for example{ "field": "brand", "size": 20 }. - Numeric fields need to be numeric. A
priceindexed as text facets as a terms list of strings, not a range. Check with catalog metadata and fix it in the mapping.
See also
Section titled “See also”- Filters — turning selections into a filter
- Search · Collections
- Catalog metadata — which fields are facetable, and their types
Was this page helpful?
Thanks.