---
title: Shopping Assistant API
description: Technical reference for the Shopping Assistant endpoint.
slug: shopping-assistant/api/assistant
docKind: endpoint
hub: shopping-assistant
tableOfContents: true
---

import ApiSection from "../../../../components/ApiSection.astro";
import ApiEndpoint from "../../../../components/ApiEndpoint.astro";
import ApiCodeTabs from "../../../../components/ApiCodeTabs.astro";
import { Aside, Tabs, TabItem } from "@astrojs/starlight/components";

<ApiSection>
  <div slot="code">
    <ApiEndpoint method="POST" url="https://live.luigisbox.com/v1/assistant" />
  </div>
## Overview

Use the Shopping Assistant API to power a question-and-answer product finder on your own site.

You define the assistant in the Luigi's Box app. Your integration then calls this endpoint to:

1. get the first question
2. send back the user's selected answers
3. receive the next question together with the currently matching products

In other words, this endpoint does not just return products. It returns the current state of the guided flow: the next question to show and the product list that matches the answers collected so far.

Luigi's Box Assistant can learn from user interactions when Luigi's Box analytics is integrated with your site. For click, add-to-cart, and conversion tracking, see [Shopping Assistant analytics](/shopping-assistant/guides/analytics/).

<Aside type="note">
  This endpoint is public and requires no authentication.
</Aside>

<Aside type="tip">
  For implementation planning, start with [Shopping Assistant implementation flow](/shopping-assistant/guides/implementation-flow/). For runnable code, use [Building a custom shopping assistant](/quickstart/shopping-assistant/building-custom-ui/). For measurement, see [Shopping Assistant analytics](/shopping-assistant/guides/analytics/).
</Aside>
</ApiSection>

<ApiSection>
## Request

Send a `POST` request to the endpoint. Query parameters identify the tracker and assistant. The JSON body carries the dialogue state and runtime overrides.

### Query parameters

