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.

    Discovery overview

    View source

    Discovery is the read side of Luigi’s Box AI: you ask a configured surface a question and it answers with a ranked list of catalog objects. Four endpoints cover the whole surface area.

    EndpointAsk it forThe request supplies
    POST /discovery/v1/searchResults for a queryA search query
    POST /discovery/v1/collectionsA listing pageA scope, such as a category
    POST /discovery/v1/recommenderRecommendationsAnchor objects, or nothing
    POST /discovery/v1/facetsFilters for a result setThe guid of a previous result set

    Two more endpoints support the first four: object lookup fetches objects by reference without ranking them, and variant resolution expands a product into its group.

    Every discovery endpoint is a POST that carries its inputs as a JSON body. Filters and sort are structured JSON objects.

    Terminal window
    curl -X POST 'https://api.eu1.luigisbox.ai/discovery/v1/search?channel_id=lbn_4hj9tv&surface_id=lbs_search_main' \
    -H 'Authorization: Bearer <token>' \
    -H 'X-Lbx-Visitor-Id: 8f14e45f-ea0f-4b5c-9a1d-2b3c4d5e6f70' \
    -H 'Content-Type: application/json' \
    -d '{ "type": "product", "query": "running shoes", "size": 24 }'

    Three inputs say where the request is being served, and they are on nearly every call:

    InputWhereWhat it names
    channel_idQuery parameterThe serving destination — which storefront or market this is
    surface_idQuery parameterThe configured discovery unit answering the request
    typeBody fieldWhich kind of object you want back: product, category, brand, …

    channel_id and surface_id are query parameters; everything the surface is asked goes in the JSON body. channel_id names the resource the request is authorized on, so leaving it out is a 400 rather than a 422.

    The surface’s intent has to match the endpoint. lbs_search_main belongs to /search; sending it to /recommender is rejected. See Surface identifiers.

    type is required and singular. An autocomplete panel showing products, categories and suggested phrases issues three requests, one per type. Each type gets its own ranking and its own result count.

    FieldDefaultPurpose
    filtersnoneRestrict results — see Filters
    return_fieldssurface defaultChoose which fields come back — see Selecting fields
    size10How many results. Max 100.
    user_idanonymousThe signed-in shopper — see Personalization
    personalizetrueWhether to personalize this request

    An unrecognised body field is a 422.

    /search and /collections additionally take sort and cursor; see Sorting and pagination.

    HeaderRequiredPurpose
    Authorization: Bearer <token>YesA token for the …/discovery audience
    Content-Type: application/jsonYesEvery discovery endpoint takes a JSON body
    X-Lbx-Visitor-IdYes, except on /facetsIdentifies the browser
    User-Agent, RefererOptionalImprove traffic classification and reporting

    A request without X-Lbx-Visitor-Id is rejected, except on /facets, which re-reads a result set the visitor was already identified for. Generate the identifier once per browser and keep it stable. See Identifying the shopper.

    {
    "hits": [
    {
    "@id": "product/sku-1001",
    "@type": "product",
    "@title": "Blue Cotton T-Shirt",
    "price": 29.9,
    "url": "https://example.com/products/blue-cotton-t-shirt"
    }
    ],
    "total": 412,
    "total_approx": 412,
    "guid": "3ab549eb64a20a0c",
    "next_page_cursor": "eyJvIjoyNCwic…",
    "pagination_status": "more"
    }

    A hit is a flat JSON object. @id and @type are always present; everything else is the catalog fields the surface returns, under the names you indexed them with. There is no attributes wrapper and no per-field envelope:

    hits.map((hit) => ({ id: hit['@id'], title: hit['@title'], price: hit.price }));

    Which fields appear depends on the surface’s configuration, your return_fields, and your credentials — a browser token never sees private: fields. See Selecting fields.

    FieldMeaning
    totalThe exact count. null when the result set is too large to count exactly.
    total_approxAlways present. Use it when total is null.

    For a result count in your UI, read total ?? total_approx. If the surface collapses variants, the count is of variant groups, not individual products — five sizes of one shirt count once. Calling /facets on the result set upgrades total to an exact figure.

    Every ranked response carries a guid identifying that result set. Pass it to /facets to compute filters over the same results, and to /variants when expanding groups from that page. It is opaque — do not parse it.

    1. POST /search with the query → hits, total, guid.
    2. POST /facets with that guid → the filter sidebar.
    3. Shopper picks a filter → POST /search again with filters, producing a new guid.
    4. Shopper scrolls → POST /search again with cursor from next_page_cursor.
    5. Report what was shown and clicked with analytics events.

    A surface learns from the interactions you report; without them its ranking does not improve.