> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cludo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics Guide

> Track Cludo search queries, clicks, and search-as-you-type events to measure search performance and improve relevance using the analytics endpoints.

Cludo provides comprehensive analytics to help you understand how users search, what they find, and where to improve.

## Tracking events

Analytics data is populated by tracking events. The recommended client-side setup is the [Cludo API analytics script](https://help.cludo.com/implementation/how-to-implement-cludos-api-analytics-script/). If you use the React hooks from `@cludosearch/cludo-search-components`, analytics events are handled automatically by the components package. If you implement search directly via the API, send tracking events yourself.

### Client-side event tracking

Track search events from your frontend. These endpoints accept any auth method, including anonymous requests with no auth header.

<CodeGroup>
  ```bash cURL theme={null}
  # Use https://api-us1.cludo.com for the US region

  # Track a search query
  curl -X POST "https://api.cludo.com/api/v3/{customerId}/{engineId}/search/pushstat/querylog" \
    -H "Content-Type: application/json" \
    -d '{
      "sw": "wireless headphones",
      "qid": "a3f2c1b8-4e9d-4a1c-9b2e-7f4d6e8a1c3d",
      "rc": "42",
      "dt": "desktop"
    }'

  # Track a result click
  curl -X POST "https://api.cludo.com/api/v3/{customerId}/{engineId}/search/pushstat/clicklog" \
    -H "Content-Type: application/json" \
    -d '{
      "ls": "searchresult",
      "sw": "wireless headphones",
      "qid": "a3f2c1b8-4e9d-4a1c-9b2e-7f4d6e8a1c3d",
      "clurl": "https://example.com/products/widget-pro",
      "cli": "1",
      "title": "Widget Pro"
    }'
  ```

  ```python Python theme={null}
  import requests

  BASE_URL = "https://api.cludo.com"  # Use https://api-us1.cludo.com for the US region


  def track_search(customer_id, engine_id, query_id, query, result_count):
      requests.post(
          f"{BASE_URL}/api/v3/{customer_id}/{engine_id}/search/pushstat/querylog",
          json={
              "sw": query,
              "qid": query_id,
              "rc": str(result_count),
              "dt": "desktop",
          },
      )


  def track_click(customer_id, engine_id, query_id, click_url, click_index, title):
      requests.post(
          f"{BASE_URL}/api/v3/{customer_id}/{engine_id}/search/pushstat/clicklog",
          json={
              "ls": "searchresult",
              "sw": "search query",
              "qid": query_id,
              "clurl": click_url,
              "cli": str(click_index),
              "title": title,
          },
      )
  ```

  ```javascript JavaScript theme={null}
  const BASE_URL = "https://api.cludo.com";
  // Use https://api-us1.cludo.com for the US region

  async function trackSearch(customerId, engineId, queryId, query, resultCount) {
    await fetch(
      `${BASE_URL}/api/v3/${customerId}/${engineId}/search/pushstat/querylog`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          sw: query,
          qid: queryId,
          rc: String(resultCount),
          dt: "desktop",
        }),
      },
    );
  }

  async function trackClick(customerId, engineId, queryId, clickUrl, clickIndex, title) {
    await fetch(
      `${BASE_URL}/api/v3/${customerId}/${engineId}/search/pushstat/clicklog`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          ls: "searchresult",
          sw: "search query",
          qid: queryId,
          clurl: clickUrl,
          cli: String(clickIndex),
          title: title,
        }),
      },
    );
  }
  ```

  ```typescript TypeScript theme={null}
  const BASE_URL = "https://api.cludo.com";
  // Use https://api-us1.cludo.com for the US region

  async function trackSearch(
    customerId: number,
    engineId: number,
    queryId: string,
    query: string,
    resultCount: number,
  ): Promise<void> {
    await fetch(
      `${BASE_URL}/api/v3/${customerId}/${engineId}/search/pushstat/querylog`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          sw: query,
          qid: queryId,
          rc: String(resultCount),
          dt: "desktop",
        }),
      },
    );
  }

  async function trackClick(
    customerId: number,
    engineId: number,
    queryId: string,
    clickUrl: string,
    clickIndex: number,
    title: string,
  ): Promise<void> {
    await fetch(
      `${BASE_URL}/api/v3/${customerId}/${engineId}/search/pushstat/clicklog`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          ls: "searchresult",
          sw: "search query",
          qid: queryId,
          clurl: clickUrl,
          cli: String(clickIndex),
          title: title,
        }),
      },
    );
  }
  ```
</CodeGroup>

The tracking body uses flat key-value pairs (for example `{"sw": "query", "qid": "..."}`). Do **not** wrap them in a `values` object. See [API Reference → Tracking](/api-reference/overview#tracking) for the full list of parameters.

### Server-side tracking and IP forwarding

If your integration sends tracking events from a backend rather than directly from the browser, the API receives your server's IP instead of the real user IP. This affects analytics deduplication and geographic data.

To pass the real client IP, include it as an `ip` field in the request body:

```javascript theme={null}
body: JSON.stringify({
  sw: query,
  qid: queryId,
  rc: String(resultCount),
  dt: "desktop",
  ip: "203.0.113.42"   // real client IP captured server-side
})
```

<Warning>
  The tracking endpoints do not read the `X-Real-IP`, `X-Forwarded-For`, or `Forwarded` headers. To pass a client IP from a server-side integration, use the `ip` body field.
</Warning>

## Search-as-you-type implementations

If your implementation fires search requests on every keystroke (search-as-you-type, or SAYT), follow the three steps below. Skipping them is the most common cause of noisy analytics. Symptoms include partial keystroke queries filling up Searches Without Results, a 0% click-through rate, and every search appearing as "ineffective" in the dashboard.

<Steps>
  <Step title="Mark keystroke searches with searchContext">
    Add `searchContext: "sayt"` to the body of [search requests](/api-reference/v4/search/search) fired on each keystroke. This tells the backend to classify them as SAYT and exclude them from Dashboard analytics (Popular Searches, Trending, etc.).

    ```javascript theme={null}
    body: JSON.stringify({
      query: partialQuery,
      searchContext: "sayt"   // keystroke search; omit for committed searches
    })
    ```
  </Step>

  <Step title="Only log committed searches">
    Call [Track Search Query](/api-reference/track-search-query) (`querylog`) only when the user commits a final search, on Enter or explicit submission, not on every debounced keystroke. Logging every partial query produces rows like `r`, `ri`, `riv`, `rive`... in Searches Without Results.
  </Step>

  <Step title="Always log result clicks">
    Call [Track Result Click](/api-reference/track-result-click) (`clicklog`) whenever a user clicks a result. Missing click events is the sole cause of a 0% click-through rate and all searches appearing as "ineffective" in the dashboard.
  </Step>
</Steps>

## Analytics setup checklist

Use this checklist to confirm every analytics event is wired up correctly. Missing a required field is the most common cause of gaps in the [Dashboard](https://my.cludo.com).

### Every tracking call

* Correct region base URL: `https://api.cludo.com` (EU, customer IDs below 10,000,000) or `https://api-us1.cludo.com` (US).
* `{customerId}` and `{engineId}` in the path match the search that produced the `qid` (a mismatch returns `404`).

### Track Search Query (`querylog`)

See [Track Search Query](/api-reference/track-search-query) for the full parameter reference.

* `sw` — the search query the user submitted (**required**).
* `qid` — the `QueryId` copied from the search response (**required**).
* `rc` — number of results returned (needed for Searches Without Results).
* `dt` — device type (`mobile`, `tablet`, or `desktop`).
* `sid` / `qsid` — session and query-session IDs to stitch events per visit.
* `fquery` — spell-corrected query, when the user saw the corrected version.
* `refurl` — URL of the page where the search happened (the destination page).
* `refpt` — title of the page where the search happened.

### Track Result Click (`clicklog`)

See [Track Result Click](/api-reference/track-result-click) for the full parameter reference.

* `qid` — links the click back to its originating search.
* `clurl` — URL of the clicked result.
* `cli` — rank of the clicked item in the result list.
* `ls` — `searchresult` for organic clicks, `banner` for banner clicks.
* `sw` and `title` — the query and the clicked result's title.
* `cloi` — banner ID, required when `ls` is `banner`.
* `refurl` — URL of the page where the click happened (the destination page).
* `refpt` — title of the page where the click happened.

## Viewing analytics

Analytics data is available in the [Cludo Dashboard](https://my.cludo.com). The dashboard provides:

* **Search term performance**: top queries, zero-result queries, content gaps
* **Search volume over time**: total and trending search volume
* **Click-through rate**: are users finding what they need?
* **AI analytics**: volume & feedback scores
