---
title: API Principles
description: Authentication, rate limits, error handling, and API conventions for Luigi's Box APIs.
slug: platform-foundations/api-principles
docKind: reference
hub: platform-foundations
---

Luigi's Box provides HTTP-based APIs for two main purposes: serving search results to users (Public) and managing your data/configuration (Private).

## Service reference and endpoints

:::note
**Base URL Note:** Most services (Search, Content, Recommender) live on `live.luigisbox.com`. The Analytics Event API lives on `api.luigisbox.com`.
:::

### Public API (unauthenticated)

**Auth:** Requires only your public `tracker_id`.

**Usage:** Safe to call from Frontend (Browsers, Mobile Apps).

| Category               | Method       | Base URL             | Endpoint               | Description                                                            |
| :--------------------- | :----------- | :------------------- | :--------------------- | :--------------------------------------------------------------------- |
| **Search**             | `GET`/`POST` | `live.luigisbox.com` | `/search`              | Full-text search & facets.                                             |
| **Autocomplete**       | `GET`        | `live.luigisbox.com` | `/autocomplete/v2`     | Prefix-based suggestions.                                              |
| **Recommender**        | `POST`       | `live.luigisbox.com` | `/v1/recommend`        | Product recommendations.                                               |
| **Shopping Assistant** | `POST`       | `live.luigisbox.com` | `/v1/assistant`        | AI-powered structured dialogue that helps customers discover products. |
| **Top items**          | `GET`        | `live.luigisbox.com` | `/v1/top_items`        | Most popular items.                                                    |
| **Trending queries**   | `GET`        | `live.luigisbox.com` | `/v2/trending_queries` | Trending search terms.                                                 |
| **Analytics**          | `POST`       | `api.luigisbox.com`  | `/v1`                  | **Events API.** Ingest user interactions.                              |

### Private API (authenticated)

**Auth:** Requires **HMAC Signature** using your Private Key.
**Usage:** Backend Server ONLY.

#### Indexing hub (content management)

_Manage your product catalog._

| Method   | Base URL             | Endpoint              | Description                                      |
| :------- | :------------------- | :-------------------- | :----------------------------------------------- |
| `POST`   | `live.luigisbox.com` | `/v1/content`         | **Create/Replace.** Add new products.            |
| `PATCH`  | `live.luigisbox.com` | `/v1/content`         | **Partial Update.** Update specific fields.      |
| `DELETE` | `live.luigisbox.com` | `/v1/content`         | **Delete.** Remove products.                     |
| `PATCH`  | `live.luigisbox.com` | `/v1/update_by_query` | **Batch Update.** Update items matching a query. |
| `GET`    | `live.luigisbox.com` | `/v1/content_export`  | **Export.** Download your current index data.    |

#### Recommender customization

_Manage pinning rules and batch recommendations._

| Method | Base URL             | Endpoint                              | Description                                        |
| :----- | :------------------- | :------------------------------------ | :------------------------------------------------- |
| `POST` | `live.luigisbox.com` | `/v1/recommender/pin/{ID}/scopes`     | Define pinning scopes (rules).                     |
| `GET`  | `live.luigisbox.com` | `/v1/recommender/pin/{ID}/summary`    | Get pinning configuration summary.                 |
| `POST` | `live.luigisbox.com` | `/v1/recommender/batching/{ID}/users` | Trigger batch recommendation generation for users. |
| `*`    | `live.luigisbox.com` | `/v1/recommender/pin/*`               | Various CRUD endpoints for managing pins.          |

:::danger
**Security Warning:** Never call the Private API (Content/Pinning) from a frontend application. Doing so exposes your Private Key and allows attackers to corrupt your data.
:::

---

## Authentication (HMAC)

Only the **Private API** endpoints require a custom **HMAC-SHA256** signature.

### Credentials

You will need your API credentials found in **Luigi's Box App > Settings > Integration Settings**:

- **Tracker_ID (Public Key):** Identifies your account.
- **Secret Key (Private Key):** Used to sign requests.

### The signing flow

To authenticate, you must construct a specific "String to Sign," hash it, and attach it as a header.

