---
title: Event API
description: Send Luigi's Box analytics events directly from your backend, mobile app, or fully custom frontend.
slug: analytics/api/events
docKind: endpoint
hub: analytics
tableOfContents: true
---
import ApiSection from "../../../../components/ApiSection.astro";
import ApiEndpoint from "../../../../components/ApiEndpoint.astro";
import { Aside } from "@astrojs/starlight/components";

<Aside type="note">
  This endpoint does not use `Authorization` headers. Authenticate by including your `tracker_id` in every event payload.
</Aside>

<ApiSection>
  <div slot="code">
    <ApiEndpoint method="POST" url="https://api.luigisbox.com" />

    <h4 class="code-section-title">Example request</h4>

```shell
curl -X POST "https://api.luigisbox.com" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "pv",
    "id": "f3f6917c-2e94-4e38-a744-24cbb82f284d",
    "url": "sku-2372711",
    "tracker_id": "1234-5678",
    "client_id": "ios-user-6969470340316755000"
  }'
```
  </div>

## Overview

The Events API accepts structured JSON events that describe what users see, click, and buy. It is the right choice when you need to send analytics from a backend, mobile app, native application, or a custom frontend that should not rely on the browser collector.

Luigi's Box currently supports these event patterns in `v1`:

- **Page and detail views** via `pv`
- **Search, autocomplete, listing, and recommendation impressions** via `event`
- **Clicks and micro-conversions** via `click`
- **Completed purchases** via `transaction`

Use the Events API when:

- You are integrating Luigi's Box in a mobile application
- You need server-side control over event delivery
- You want to send analytics from a custom frontend implementation

<Aside type="tip">
  On websites, prefer the [DataLayer Collector](/analytics/collector/) unless you explicitly need backend or custom event delivery.
</Aside>
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: event envelope</h4>

```json
{
  "type": "pv",
  "id": "f3f6917c-2e94-4e38-a744-24cbb82f284d",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "customer_id": "customer-42",
  "platform": ["iOS", "1.2.0"],
  "local_timestamp": 1739420400,
  "context": {
    "warehouse": "berlin"
  }
}
```
  </div>

## Event envelope

Every event shares the same top-level envelope. The event `type` determines which additional payload keys are required.

### Common parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `type` | String | ✓ | Event type such as `pv`, `event`, `click`, or `transaction`. |
| `tracker_id` | String | ✓ | Your Luigi's Box tracker ID. |
| `client_id` | String or Integer | ✓ | Stable anonymous identifier for the current browser, device, or app install. |
| `id` | String | ✓ | Globally unique identifier for this event. A UUID is recommended. |
| `customer_id` | String or Integer |  | Your logged-in user identifier. Send it together with `client_id` when available. |
| `local_timestamp` | Integer |  | UNIX timestamp in **seconds**. Do not send milliseconds. |
| `platform` | String or Array |  | Platform identifier such as `iOS`, `Android`, or `desktop`. You can also send `[platform, version]`. |
| `user_agent` | String |  | Browser or app user agent string. |
| `referer` | String |  | Previous page URL if available. |
| `context` | Object |  | Context values that match your indexed catalog context, for example warehouse or market. |
| `ab_test_variant` | String |  | Session-level A/B variant name such as `Original` or `Luigis`. |
| `consent_granted` | Boolean |  | Set `true` when the user granted consent for personalization. |
| `recommendation_id` | String |  | Recommendation batch identifier used mainly for email/newsletter attribution. |

### Identity rules

- Keep `client_id` stable for the same browser or device, even if the user logs in or logs out.
- Add `customer_id` only when the user is authenticated.
- Use the same product or object identity here as in your indexing data. See [Object identity](/platform-foundations/identity/).

<Aside type="caution">
  `local_timestamp` must be sent in seconds. If you send milliseconds, Luigi's Box will interpret the event as a date far in the future and reject it.
</Aside>
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: impression / pageview</h4>

```json
{
  "type": "pv",
  "id": "f3f6917c-2e94-4e38-a744-24cbb82f284d",
  "url": "sku-2372711",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "title": "White sneakers GLX 23",
  "customer_id": "customer-42",
  "local_timestamp": 1739420400,
  "referer": "/search?q=sneakers",
  "context": {
    "warehouse": "berlin"
  }
}
```
  </div>

## Impression events

Use `pv` to report page views and detail views.

- For a **catalog detail page**, send the indexed object identity in `url`
- For a **generic page view**, send the canonical page URL or path in `url`

### Parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `type` | String | ✓ | Must be `pv`. |
| `url` | String | ✓ | Object identity for a catalog detail page, or canonical page URL/path for a generic page view. |
| `title` | String |  | Human-readable page or object title for analytics presentation. |

This event is the foundation of journey tracking. It tells Luigi's Box what the user viewed before they searched, clicked, converted, or purchased.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: search event</h4>