| Parameter          | Type   | Required    | Description                                                                                                                                                                                         |
| :----------------- | :----- | :---------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tracker_id`       | string | ✓           | Your site identifier within Luigi's Box. You can find this identifier in every URL in [the Luigi's Box app](https://app.luigisbox.com) once you are logged in.                                      |
| `assistant_handle` | string | ✓           | The unique handle of the assistant to use.                                                                                                                                                          |
| `user_id`          | string | Recommended | The unique identifier of the end-user. If it matches the user ID collected in analytics, it can connect assistant requests to the same user or session and support personalization where available. |

### Request body parameters

<table>
  <thead>
    <tr>
      <th>Parameter</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <code>assistant_version</code>
      </td>
      <td>integer</td>
      <td>
        The version of the assistant to use. This parameter is required. Use{" "}
        <code>-1</code> for the latest published version, or pass a specific
        published version when you need deterministic behavior.
      </td>
    </tr>
    <tr>
      <td>
        <code>steps</code>
      </td>
      <td>array[object]</td>
      <td>
        An array of previous interactions (questions and answers) in the
        dialogue. Each step contains the question handle and one or more
        selected option handles. Send an empty array or omit this field for the
        first request.
      </td>
    </tr>
    <tr>
      <td>
        <code>next_question_handle</code>
      </td>
      <td>string</td>
      <td>
        The handle of the next question to be presented to the user. If an
        option returns <code>next_question_handle</code>, pass it in your next
        request to continue that branch of the flow.
      </td>
    </tr>
    <tr>
      <td>
        <code>size</code>
      </td>
      <td>integer</td>
      <td>
        The number of product results to return. Default: <code>10</code>.
        Maximum: <code>200</code>.
      </td>
    </tr>
    <tr>
      <td>
        <code>price_field</code>
      </td>
      <td>string</td>
      <td>
        The product field used for calculating price ranges. Defaults to{" "}
        <code>price_amount</code>. If you want to use a different numeric field,
        specify it here. If the field does not exist or is not numeric, the
        request returns <code>400</code>.
      </td>
    </tr>
    <tr>
      <td>
        <code>hit_fields</code>
      </td>
      <td>string</td>
      <td>
        A comma-separated list of hit attributes to include in the response, for
        example <code>title,sku,image_link</code>. This controls fields inside
        each hit's <code>attributes</code> object. Top-level hit fields such as{" "}
        <code>url</code> are always returned.
      </td>
    </tr>
    <tr>
      <td>
        <code>field_overrides</code>
      </td>
      <td>object</td>
      <td>
        An object that replaces fields used by the assistant's filters and
        facets with other indexed fields at request time. A typical use case is
        runtime pricing, for example replacing <code>price_amount</code> with{" "}
        <code>price_amount_7</code> for a logged-in user. Source and target
        fields must exist and use compatible types.
      </td>
    </tr>
    <tr>
      <td>
        <code>sort</code>
      </td>
      <td>string</td>
      <td>
        The sorting criteria for the product results, for example{" "}
        <code>price:asc</code> or <code>price:desc,title:asc</code>. If not
        provided, the default sorting is applied.
      </td>
    </tr>
    <tr>
      <td>
        <code>context</code>
      </td>
      <td>object</td>
      <td>
        An object that overrides fields used by search. The fields available for
        overriding are <code>availability_field</code>,{" "}
        <code>availability_rank_field</code>, <code>freshness_field</code>,{" "}
        <code>boost_field</code>, <code>geo_location_field</code>,{" "}
        <code>margin_field</code>, <code>absolute_margin_field</code>, and{" "}
        <code>discount_field</code>.
      </td>
    </tr>
    <tr>
      <td>
        <code>f</code>
      </td>
      <td>array[string]</td>
      <td>
        An array of <code>OR</code> filters to apply to the product results (for
        example <code>category:electronics</code> or <code>price:1|5</code>).
      </td>
    </tr>
    <tr>
      <td>
        <code>f_must</code>
      </td>
      <td>array[string]</td>
      <td>
        An array of <code>AND</code> filters that must match the product results
        (for example <code>category:electronics</code> or <code>price:1|5</code>
        ).
      </td>
    </tr>
    <tr>
      <td>
        <code>neg_f</code>
      </td>
      <td>array[string]</td>
      <td>
        An array of <code>OR</code> filters to exclude product results (for
        example <code>category:electronics</code> or <code>price:1|5</code>).
      </td>
    </tr>
    <tr>
      <td>
        <code>neg_f_must</code>
      </td>
      <td>array[string]</td>
      <td>
        An array of <code>AND</code> filters to exclude the product results (for
        example <code>category:electronics</code> or <code>price:1|5</code>).
      </td>
    </tr>
    <tr>
      <td>
        <code>search_in_variants</code>
      </td>
      <td>boolean</td>
      <td>
        A boolean value indicating whether to search in product variants. If set
        to <code>true</code>, the search includes variants of products. Defaults
        to the search setting configured in the app.
      </td>
    </tr>
    <tr>
      <td>
        <code>non_collapsed_variants</code>
      </td>
      <td>boolean</td>
      <td>
        A boolean value indicating whether to return non-collapsed variants in
        the results. If set to <code>true</code>, variants are returned as
        separate items in the results.{" "}
        <b>Works only when search_in_variants is allowed</b>.
      </td>
    </tr>
  </tbody>
</table>

### Step object fields

| Field             | Type          | Description                                            |
| :---------------- | :------------ | :----------------------------------------------------- |
| `question_handle` | string        | The handle of the question the user answered.          |
| `option_handles`  | array[string] | An array of selected option handles for that question. |

### Request headers

| Header            | Value              | Required    | Description                                |
| :---------------- | :----------------- | :---------- | :----------------------------------------- |
| `Content-Type`    | `application/json` | ✓           | The content type of the request body.      |
| `Accept-Encoding` | `gzip, deflate`    | Recommended | The accepted response compression formats. |

  <div slot="code">
    <h4 class="code-section-title">Example Request</h4>

```shell
curl -X POST "https://live.luigisbox.com/v1/assistant?tracker_id=1234-5678&assistant_handle=shoe_finder&user_id=123456" \
  -H "Content-Type: application/json" \
  -H "Accept-Encoding: gzip, deflate" \
  -d '{
    "assistant_version": 1,
    "steps": [
      {
        "question_handle": "budget",
        "option_handles": ["budget-low"]
      }
    ],
    "hit_fields": "title,price_amount,image_link",
    "field_overrides": {
      "price_amount": "price_amount_7"
    }
  }'