<div class="lb-mermaid-frame"><pre class="mermaid">flowchart TD
A["HTTP method"]
B["Content-Type"]
C["Date header"]
D["Path"]
E["Canonical string"]
F{"Hash function"}
G["Raw bytes"]
H["Final signature"]
I["Authorization header"]
J["API server"]
Key["Private secret"]
A --> E
B --> E
C --> E
D --> E
E -->|HMAC SHA-256| F
Key -.-> F
F -->|Binary output| G
G -->|Base64 encode| H
H -->|Format string| I
I -->|Send| J</pre></div>

### Detailed specification

#### Step 1: Required headers

Every authenticated request must include these headers. The values used here **must** match the values used in the signature generation byte-for-byte.

| Header          | Description                                                                                                                                                                                                                                                                |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Date`          | The current timestamp in [HTTP Date format](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date) (e.g., `Thu, 29 Jun 2017 12:11:16 GMT`). **Note:** Our server clock tolerance is **±5 seconds**. You must regenerate the date and signature for every request. |
| `Content-Type`  | Describes your payload (e.g., `application/json; charset=utf-8`).                                                                                                                                                                                                          |
| `Authorization` | The authentication string. Format: `AppName [tracker_id]:[signature]`                                                                                                                                                                                                      |

#### Step 2: Canonical string construction

Concatenate the following four strings, separated by a newline character (`\n`).

1. **HTTP Verb:** Uppercase (e.g., `POST`, `PUT`, `DELETE`).
2. **Content-Type:** Exactly as sent in the header (e.g., `application/json; charset=utf-8`).
3. **Date:** Exactly as sent in the header.
4. **Resource Path:** The absolute path **excluding** the query string (e.g., `/v1/content`, not `/v1/content?foo=bar`).

#### Step 3: Signature calculation

1. Compute the HMAC-SHA256 digest of the Canonical String using your **Secret Key**.
2. Encode the binary digest into a **Base64** string.

### Example implementations

```ruby
require 'time'
require 'openssl'
require 'base64'

def digest(key, method, endpoint, date)
  content_type = 'application/json; charset=utf-8'

  data = "#{method}\n#{content_type}\n#{date}\n#{endpoint}"

  dg = OpenSSL::Digest.new('sha256')
  Base64.strict_encode64(OpenSSL::HMAC.digest(dg, key, data)).strip
end

date = Time.now.httpdate
digest("secret", "POST", "/v1/content", date)
```

```php
<?php

function digest($key, $method, $endpoint, $date) {
  $content_type = 'application/json; charset=utf-8';

  $data = "{$method}\n{$content_type}\n{$date}\n{$endpoint}";

  $signature = trim(base64_encode(hash_hmac('sha256', $data, $key, true)));

  return $signature;
}

$date = gmdate('D, d M Y H:i:s T');
digest("secret", "POST", "/v1/content", $date);
```

```javascript
// This configuration and code work with the Postman tool
// https://www.getpostman.com/
//
// Start by creating the required HTTP headers in the "Headers" tab
//  - Content-Type: application/json; charset=utf-8
//  - Authorization: {{authorization}}
//  - date: {{date}}
//
// The {{variable}} is a postman variable syntax. It will be replaced
// by values precomputed by the following pre-request script.

var privateKey = "your-secret";
var publicKey = "your-tracker-id";
var contentType = "application/json; charset=utf-8";

var requestUri = request.url.replace(/^.*\/\/[^\/]+/, "").replace(/\?.*$/, "");
var timestamp = new Date().toUTCString();
var signature = [request.method, contentType, timestamp, requestUri].join("\n");

var encryptedSignature = CryptoJS.HmacSHA256(signature, privateKey).toString(
  CryptoJS.enc.Base64,
);

pm.request.headers.add({
  key: "Content-Type",
  value: contentType,
});
pm.request.headers.add({
  key: "Authorization",
  value: "ApiAuth " + publicKey + ":" + encryptedSignature,
});
pm.request.headers.add({
  key: "Date",
  value: timestamp,
});
```

```shell
#!/bin/bash

digest() {
  KEY=$1
  METHOD=$2
  CONTENT_TYPE="application/json; charset=utf-8"
  ENDPOINT=$3
  DATE=$4

  DATA="$METHOD\n$CONTENT_TYPE\n$DATE\n$ENDPOINT"

  printf "$DATA" | openssl dgst -sha256 -hmac "$KEY" -binary | base64
}

