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/discovery/v1/search?channel_id=example&surface_id=example';const options = { method: 'POST', headers: { 'x-lbx-visitor-id': 'example', 'user-agent': '', referer: '', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"cursor":"example","debug":false,"filters":{"$and":[{"brand":{"in":["nike","adidas"]}},{"lbx:price":{"gte":50,"lte":200}},{"$or":[{"condition":{"eq":"new"}},{"warranty_months":{"gte":12}}]},{"$not":{"material":{"eq":"leather"}}},{"image":{"exists":true}}]},"personalize":true,"query":"","return_fields":["example"],"size":10,"sort":{"direction":"asc","field":"example"},"touchpoint":"autocomplete","type":"example","user_id":""}'};
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/discovery/v1/search?channel_id=example&surface_id=example' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --header 'referer: ' \ --header 'user-agent: ' \ --header 'x-lbx-visitor-id: example' \ --data '{ "cursor": "example", "debug": false, "filters": { "$and": [ { "brand": { "in": [ "nike", "adidas" ] } }, { "lbx:price": { "gte": 50, "lte": 200 } }, { "$or": [ { "condition": { "eq": "new" } }, { "warranty_months": { "gte": 12 } } ] }, { "$not": { "material": { "eq": "leather" } } }, { "image": { "exists": true } } ] }, "personalize": true, "query": "", "return_fields": [ "example" ], "size": 10, "sort": { "direction": "asc", "field": "example" }, "touchpoint": "autocomplete", "type": "example", "user_id": "" }'Run full-text search from a structured JSON request body.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Query Parameters
Section titled “Query Parameters ”Channel identifier (serving destination).
Channel identifier (serving destination).
Surface identifier, lbs_
Surface identifier, lbs_
Header Parameters
Section titled “Header Parameters ”Stable per-browser visitor identifier, generated client-side and persisted (e.g. a UUID in localStorage or a first-party cookie). Send it whether or not personalization is on: it is what links a request to a visitor’s history and to the analytics events reporting it. Keep it stable across page loads, sessions and token refreshes; never generate it server-side, or every visitor collapses into one.
Stable per-browser visitor identifier, generated client-side and persisted (e.g. a UUID in localStorage or a first-party cookie). Send it whether or not personalization is on: it is what links a request to a visitor’s history and to the analytics events reporting it. Keep it stable across page loads, sessions and token refreshes; never generate it server-side, or every visitor collapses into one.
Originating client’s User-Agent. Optional; used to derive the platform (ios, android, web) the result set is attributed to. A backend caller should forward the end user’s value rather than its own.
Originating client’s User-Agent. Optional; used to derive the platform (ios, android, web) the result set is attributed to. A backend caller should forward the end user’s value rather than its own.
Originating page URL. Optional; lets the result set be attributed to the page it was served on.
Originating page URL. Optional; lets the result set be attributed to the page it was served on.
Request Body required
Section titled “Request Body required ”JSON body accepted by the search endpoint.
object
Include a diagnostic debug payload in the response.
A group node: exactly one of $and, $or, $not.
object
Nodes that must all match.
A condition node: field names mapped to operator objects; sibling fields combine as AND.
object
Operators applied to one field. Several operators in one object combine as AND.
object
A condition node: field names mapped to operator objects; sibling fields combine as AND.
object
Operators applied to one field. Several operators in one object combine as AND.
object
Nodes of which at least one must match.
A condition node: field names mapped to operator objects; sibling fields combine as AND.
object
Operators applied to one field. Several operators in one object combine as AND.
object
A condition node: field names mapped to operator objects; sibling fields combine as AND.
object
Operators applied to one field. Several operators in one object combine as AND.
object
Personalize this request, and assert that the shopper has consented to behavioural personalization. Defaults to true: personalized, and recorded as consented. Send false for a shopper who has not consented; read it from your consent manager on every request. When true and no sort is given, the result set (and, where returned, its request_key and cursors) is bound to the shopper: user_id and the X-Lbx-Visitor-Id sent. Under an explicit sort personalization does not change the order, so the result set is shared.
Free-text search query.
Object fields to return; empty for the surface default, or [’*’] for all fields.
Number of objects to return per page.
Structured sort override supplied in a JSON request body.
object
Sort direction.
Attribute to sort by, for example ‘lbx:price’.
Where in the shopper’s journey search results appear.
Catalog object type to retrieve, for example ‘product’.
Signed-in shopper’s stable identifier, your own opaque key. Omit it or send it blank for anonymous traffic; the strings undefined, null, None, NaN and [object Object] (any case) also count as anonymous.
Responses
Section titled “ Responses ”Successful Response
Response of the search endpoint.
object
Deprecated alias of request_key carrying the same value. Read request_key instead: guid will be repurposed as the per-request attribution identifier once clients have migrated.
Ranked list of result hits — only objects this channel can serve.
A hit from /search, /collection or /recommender.
These endpoints rank, so a campaign or an ad platform can place a product on them — and
a placed hit may carry @disclosure. A hit from a direct lookup never does.
object
Present only on a hit whose placement is disclosed — one a disclosing campaign’s rules pinned or boosted, or one an ad platform sold. Absent otherwise; never null.
object
The person or company on whose behalf the placement is shown, e.g. Northwear Ltd, as the campaign wrote it. Present when the campaign names one.
Who paid for the placement. Present when the campaign names one.
The ad platform that sold the placement. Absent for a campaign-authored disclosure.
What decided that this shopper is shown the placement on this request: search_query for what they searched, collection for the collection they browse, anchor for the products a recommendation is shown for, or none, alone, when nothing about the shopper did. Codes, for you to word in the shopper’s language; more may be added, so handle one you do not know generically. Present on a campaign’s placement.
Impression and click URLs to call, under the ad platform’s own key names. Absent for a campaign-authored disclosure, and when the platform returned none.
object
How the placement is disclosed. sponsored marks a paid placement.
Unique identity of the hit.
Type of the hit (e.g. ‘product’).
Why this response does or does not carry a next_page_cursor: more — more pages exist and next_page_cursor is set; exhausted — this was the last page; unsupported — this result set cannot be paged and retrying will not produce a cursor; unavailable — pagination state could not be saved for this request (a transient failure) and re-issuing the search may succeed.
Key derived from the request parameters that decide which objects are returned. Two identical requests produce the same value. A request with personalize set to true and no explicit sort is also scoped to its shopper (user_id and X-Lbx-Visitor-Id), so two visitors sending it get different values; every other request keys the same for every shopper. Pass it to /facets and /variants to address the same result set; a cursor is only valid together with the request key it was issued for.
Approximate total for the whole result set, counting only the objects this channel can serve. Always present, so use it as the fallback when total is null. It may be an estimate, so prefer total wherever that is set.
Example
{ "hits": [ { "@disclosure": { "recipient_parameters": [ "none" ], "type": "sponsored" } } ], "pagination_status": "more"}Malformed request
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"}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"}Resource not found
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
The canonical error body of a Discovery 422, with error_code typed.
Every key of the envelope is required, as the served body always carries all three.
object
Machine-readable error context; empty object when there is nothing to add.
object
exception_details.error_code values for a rejected cursor.
Clients switch on these to drop the cursor and request page 1; the reason beside them is for humans and may be reworded.
exception_details.error_code values for a request that names the wrong surface.
exception_details.error_code values for a touchpoint the endpoint cannot record.
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": { "error_code": "filter_syntax" }}Rate limit exceeded
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"}Service temporarily unavailable
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.