```

  </div>
</ApiSection>

<ApiSection>
## How to make a request

The endpoint is stateful from your application's perspective. Your frontend or backend keeps track of answered steps and resends them on every request.

1. Start the flow with an empty `steps` array.
2. Render the returned `question` and `hits`.
3. Append the selected answer to `steps`.
4. If the selected option included `next_question_handle`, send that exact value in the next request.
5. Call the endpoint again with the updated state.
6. Stop when `question` becomes `null`.

  <div slot="code">
    <h4 class="code-section-title">Code Examples</h4>
    <ApiCodeTabs syncKey="shopping-assistant-request">
      <div slot="javascript">

```javascript
import axios from "axios";

const trackerId = "1234-5678";
const assistantHandle = "shoe_finder";
const userId = "123456";
const endpoint = "https://live.luigisbox.com/v1/assistant";

let steps = [];

async function fetchAssistant(nextQuestionHandle = undefined) {
  const response = await axios.post(
    endpoint,
    {
      assistant_version: 1,
      steps,
      next_question_handle: nextQuestionHandle,
      hit_fields: "title,price_amount,image_link",
      field_overrides: {
        price_amount: "price_amount_7",
      },
    },
    {
      params: {
        tracker_id: trackerId,
        assistant_handle: assistantHandle,
        user_id: userId,
      },
      headers: {
        "Accept-Encoding": "gzip, deflate",
      },
    },
  );

  return response.data;
}

function registerAnswer(questionHandle, optionHandle) {
  steps.push({
    question_handle: questionHandle,
    option_handles: [optionHandle],
  });
}
```

      </div>
      <div slot="ruby">

```ruby
require 'faraday'
require 'json'

connection = Faraday.new(url: 'https://live.luigisbox.com')

response = connection.post('/v1/assistant') do |req|
  req.params['tracker_id'] = '1234-5678'
  req.params['assistant_handle'] = 'shoe_finder'
  req.params['user_id'] = '123456'
  req.headers['Content-Type'] = 'application/json'
  req.body = {
    assistant_version: 1,
    steps: [
      {
        question_handle: 'budget',
        option_handles: ['budget-low']
      }
    ],
    hit_fields: 'title,price_amount,image_link',
    field_overrides: {
      price_amount: 'price_amount_7'
    }
  }.to_json
end

puts JSON.pretty_generate(JSON.parse(response.body))
```

      </div>
      <div slot="php">

```php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client();

$response = $client->post('https://live.luigisbox.com/v1/assistant', [
    'query' => [
        'tracker_id' => '1234-5678',
        'assistant_handle' => 'shoe_finder',
        'user_id' => '123456',
    ],
    'json' => [
        'assistant_version' => 1,
        'steps' => [
            [
                'question_handle' => 'budget',
                'option_handles' => ['budget-low'],
            ],
        ],
        'hit_fields' => 'title,price_amount,image_link',
        'field_overrides' => [
            'price_amount' => 'price_amount_7',
        ],
    ],
]);