date=$(env LC_ALL=en_US date -u '+%a, %d %b %Y %H:%M:%S GMT')
echo $(digest "secret" "GET" "/v1/content" "$date")
```

---

## User identification

While "Authentication" identifies your _server_ to us, "User Identification" identifies your _customer_ to the AI.

To maintain session continuity and personalization, you must handle two specific IDs correctly in every frontend request (Search, Autocomplete, Analytics).

| ID Type       | Parameter     | Description                                                                                                                                               |
| :------------ | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Device ID** | `client_id`   | **Required.** A persistent identifier for the browser/device (e.g., a cookie ID). Must remain constant for the entire session, regardless of login state. |
| **Auth ID**   | `customer_id` | **Optional.** The database ID of the logged-in user. Send this _only_ when the user is logged in.                                                         |

### The session continuity rule

The `client_id` is the anchor. You must never change it mid-session, even if the user logs in or out.

<div class="lb-mermaid-frame"><pre class="mermaid">sequenceDiagram
participant User
participant App
participant LuigiBox as LBX
Note over User, App: Anonymous browsing
User->>App: Views Homepage
App->>LuigiBox: Event with device_user_id COOKIE_123
Note over User, App: User logs in with auth_user_id USER_99
User->>App: Enters Credentials
App->>LuigiBox: Event with device_user_id COOKIE_123 and auth_user_id USER_99
Note over LuigiBox: History merges because device_user_id stays the same
Note over User, App: User logs out
User->>App: Clicks Logout
App->>LuigiBox: Event with device_user_id COOKIE_123
Note over LuigiBox: Session continues anonymously</pre></div>

:::danger
**Common Mistake:** Do not replace the `client_id` with the `customer_id` upon login.

- **Wrong:** Anonymous (`dev_1`) -> Login -> Logged In (`user_99`)

- **Result:** The system thinks a new user has arrived. The session breaks, and personalization history is lost.
  :::

## Rate limiting (throttling)

We enforce rate limits to ensure stability. Limits are applied per **Tracker ID** (Account level) and per **IP Address**.

| Endpoint            | Account Limit                    | IP Limit    |
| :------------------ | :------------------------------- | :---------- |
| **Content Updates** | 500 req / min, 5 concurrent reqs | -           |
| **Autocomplete**    | 800 req / min                    | 30 req / 5s |
| **Search**          | 350 req / min                    | 30 req / 5s |
| **Recommender**     | 60 req / 5s                      | 30 req / 5s |

### Handle 429 errors

If you exceed a limit, you will receive a `429 Too Many Requests` status. Your application **must** handle this gracefully.

We provide a `Retry-After` header indicating how many seconds you must wait.

<div class="lb-mermaid-frame"><pre class="mermaid">sequenceDiagram
participant Client
participant API
Client->>API: POST content request
API-->>Client: 429 Retry-After 5 seconds
Note over Client: Wait 5 seconds
Client->>API: Retry content request
API-->>Client: 200 OK</pre></div>

**Recommended Strategy:**

- **Realtime APIs (Search, Autocomplete):** Do not retry immediately. Discard the request.
- **Batch APIs (Content Updates):** Use an exponential backoff strategy (wait `Retry-After`, then retry).

---

## Error handling

You should design your integration to be resilient to network failures.

| Code    | Reason              | Type          | Action                                                                                                |
| :------ | :------------------ | :------------ | :---------------------------------------------------------------------------------------------------- |
| **401** | Auth Failed         | **Fatal**     | Check your Signature generation. The response body contains the string we expected vs. what you sent. |
| **408** | Timeout             | **Transient** | Safe to retry once.                                                                                   |
| **429** | Throttling          | **Transient** | Wait and retry (see above).                                                                           |
| **500** | Server Error        | **Fatal**     | Log the error and contact support.                                                                    |
| **502** | Bad Gateway         | **Transient** | Usually a deploy or network blip. Retry after 1s.                                                     |
| **503** | Service Unavailable | **Transient** | Check [luigisboxstatus.com](https://www.luigisboxstatus.com/).                                        |

### Development mode

To ensure your integration is robust, we may enable **Development Mode** on your account during the integration phase.

**What it does:**

- A small percentage of your requests will deliberately fail with various error codes (408, 429, 500).
- These errors are marked in the JSON body as `development_mode: true`.

**Why:**
This forces your developers to write proper error handling and retry logic _before_ you go to production.

:::note
**Note:** Development mode is never enabled on Production accounts without explicit consent.
:::