```json
{
  "id": "f3f6917c-2e94-4e38-a744-24cbb82f284d",
  "type": "event",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "lists": {
    "Search Results": {
      "items": [
        {
          "title": "White shirt v-neck",
          "type": "item",
          "url": "39818",
          "position": 1,
          "price": 19
        }
      ],
      "query": {
        "string": "white shirt",
        "filters": {
          "brand": "Loona fashion",
          "sort by": "price_amount:asc",
          "_Variant": "Luigis",
          "_Guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        }
      }
    }
  }
}
```
  </div>

## Search events

Report full-text search interactions with a list event whose key is exactly `Search Results`.

### Parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `type` | String | ✓ | Must be `event`. |
| `lists` | Object | ✓ | Container for the reported lists. |
| `lists["Search Results"]` | Object | ✓ | The search results list. The list name is case-sensitive. |
| `lists["Search Results"].query.string` | String | ✓ | The exact user query. |
| `lists["Search Results"].query.filters` | Object |  | Active search filters, sort values, `_Guid`, and `_Variant`. |
| `lists["Search Results"].items` | Array | ✓ | The visible search results returned to the user. |

### Search result item fields

| Field | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `title` | String | ✓ | Display title of the result. |
| `type` | String | ✓ | Catalog object type such as `item`, `category`, or `article`. |
| `url` | String | ✓ | Indexed object identity. |
| `position` | Integer | ✓ | Absolute position in the full result set. Keep it cumulative across pagination. |
| `price` | Float |  | Displayed price if relevant. |

### Search-specific filters

- Use `_Guid` to send the `guid` from the Search API response
- Use `_Variant` to report the list-level A/B variant
- Include active filter and sort values exactly as the user sees them

<Aside type="tip">
  Search analytics should reflect what the user actually saw. If you reorder, hide, or enrich results on the frontend, report the final visible list, not the raw API response.
</Aside>
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: autocomplete event</h4>

```json
{
  "id": "b1a371fb-e7a2-4ed9-a86c-ce6747f4f65c",
  "type": "event",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "lists": {
    "Autocomplete": {
      "items": [
        {
          "title": "Shirts",
          "type": "category",
          "url": "c398818",
          "position": 1
        },
        {
          "title": "White shirt v-neck",
          "type": "item",
          "url": "39818",
          "position": 2,
          "price": 19
        }
      ],
      "query": {
        "string": "white shirt",
        "filters": {
          "_Variant": "Luigis",
          "_Guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        }
      }
    }
  }
}
```
  </div>

## Autocomplete events

Use `event` with a list named exactly `Autocomplete` to report what the user saw in the autocomplete dropdown.

### Parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `type` | String | ✓ | Must be `event`. |
| `lists.Autocomplete.query.string` | String | ✓ | The text currently typed by the user. |
| `lists.Autocomplete.query.filters` | Object |  | Optional filters. Include `_Guid` when the autocomplete response provides one. |
| `lists.Autocomplete.items` | Array | ✓ | Suggestions shown to the user. |

### Item fields

Autocomplete items use the same core fields as search results:

- `title`
- `type`
- `url`
- `position`
- optional `price`

<Aside type="tip">
  Debounce autocomplete tracking so you send the event only after the user pauses typing. Luigi's Box recommends roughly 500 ms.
</Aside>