echo $response->getBody();
```

      </div>
    </ApiCodeTabs>

  </div>
</ApiSection>

<ApiSection>
## HTTP response

The endpoint returns the current result set and, when applicable, the next question in the assistant flow.

### Top-level fields

| Field                  | Description                                                                            |
| :--------------------- | :------------------------------------------------------------------------------------- |
| `assistant_handle`     | The handle of the assistant used for this response.                                    |
| `tracker_id`           | The Luigi's Box tracker identifier.                                                    |
| `hits`                 | An array of hit objects matching the current dialogue state.                           |
| `important_attributes` | An array of attribute names Luigi's Box considers useful to highlight in the hit list. |
| `avatar_image_link`    | The URL of the assistant avatar image, if available.                                   |
| `intro_html`           | The introductory HTML for the assistant flow, if available.                            |
| `question`             | The next question to display, or `null` when the flow is finished.                     |
| `steps`                | An echo of the current step state, when applicable.                                    |

### Hit fields

| Field               | Description                                                                                                                   |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------- |
| `hits[].url`        | The URL of the matched product.                                                                                               |
| `hits[].type`       | The type of the returned hit.                                                                                                 |
| `hits[].attributes` | An object containing the product attributes returned in the response. The exact keys depend on your catalog and `hit_fields`. |
| `hits[].updated_at` | The timestamp of the last hit update, if available.                                                                           |

### Question fields

| Field                       | Description                                                   |
| :-------------------------- | :------------------------------------------------------------ |
| `question.question_handle`  | The unique handle of the question.                            |
| `question.title_html`       | The HTML-formatted title of the question.                     |
| `question.description_html` | The HTML-formatted description of the question, if available. |
| `question.image_link`       | The URL of the question image, if available.                  |
| `question.type`             | The type of the question: `single_choice` or `multi_choice`.  |
| `question.options`          | An array of options available for the next answer.            |

### Option fields

| Field                    | Description                                                            |
| :----------------------- | :--------------------------------------------------------------------- |
| `option_handle`          | The unique handle of the option.                                       |
| `title_html`             | The HTML-formatted title of the option.                                |
| `description_html`       | The HTML-formatted description of the option, if available.            |
| `parent_question_handle` | The handle of the parent question, if available.                       |
| `next_question_handle`   | The handle of the next question, if fixed by the assistant definition. |
| `image_link`             | The URL of the option image, if available.                             |
| `color_code`             | The color swatch value of the option, if available.                    |
| `hits_count`             | The number of products matching the option.                            |
| `price_range`            | The computed price range for the products behind that option.          |

  <div slot="code">
    <h4 class="code-section-title">Example Response</h4>

```json
{
  "assistant_handle": "shoe_finder",
  "tracker_id": "1234-5678",
  "hits": [
    {
      "url": "/products/running-shoe-1",
      "type": "product",
      "updated_at": "2026-04-01T12:00:00Z",
      "attributes": {
        "title": "Trail Runner",
        "price_amount": 89,
        "image_link": "https://example.com/images/trail-runner.jpg"
      }
    }
  ],
  "important_attributes": ["brand", "color"],
  "avatar_image_link": "",
  "intro_html": "",
  "question": {
    "question_handle": "color",
    "title_html": "Pick a color",
    "description_html": "Choose the one you are most likely to wear.",
    "image_link": null,
    "type": "single_choice",
    "options": [
      {
        "option_handle": "color-blue",
        "title_html": "Blue",
        "description_html": "Classic and versatile.",
        "parent_question_handle": "color",
        "next_question_handle": null,
        "image_link": "https://example.com/blue.jpg",
        "hits_count": 12,
        "price_range": "69.0 - 119.0"
      }
    ]
  },
  "steps": []
}
```

  </div>
</ApiSection>

<ApiSection>
## Errors

The API uses standard HTTP status codes. Note that the format of the error response body can vary depending on the type of error.

Parse the status code first, then inspect the response body when one is present.

| Status                      | Meaning                                                                                                                   |
| :-------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `400 Bad Request`           | An invalid request. This includes malformed input, incorrect data types, wrong HTTP method, or assistant lookup failures. |
| `404 Not Found`             | A request to an endpoint path that does not exist. Assistant lookup failures are returned as `400`, not `404`.            |
| `500 Internal Server Error` | A temporary internal error. Retry with backoff, and log the `Request-Id` if the response includes one.                    |

<Aside type="note">
  A lookup failure can still return `400 Bad Request`. That happens when the
  request reaches the correct endpoint, but the supplied `tracker_id`,
  `assistant_handle`, or `assistant_version` cannot be resolved to an assistant.
</Aside>

  <div slot="code">
    <h4 class="code-section-title">Error responses</h4>

<Tabs>
  <TabItem label="400 Bad Request">
```json
{
  "type": "malformed_input",
  "reason": "incorrect parameters provided",
  "caused_by": {
    "assistant_version": ["must be an integer"]
  }
}
```
  </TabItem>
  <TabItem label="404 Not Found">
```text
Not Found
Request-Id: <request-id>
```
  </TabItem>
</Tabs>
  </div>
</ApiSection>