If your empty-state autocomplete shows **Top Items** on focus, do **not** track it as `Autocomplete`. Track it as a [Recommendation event](#recommendation-events) and set both `Recommender` and `RecommenderClientId` to `autocomplete_popup`.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: product listing event</h4>

```json
{
  "id": "6667520098487994000",
  "type": "event",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "lists": {
    "Product Listing": {
      "items": [
        {
          "title": "White shirt",
          "type": "item",
          "url": "39818",
          "position": 1,
          "price": 19
        }
      ],
      "query": {
        "filters": {
          "sort by": "price_amount:asc",
          "_Variant": "Luigis",
          "_Guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        },
        "scopes": {
          "_category_label": "Clothing||Shirts",
          "_category_identity": "category_10283"
        }
      }
    }
  }
}
```
  </div>

## Product listing events

Use `event` with a list named exactly `Product Listing` to report category pages, brand pages, campaign landing pages, or other browse-first listing experiences.

### Parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `lists["Product Listing"].items` | Array | ✓ | Visible products in the listing. |
| `lists["Product Listing"].query.filters` | Object |  | Active filters, sorting, `_Guid`, and `_Variant`. |
| `lists["Product Listing"].query.scopes` | Object | ✓ | Listing context such as category or brand. |

### Scope keys

- For categories, send `_category_label` and `_category_identity`
- For brands, send `_brand_label` and `_brand_identity`

As with search, positions must be absolute across paginated results.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: recommendation event</h4>

```json
{
  "id": "3f1a615c-fce8-4d48-87bd-3a0fed760bcd",
  "type": "event",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "lists": {
    "Recommendation": {
      "items": [
        {
          "title": "Off-white long-sleeve shirt",
          "type": "item",
          "url": "283838",
          "position": 1,
          "price": 89
        },
        {
          "title": "Khaki slacks",
          "type": "item",
          "url": "881818",
          "position": 2,
          "price": 45.5
        }
      ],
      "query": {
        "filters": {
          "RecommenderClientId": "item_detail_alternatives",
          "RecommendationId": "rec_1abc2de3-f456-7890",
          "ItemIds": ["39818"],
          "Recommender": "similar_products_v3",
          "Type": "complementary_combined_assoc",
          "_Variant": "Luigis"
        }
      }
    }
  }
}
```
  </div>

## Recommendation events

Use `event` with a list named exactly `Recommendation` whenever a recommendation widget is shown.

### Required filters

| Filter key | Required | Description |
| :-- | :-- | :-- |
| `RecommenderClientId` | ✓ | Stable identifier of the recommender placement, such as `homepage_top` or `item_detail_alternatives`. |
| `RecommendationId` | Strongly recommended | Unique identifier of the returned recommendation set. Required for all new integrations. |
| `ItemIds` |  | Input item identities used to generate the recommendation. |
| `Recommender` |  | Luigi's Box recommender name from the response. |
| `Type` |  | Recommendation type from the response. |
| `_Variant` |  | A/B test variant for this list. |

### Why `RecommendationId` matters

`RecommendationId` allows Luigi's Box to attribute later clicks and purchases to the exact recommendation set that generated them. For new integrations, treat it as mandatory.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: click event</h4>

```json
{
  "id": "cecceeef-f82f-4fd0-9caf-aaeef2981370",
  "type": "click",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "action": {
    "type": "click",
    "resource_identifier": "983838"
  }
}
```
  </div>

## Click events

Send a click event whenever a user clicks a search result, autocomplete suggestion, or recommendation.

### Parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `type` | String | ✓ | Must be `click`. |
| `action.type` | String | ✓ | Must be `click`. |
| `action.resource_identifier` | String | ✓ | Identity of the clicked object. If the clicked result is of type `query`, send its title instead. |

Luigi's Box uses `resource_identifier` to attribute the click to previously reported lists.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: conversion event</h4>

```json
{
  "id": "4a6de937-4e16-48fc-b2c9-024c45d0ef29",
  "type": "click",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "action": {
    "type": "add-to-cart",
    "resource_identifier": "product_123"
  }
}
```
  </div>

## Conversion events

Micro-conversions use the same outer event type as clicks.

- Keep `type` set to `click`
- Use an `action.type` other than `click`, such as `add-to-cart`, `add-to-wishlist`, or `buy`
- Set `action.resource_identifier` to the same object identity used in your catalog and list events

Send conversion events from every place where the action can happen, including list pages and detail pages. Luigi's Box scans the earlier event stream backwards to attribute the conversion to prior search, autocomplete, product listing, or recommendation impressions.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: transaction event</h4>

```json
{
  "id": "03dd16c3-4dd5-44c0-87c4-b3a652c06a87",
  "type": "transaction",
  "tracker_id": "1234-5678",
  "client_id": "web-user-7e2b9c4d-a1f3-4e8d-b6c5-2d3e4f5a6b7c",
  "items": [
    {
      "title": "White shirt, round neck, short sleeves",
      "url": "9339993",
      "count": 1,
      "total_price": 19,
      "was_discounted": false,
      "was_volume_discounted": false
    },
    {
      "title": "Brown overcoat",
      "url": "299299",
      "count": 2,
      "total_price": 268.5,
      "was_discounted": true,
      "was_volume_discounted": false
    }
  ]
}
```
  </div>

## Transaction events

Use `transaction` to report completed purchases.

### Parameters

| Parameter | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `type` | String | ✓ | Must be `transaction`. |
| `items` | Array | ✓ | All purchased line items in the order. |

### Transaction item fields

| Field | Type | Required | Description |
| :-- | :-- | :-- | :-- |
| `url` | String | ✓ | Indexed identity of the purchased object. |
| `count` | Integer | ✓ | Quantity purchased. |
| `total_price` | Float | ✓ | Final line-item price after quantity and discounts are applied. |
| `title` | String |  | Human-readable title for reporting. |
| `was_discounted` | Boolean |  | Set `true` when the line item was discounted below the catalog price. |
| `was_volume_discounted` | Boolean |  | Set `true` when a quantity-based discount was applied. |

Include **every purchased item** in the order, not only the ones previously touched by Luigi's Box lists. Luigi's Box handles attribution after ingesting the transaction.
</ApiSection>

<ApiSection>
  <div slot="code">
    <h4 class="code-section-title">Example: error response</h4>

```json
{
  "message": "Unknown event type: p"
}
```
  </div>

## Error handling and limits

### Common responses

| HTTP status | Meaning |
| :-- | :-- |
| `400 Bad Request` | Invalid JSON or invalid event structure. |
| `413 Payload Too Large` | Request is too large. Reduce payload size or enable compression. |
| `429 Too Many Requests` | Rate limit exceeded. Respect `Retry-After` and retry later. |

### Limits

- Maximum **30 events per 15 seconds** for a given `client_id`
- Maximum **400 events per session**
- Sessions expire after **30 minutes of inactivity**

If you exceed the rate limits, Luigi's Box returns `429 Too Many Requests`.
</ApiSection>
