# Welcome

Welcome to the Limy.ai developer docs!\
Limy helps companies understand how AI systems perceive their brand and improve their visibility across the agentic web. Our lightweight tracking pixel and CDN integration allow you to detect AI bots visiting your site, analyze their behavior, and uncover insights that help strengthen your brand’s presence in AI-driven conversations.

<figure><img src="/files/6g89az4tIBCCa6nsU2NY" alt=""><figcaption></figcaption></figure>


# Intro

### What is Limy?

Limy.ai is a visibility and intelligence platform built for the agentic web - the new ecosystem where AI systems like ChatGPT, Perplexity, and Gemini actively browse, interpret, and reference online content.

Limy helps companies understand how AI models perceive their brand, which pages AI bots visit, and how their information is being used to answer user prompts across AI search engines.

Inspired by the shift toward AI-driven discovery, Limy was built to give brands a clear picture of their presence in AI results and provide actionable guidance to improve visibility.

### What is the Limy Pixel? How is it different from traditional analytics?

The Limy tracking pixel is designed specifically for the agentic web.\
Unlike traditional analytics tools that focus on human visitors, Limy detects AI bots, maps their crawling behavior, and reveals how AI systems understand and surface your content in AI-driven conversations.

### Track with Limy

To start tracking AI bot activity on your website, install our lightweight tracking pixel via Google Tag Manager (GTM).

For improved bot accessibility and faster delivery, you can optionally deploy the Limy CDN snippet, which optimizes how AI crawlers access key parts of your site.

Once set up, Limy automatically detects AI crawlers, analyzes their behavior, and surfaces insights in your dashboard - with zero ongoing maintenance.

Once installed, Limy begins detecting AI crawlers and surfacing insights directly in your dashboard - no additional setup required.

To get started, visit the **Limy Installation Guide**

<figure><img src="/files/qS2yAJStdDWVl0y5C7pH" alt=""><figcaption></figcaption></figure>

For any questions please contact `answers@limy.ai` .


# Common FAQ's

<details>

<summary>Why is the data different in my GA4 and Limy ?</summary>

If you’ve recently integrated Agent Analytics via CDN (within the past week), Limy may not have collected enough data yet to fully reflect your site’s traffic patterns. While Google Analytics provides access to historical data immediately, Limy begins tracking from the point of integration and will need a bit more time to catch up.

Also, regardless of when you signed up, keep in mind that GA4 data can be partially blocked by certain browsers or extensions. In contrast, Limy data is not subject to these limitations, which means there may be slight discrepancies between the two.

</details>

<details>

<summary>Does Limy collect PII?</summary>

Limy does not collect or store any personally identifiable information (PII). All tracking is intentionally designed to operate only at the brand, traffic, and agent-interaction level, without identifying individual users.

This approach ensures full compliance with global privacy standards, including GDPR and CCPA, while still providing meaningful insights into how AI agents surface your content, how users engage with links shared inside LLMs, and how your brand performs across the agentic web.

</details>

<details>

<summary>Is my server exposed?</summary>

No, The integration runs entirely server-side, meaning no scripts are exposed to the browser. This reduces the risk of client side injection attacks or data leaks.

</details>

<details>

<summary><strong>What does the tracking pixel do?</strong></summary>

The pixel we embed in your website HTML monitors interactions from AI bots and crawlers, including ChatGPT, Gemini, and other LLM-related traffic.

</details>

<details>

<summary><strong>Why is the pixel needed to track AI bots?</strong></summary>

The pixel allows us to detect real-time visits from users who click your links directly inside LLM conversations - traffic that traditional analytics tools often miss.

This gives you clear visibility into how your content is surfaced by AI models, which pages users visit after engaging with AI-generated responses, and the real impact of agentic web platforms on your site.

</details>

<details>

<summary><strong>How will this help my brand or website?</strong></summary>

By revealing which pages are fetched by AI and how often, the pixel helps you fine-tune content to improve visibility in AI-generated answers and boost discoverability.

</details>

<details>

<summary><strong>Is personal data being tracked?</strong></summary>

No. The pixel only tracks non-human (bot) activity. We do not collect or process any personal or identifiable user data.

</details>

<details>

<summary><strong>What is your privacy policy?</strong></summary>

We comply with GDPR and other global privacy regulations. You can review our full privacy policy for details on how the pixel functions and how data is handled.

You can also review our full privacy policy on [our website](https://www.limy.ai/legal/privacy-policy).

</details>

<details>

<summary><strong>Will installing the pixel affect my website's performance?</strong></summary>

Not at all. The pixel is lightweight and designed to have no impact on page load times or user experience.

</details>

<details>

<summary><strong>How often is pixel data updated?</strong></summary>

The pixel logs bot activity continuously, with updates reflected in your dashboard in near real-time for up-to-date analysis.

</details>

<details>

<summary><strong>Can I see which specific pages the bots are visiting?</strong></summary>

Yes. The pixel tracks bot visits at the page level, so you can see exactly which URLs are being accessed and how frequently.

</details>


# Tracking

Unlike traditional analytics tools, Limy focuses on AI visibility signals helping you understand how your brand appears, performs, and is referenced across AI search engines.

<figure><img src="/files/LLBtiqj1Ph5h4CpxNBfu" alt=""><figcaption></figcaption></figure>


# What are we tracking?

Our JavaScript tracking pixel is carefully designed to collect high level behavioral and technical data in a privacy conscious manner. Its primary purpose is to provide transparent insights into user journeys, enabling businesses to enhance usability, performance, and engagement across their digital experiences.

This outlines the standard data points collected, along with details on how data is handled, compliance with privacy regulations, and available customization options to align with your organization’s policies and legal requirements.

### Data Captured by the Pixel

#### Page Information

* **Page URL** - The full URL of the current page.
* **Referrer URL** - The URL of the previous page (if available).
* **Page Title** - The title of the current document.
* **Timestamp** - Exact timestamp when the pageview occurred.

***

####

#### User Interaction Data *(if enabled)*

* **Click Events** - Interactions with buttons, links, and other clickable elements.
* **Scroll Depth** - Percentage of the page scrolled.
* **Time on Page** - Duration of active presence on the page.
* **Form Submissions** - Captures submission of forms (excluding sensitive fields such as passwords or credit card data).

***

####

#### Session & Visit Context

* **Session ID** - A unique, anonymized identifier per session.
* **Visit Duration** - Total time spent across the session.
* **Page Views** - Count of pages viewed during the session.

***

####

#### Device & Browser Details

* **Device Type** - Detected as desktop, mobile, or tablet.
* **Operating System** - OS name and version.
* **Browser Info** - Browser name and version.
* **Screen Resolution** - Width × height of the viewport.
* **Language Settings** - Browser locale/language preferences.

***

####

#### Traffic Source & Attribution

* **UTM Parameters** - Captures `utm_source`, `utm_medium`, `utm_campaign`, etc.
* **Attribution Data** - First-touch and last-touch attribution details.

***

####

#### Geolocation Data

* **Country & City** - Based on IP (anonymized or hashed in compliance with privacy laws).

***

####

#### Custom Metadata *(optional)*

* **User/Account ID** - Captured from your site’s data layer or logged-in state (if made available).
* **Dynamic Variables** - Any additional metadata passed via the data layer (e.g., product categories, custom flags).

***

### Privacy & Compliance

All data collected complies with leading privacy frameworks such as **GDPR** and **CCPA**. IP addresses are either anonymized or hashed. You may configure the pixel to:

* Limit tracking to certain data types
* Opt out of specific user interactions
* Disable data collection under certain jurisdictions

***

For any questions please contact `answers@limy.ai`


# Bots we are tracking

Limy tracks and analyzes AI bots, crawlers, and assistants like ChatGPT, Gemini, Grok, Perplexity to uncover how your content is accessed, interpreted and referenced.

We continuously monitor a wide range of AI bots and crawlers that interact with websites and online content. This includes search engine bots, AI assistants (ChatGPT, Gemini, Grok and Perplexity), and third-party scrapers used by LLM platforms to train or surface content.

By identifying and analyzing these bots’ behavior, we’re able to understand which content is being accessed, how it is being interpreted, and how often it’s being referenced in AI-generated responses. This visibility helps our clients optimize their content to improve discoverability and relevance across AI-driven search platforms.

| Platform            | Robots.txt Identifier                                                                                           | User-Agent header value                                                                                                                       |
| ------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| ChatGPT             | `ChatGPT-User`                                                                                                  | `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot`                                   |
| Meta AI             | <p><code>meta-externalagent</code><br><code>meta-externalfetcher</code><br><code>facebookexternalhit</code></p> | <p><code>facebookexternalhit/1.1</code><br><code>meta-externalagent/1.1</code><br><code>meta-externalfetcher/1.1</code></p>                   |
| Perplexity          | `PerplexityBot`                                                                                                 | `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)`                     |
| Google AI Overviews | `Googlebot`                                                                                                     | `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/W.X.Y.Z Safari/537.36` |
| Google Gemini       | `Googlebot-extended`                                                                                            | N/A (Uses `Googlebot` user agent. `robots.txt` controls how Google uses the data.)                                                            |

For any questions please contact `answers@limy.ai` .


# Search engines we support

This page outlines the engines we currently support, what they do, and which **bot user-agents** we detect via your server-side integration.

#### **ChatGPT (OpenAI)**

* #### **Bot(s) Tracked:**
  * `Mozilla/5.0 (compatible; GPTBot/1.0)`
  * `ChatGPT-User`
  * `OpenAI-User`
  * `Mozilla/5.0 AppleWebKit (OpenAI)`

#### **Perplexity.ai**

* **Bot(s) Tracked:**
  * `PerplexityBot`
  * `Mozilla/5.0 (compatible; PerplexityBot/1.0)`
  * `Perplexity/1.0`

#### **Gemini (Google)**

* **Bot(s) Tracked:**
  * `Google-Extended`
  * `Gemini-Crawler`
  * `GoogleOther`
  * `GoogleAI-ContentFetcher`

#### **AI Overviews (Google Search)**

* **Bot(s) Tracked:**
  * `Google-SGE-Crawler`
  * `Google-Extended`
  * `GoogleOther`
  * `AIOverviewBot`

#### 5. **AI Mode (Bing Copilot)**

* **Bot(s) Tracked:**
  * `Bingbot`
  * `EdgeGPT`
  * `Microsoft Copilot`
  * `BingAI-Reader`

#### 6. **Claude (Anthropic)**

* **Bot(s) Tracked:**
  * `ClaudeBot`
  * `AnthropicBot`
  * `Mozilla/5.0 (compatible; Claude/1.0)`

#### 7. **Grok (xAI)**

* **Bot(s) Tracked:**
  * `xAI-GrokBot`
  * `GrokCrawler`
  * `TwitterAI-Previewer`

***

We use a combination of:

* **Bot detection** (user-agent parsing + IP reputation)
* **Prompt-response mapping**
* **LLM-based mention scanning**
* **Custom headers and CDN-based fingerprinting**
* **Inference from click-throughs and traffic behavior**

***

### Coming Soon

We’re actively expanding support for:

* Meta AI (Facebook, Instagram)
* Apple Spotlight AI
* Brave Summarizer
* Kagi AI
* Amazon Rufus

***

By monitoring bot traffic, AI prompt behavior, and visibility signals, Limy helps you understand how your brand is discovered, cited, or recommended in these AI systems.


# GTM pixel

**Quick Start**

Get up and running with Limy Analytics in few minutes. Track LLM and AI agent visits and users page views.Adding Limy.ai to your website

#### Installation

Add this script to the `<head>` section of your website:

```javascript
<script>
(function(l,i,m,y,g,e,o){
    l[m] = l[m] || function () { 
        (l[m].q = l[m].q || []).push(arguments) 
    };
    e = i.createElement(y);
    e.async = 1;
    e.id = 'limy-analytics';
    e.src = "https://sdk.getlimy.ai/p/limy-analytics.min.js";
    e.setAttribute(m, g);
    o = i.getElementsByTagName(y)[0];
    o.parentNode.insertBefore(e, o);
})(window, document, "limy", "script", "YOUR_TOKEN_HERE");
</script>
```

Replace `YOUR_TOKEN_HERE` with your actual Limy Analytics token.

For any questions please contact `answers@limy.ai` .


# Adding Limy via Tag Manager

{% stepper %}
{% step %}

### Step 1

Go to- <https://tagmanager.google.com/>
{% endstep %}

{% step %}

### Step 2

Click- add a new tag

<figure><img src="/files/8Nuxq0xVIQK1uIXCgP92" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 3

In the top left side, name the tag in a name "Limy AI"

**Select "Tag Configuration"**

<figure><img src="/files/LG9whf8mOHsu9XEhZqW1" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 4

Select "Custom HTML"

<figure><img src="/files/Xghyt53Forrcjwg94ycf" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 5

Copy this code and apply the token you received instead of - "YOUR\_TOKEN\_HERE"

```javascript
<script>
(function(l,i,m,y,g,e,o){
    l[m] = l[m] || function () { 
        (l[m].q = l[m].q || []).push(arguments) 
    };
    e = i.createElement(y);
    e.async = 1;
    e.id = 'limy-analytics';
    e.src = "https://sdk.getlimy.ai/p/limy-analytics.min.js";
    e.setAttribute(m, g);
    o = i.getElementsByTagName(y)[0];
    o.parentNode.insertBefore(e, o);
})(window, document, "limy", "script", "YOUR_TOKEN_HERE");
</script>
```

{% endstep %}

{% step %}

### Step 6

Paste the code to the "HTML" input . Use the token provided by the company.

<figure><img src="/files/D2tXMoh1QjBmPRj9HIee" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 7

Scroll down and click "Triggering" and select "All pages"

<figure><img src="/files/YviPKgz8DADgMJmiecmb" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 8

Ensure "All Pages" is selected and click "Save"

<figure><img src="/files/ZpMznHhxzvyd0fuULoCl" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 8

Once saved, click on **Submit** on the upper right corner

<figure><img src="/files/34QmxgGV3XjwmB9sDMDM" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Ste 9

Click **Publish** and when the pop-up appears, click **Continue**.

<figure><img src="/files/ppUkGcm0bIum9lBRZtWe" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/l5nOE21YhYMn2FtBVmXF" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

```mathml
// Your pixel should be working now! :) 

// Please check with the Limy team to ensure it works. 
```

For any questions please contact `answers@limy.ai` .


# CDN Integration

<figure><img src="/files/UE7zcdocz2zRMtbHO26l" alt="" width="375"><figcaption></figcaption></figure>


# Vercel

Connect your Vercel project logs to Limy Analytics using a custom Log Drain.

### Overview

Vercel's Log Drain feature lets you forward logs, traces, speed insights, and analytics to third-party providers or custom endpoints. This guide walks you through connecting your Vercel project to Limy Analytics in 5 steps.

***

### Prerequisites

* A Vercel project (with admin access to Settings)
* Your **Limy Analytics API key** (found in your Limy dashboard)

***

### Step 1 — Open Drains in Vercel Project Settings

1. Go to your Vercel dashboard and open your project.
2. Click **Settings** in the left sidebar.
3. Scroll down and select **Drains** from the menu.
4. Click the **Add Drain** button in the top-right corner.

If no drains exist yet, you'll see an empty state with a "No drains are associated with this project" message.<br>

<figure><img src="/files/8JINmc0Cuo0856FxAdls" alt=""><figcaption></figcaption></figure>

***

### Step 2 — Choose Data to Drain

In the **Add Drain** wizard:

1. Select **Logs** *(Runtime, build and static logs)*.
2. Click **Next**.

> The other options — Traces, Speed Insights, and Web Analytics — are not required for Limy Analytics log ingestion.

<figure><img src="/files/OkgZNQHh95UVIS4LYxPN" alt=""><figcaption></figcaption></figure>

***

### Step 3 — Configure the Drain

Fill in the drain settings:

| Field           | Value                                                               |
| --------------- | ------------------------------------------------------------------- |
| **Drain Name**  | `LimyAnalytics` (or any name you prefer)                            |
| **Projects**    | Select **Specific Projects**, then choose your project              |
| **Sources**     | Check at least: `Static Files`, `Rewrites`, `Firewall`, `Redirects` |
| **Environment** | Select **Production** (optionally add Preview)                      |
| **Sampling**    | Leave empty to capture all logs                                     |

Click **Next** when done.

<figure><img src="/files/arusNw8V5RxTWbmwMGeu" alt=""><figcaption></figcaption></figure>

***

### Step 4 — Configure the Destination

On the **Configure the destination** step:

1. Stay on the **Custom Endpoint** tab.
2. Set the URL to:

```
https://stream.getlimy.ai
```

3. Set **Encoding** to `JSON`.
4. Enable the **Custom Headers** toggle and add:

```
x-api-key: <Your Limy Analytics Key>
```

Replace `<Your Limy Analytics Key>` with your actual API key from the Limy dashboard. Do not share this key publicly.

5. Click **Create Drain**.

<figure><img src="/files/0pHAL1CbWyIIS2cJapgB" alt=""><figcaption></figcaption></figure>

***

### Step 5 — Verify the Connection

Once saved, your drain will appear in the **Drains** list. Confirm it shows:

* **Name:** LimyAnalytics
* **Type:** Logs
* **Connected Projects:** your selected project
* A **blue checkmark** icon indicating the drain is active

<figure><img src="/files/N8AySa0J4lhmUJswdaPu" alt=""><figcaption></figcaption></figure>

***

### You're all set! 🎉

After setup, it may take **up to 10 minutes** before data appears in your Limy Analytics dashboard.

If you have any questions, reach out to **Limy Support**.


# AWS Cloudfront

This page explains how to set up Amazon CloudFront real-time logs delivery to Limy

{% stepper %}
{% step %}

### Step 1

In your AWS account, search for the Firehouse

<figure><img src="/files/LNQTBSu3huVcUvjzuLtw" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Step 2

Create delivery select “Direct PUT” as source and “HTTP Endpoint” as destination
{% endstep %}

{% step %}

### Step 3

1. Configure the HTTP endpoint with the following URL format:

```html
HTTPS Script provided to you by the Limy team 
```

2. For authentication, provide your Profound API key as the access key (we recommend using AWS Secrets Manager for secure key storage).

```
// Some code
```

2.

{% endstep %}

{% step %}

### Step 4

{% endstep %}

{% step %}

###

* Time and IP
  * `date` - Date when the request was completed
  * `time` - Time when the request was completed
  * `c-ip` - Client IP address
* Request Details
  * `cs-method` - HTTP request method
  * `cs(host)` - Requested host header
  * `cs-uri-stem` - Request URI path
  * `cs-uri-query` - Request query string
  * `cs(User-Agent)` - Client user agent
  * `cs(Referer)` - Request referrer
* Response Details
  * `sc-status` - HTTP response status
  * `sc-bytes` - Response size in bytes
  * `time-taken` - Request processing time
    {% endstep %}
    {% endstepper %}

### Additional Resources:

* [Amazon CloudFront Real-time Logs Documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/real-time-logs.html)
* [Amazon Kinesis Data Firehose Documentation](https://docs.aws.amazon.com/firehose/latest/dev/what-is-this-service.html)


# Cloudflare (Worker)

### Overview

This integration runs a Cloudflare Worker as middleware. On each request, the Worker extracts metadata and sends it to Agent Monitoring — without interfering with normal request flow.

{% hint style="info" %}
For additional details about Logpush, refer to Cloudflare’s official documentation.\
Cloudflare Worker is free up to 100K requests per day, for additional details, refer to [Cloudflare’s official documentation](https://developers.cloudflare.com/workers/platform/pricing/).
{% endhint %}

{% stepper %}
{% step %}
Open Terminal and install the following using npm

```
npm create cloudflare@2.37.4 -- log-shipping
```

Follow the instructions below

<figure><img src="/files/YjmlWT9eOhEyd3gRwKU6" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/aqcsZSp48Ryulc9nLktE" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/U5ydIjUGbULKneybC90M" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/w2RWDptQxIB4FIMUIY85" alt=""><figcaption></figcaption></figure>

Running the CLI creates the `log-shipping` project and handles dependency installation automatically.

**VERY IMPORTANT - DO NOT DEPLOY YET**

<figure><img src="/files/5YrGAWuHxQZ1uqFBfLF7" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

#### Configure your worker

Edit your `wrangler.jsonc` file to configure `LIMY_URL` environment variable to our endpoint and replace the `domain.com/*` placeholder should match your organization's domain. If your site is `https://www.domain.com`, use `domain.com/*` as the pattern and `domain.com` for zone\_name

```javascript
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "log-collector",
  "main": "src/index.ts",
  "compatibility_date": "2025-12-09",
  "observability": {
    "enabled": true
  },
  "route": {
    "pattern": "domain.com/*",
    "zone_name": "domain.com"
  },
  "vars": { "LIMY_URL": "https://stream.getlimy.ai" }
}
```

Then replace `src/index.ts` code within the code below :

{% hint style="danger" %}
If your unsure- contact us at <kbd><answers@limy.ai></kbd>
{% endhint %}

```tsx
/**
 * Cloudflare Worker for Log Collection
 *
 * This worker captures HTTP request/response data and forwards it to Limy's log collection API.
 * It runs as a middleware, meaning it doesn't interfere with the actual request handling.
 */

export interface Env {
     LIMY_KEY: string;
    LIMY_URL: string;
}

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

    // Get the original response
    const response = await fetch(request);

    // Clone the response so we can read it multiple times
        const responseClone = response.clone();
    
        ctx.waitUntil(handleRequest(request, responseClone, env));
        return response;
    }
} satisfies ExportedHandler<Env>;

async function handleRequest(request: Request, response: Response, env: Env) {
	const requestUrl = new URL(request.url);
	const cf = request.cf;
  
	const headerSize = Array.from(response.headers.entries())
	  .reduce((total, [key, value]) => total + key.length + value.length + 4, 0);
	
	const responseBody = await response.blob();
	const bodySize = responseBody.size;

    // Total bytes sent includes headers and body
    const totalBytesSent = headerSize + bodySize;

    const logData = {
		// Timing
		timestamp: Date.now(),
		
		// Request basics
		host: requestUrl.hostname,
		method: request.method,
		pathname: requestUrl.pathname,
		query_params: Object.fromEntries(requestUrl.searchParams),
		
		// Client info
		ip: request.headers.get('cf-connecting-ip'),
		userAgent: request.headers.get('user-agent'),
		referer: request.headers.get('referer'),
		acceptLanguage: request.headers.get('accept-language'),
		
		// Response info
		bytes: headerSize + bodySize,
		status: response.status,
		contentType: response.headers.get('content-type'),
		
		// Cloudflare request ID
		rayId: request.headers.get('cf-ray'),
		
		// Geographic (from cf object)
		country: cf?.country,
		city: cf?.city,
		region: cf?.region,
		regionCode: cf?.regionCode,
		continent: cf?.continent,
		postalCode: cf?.postalCode,
		latitude: cf?.latitude,
		longitude: cf?.longitude,
		timezone: cf?.timezone,
		
		// Network
		asn: cf?.asn,
		asOrganization: cf?.asOrganization,
		colo: cf?.colo,  // Cloudflare datacenter
		
		// Connection
		httpProtocol: cf?.httpProtocol,
		tlsVersion: cf?.tlsVersion,
		tlsCipher: cf?.tlsCipher,
    }

    await fetch(env.LIMY_URL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-API-Key': env.LIMY_KEY,
			'User-Agent': 'Cloudflare-Worker-Logs'
        },
        body: JSON.stringify([logData])
    }).catch(error => console.error('Failed to send logs:', error))
}
```

{% endstep %}

{% step %}

#### Login to Cloudflare

Deploy the worker using Wrangler CLI:

```typescript
//Login to Cloudflare
npx wrangler login
```

**Configure Limy API key**

Cloudflare Workers Secrets let you store sensitive values like API keys securely, outside of your code.

```typescript
npx wrangler secret put LIMY_KEY
```

{% endstep %}

{% step %}

#### Deploy your worker

```
npx wrangler deploy
```

<figure><img src="/files/aYpJIGcHxddezpZLL01v" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Need More Help? <a href="#additional-resources" id="additional-resources"></a>

* [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/)
* [Wrangler CLI Documentation](https://developers.cloudflare.com/workers/wrangler/)
* Contact <kbd><answers@limy.ai></kbd> for API-related questions


# Cloudflare (LogPush)

#### Overview <a href="#overview" id="overview"></a>

Use Cloudflare Logpush to stream request metadata into Agent Monitoring. Logpush is a Cloudflare Enterprise feature that supports forwarding logs to external destinations

For additional details about Logpush, refer to Cloudflare’s [official documentation](https://developers.cloudflare.com/logs/about/).

{% stepper %}
{% step %}
on your domain Cloudflare Dashboard, go to **Analytics & Logs -> Logpush**

<figure><img src="/files/LgrBHaanvNTua4Az3v4P" alt="" width="260"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Create a new Logpush job, select HTTP destination

<figure><img src="/files/Xhm8n4tQnIJ4Cv1ZZRLq" alt="" width="250"><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Set the destination URL to the Limy Analytics API endpoint

```
https://stream.getlimy.ai/?header_x-api-key=<YOUR_API_KEY>
```

{% endstep %}

{% step %}
**Select HTTP Requests**

<figure><img src="/files/vHsVY25o0mKBdCmWF6N3" alt="" width="289"><figcaption></figcaption></figure>

Under "If logs match", select `Filtered logs`. Add a filter on the `ClientRequestHost` field matching your domain.

<figure><img src="/files/SY1MosEuyJ5E7Ha2JKmp" alt=""><figcaption></figcaption></figure>

Select HTTP requests dataset, and choose the following fields to send:

* `ClientIP`
* `ClientRequestHost`
* `ClientRequestMethod`
* `ClientRequestReferer`
* `ClientRequestURI`
* `ClientRequestUserAgent`
* `EdgeStartTimestamp`
* `EdgeEndTimestamp`
* `EdgeResponseBytes`
* `EdgeResponseStatus`
  {% endstep %}
  {% endstepper %}

### Need More Help?

* Contact `answers@limy.ai` for any further questions


# Amazon CloudFront

### Overview <a href="#overview" id="overview"></a>

This integration uses Amazon Data Firehose to deliver CloudFront logs directly to our Agent Monitor. Amazon Data Firehose (formerly Kinesis Data Firehose) is a fully managed AWS service for loading streaming data into a variety of destinations, including HTTP endpoints.

For additional details, refer to the[ AWS documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/real-time-logs.html).

### Setup <a href="#configuration" id="configuration"></a>

{% stepper %}
{% step %}
Log into your AWS Console and head over to the Amazon Data Firehose section. This is where we'll create the pipeline that sends you to Limy.

<figure><img src="/files/nRUN33aIH6gtCRI5Eey4" alt=""><figcaption></figcaption></figure>

<br>
{% endstep %}

{% step %}
Click to create a new delivery stream and configure it with these settings:

* **Source:** Choose "Direct PUT"
* **Destination:** Select "HTTP Endpoint"\
  These settings tell Data Firehose to accept data directly and send it to an HTTP endpoint (which will be Limy's API).
* **Content encoding:** Choose "Disabled"

<figure><img src="/files/D5ijCCN37jZesgTaWzIe" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Now for the important part - connecting to Limy:

```
https://stream.getlimy.ai
```

**Authentication:** You'll need to provide your Limy key as the access key. We strongly recommend using AWS Secrets Manager to store this securely.

Create a secret in AWS Secrets Manager with this JSON structure:

```json
{ "api_key": "your_limy_api_key" }
```

{% hint style="warning" %}
I**mportant Settings:**\
**Backup S3 Bucket:**\
AWS requires you to specify an S3 bucket for failed deliveries. Create a new bucket or select an existing one - this is just for error cases.
{% endhint %}

<figure><img src="/files/VNd9WP4Q3GwaDJVy2gHQ" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Navigate to your CloudFront distribution in the AWS Console and click over to the "Logging" tab. Hit the "Add" button and choose "Kinesis Data Firehose" as your destination.

{% hint style="warning" %}
**Note:**

You might see it called "Kinesis Data Firehose" in CloudFront even though AWS renamed it to Amazon Data Firehose - they're the same thing.
{% endhint %}

<figure><img src="/files/0lxgE293e34S8VpqF4Cg" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
You'll be on the "Add standard logging destination" screen now. Pick the delivery stream you just created, then scroll down to "Additional settings - optional" and select these fields:

<figure><img src="/files/80oWGMcAinj6EHfttT7a" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/TN20RjGWDSGsMk7W4cDq" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/EBM7TVieKUAgZPN9jBAa" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/TN20RjGWDSGsMk7W4cDq" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/nLEABEF1UTldxNXJJm6V" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Under "Output format", select **JSON**. This ensures the data is structured properly for Limy to process.

<figure><img src="/files/w6mmEzoiinDMY2iz0x71" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Add the `LogDeliveryEnabled = true`  Tag

In order to enable the log delivery, the Kinesis firehose must have this tag included.

{% endstep %}
{% endstepper %}

### Need More Help?

* Contact `answers@limy.ai` for any further questions


# Akamai

This guide walks through configuring **Akamai DataStream 2** to forward CDN access logs to **Limy Agent Analytics.**

***

### Prerequisites

Before you begin, make sure you have:

* Access to **Akamai Control Center** with permission to create and activate DataStream configurations.
* Access to **Property Manager** for the property (CDN configuration) whose traffic you want to log.

***

### 1. Open DataStream

1. Sign in to **Akamai Control Center**.
2. From the hamburger menu (**☰**), go to **COMMON SERVICES > DataStream** to open the streams dashboard.

### 2. Create a new stream

1. Click **Create stream**.
2. Choose the log type **Delivery (CDN) products** — this captures CDN access logs.

### 3. Choose the source

Select the **property / CDN configuration** whose traffic you want to stream. This is the source of the access logs that will be delivered to Limy.

### 4. Select the data set fields

On the **Data Sets** step, select **all** of the following fields. These are mandatory for the Limy integration. The JSON key column is the field name as it appears in each delivered log record.

\*Is mandatory

<table><thead><tr><th>Field</th><th>JSON key</th><th data-hidden>#</th></tr></thead><tbody><tr><td>Request time *</td><td><code>reqTimeSec</code></td><td>1</td></tr><tr><td>Request ID *</td><td><code>reqId</code></td><td>2</td></tr><tr><td>Client IP *</td><td><code>cliIP</code></td><td>4</td></tr><tr><td>HTTP status code *</td><td><code>statusCode</code></td><td>5</td></tr><tr><td>Request method *</td><td><code>reqMethod</code></td><td>6</td></tr><tr><td>Request path *</td><td><code>reqPath</code></td><td>7</td></tr><tr><td>Request host *</td><td><code>reqHost</code></td><td>9</td></tr><tr><td>User-Agent *</td><td><code>UA</code></td><td>11</td></tr><tr><td>Total bytes *</td><td><code>totalBytes</code></td><td>12</td></tr><tr><td>Response Content-Length *</td><td><code>rspContentLen</code></td><td>13</td></tr><tr><td>Response Content-Type *</td><td><code>rspContentType</code></td><td>14</td></tr><tr><td>Accept-Language</td><td><code>accLang</code></td><td>15</td></tr><tr><td>Cookie</td><td><code>cookie</code></td><td>16</td></tr><tr><td>Range</td><td><code>range</code></td><td>17</td></tr><tr><td>Referer</td><td><code>referer</code></td><td>18</td></tr><tr><td>X-Forwarded-For</td><td><code>xForwardedFor</code></td><td>19</td></tr><tr><td>Max age</td><td><code>maxAgeSec</code></td><td>20</td></tr><tr><td>Request end time</td><td><code>reqEndTimeMSec</code></td><td>21</td></tr><tr><td>Turn around time</td><td><code>turnAroundTimeMSec</code></td><td>22</td></tr><tr><td>Transfer time</td><td><code>transferTimeMSec</code></td><td>23</td></tr><tr><td>Country/Region</td><td><code>country</code></td><td>24</td></tr><tr><td>City</td><td><code>city</code></td><td>25</td></tr><tr><td>State</td><td><code>state</code></td><td>26</td></tr><tr><td>Bytes</td><td><code>bytes</code></td><td></td></tr><tr><td>Request port</td><td><code>reqPort</code></td><td></td></tr><tr><td>Protocol type</td><td><code>proto</code></td><td></td></tr></tbody></table>

### 5. Set the log format

Set **Log format** to **JSON**.

### 6. Configure the destination

On the **Destination** tab:

1. **Destination:** select **Custom HTTPS**.
2. **Name:** enter a human-readable label (e.g. `Limy Agent Analytics`).
3. **Endpoint URL:** `https://stream.getlimy.ai/`
4. **Authentication:** select **Basic**.
   1. In username, enter the org name (for example: YourCompanyName)
   2. In password, enter your Limy Key (lmy\_\*\*\*\*\*\*\*\*\*). The key can be found in Agent Monitor page in your Limy Dashboard.
5. **Do not - Compress data:** **leave button unchecked** — do **not** compress data.
6. Click **Validate & Save**.

### 7. Review and activate the stream

1. Review the configuration on the summary step.
2. Click **Activate** to deploy the stream.

### 8. Enable the DataStream behavior in Property Manager

A stream only collects data once the **DataStream behavior** is enabled on the property:

1. Go to **☰ > CDN > Properties** and open your property.
2. Open the configuration **Version** you want to edit.
3. Click **Edit New Version**
4. In the default rule, under Log Delivery, Scroll to **Behaviors**
5. Turn on Log User-Agent Header. (And Referrer Header)
6. **Save**, then activate the property version on the **production** network.

***

### Verify the integration

* After activation, allow up to **\~120 minutes** for logs to begin arriving in Limy Dashboard.
* Confirm data is appearing in the Limy Agent Analytics dashboard.
* If nothing arrives, check that the stream is **active**, the **DataStream behavior is enabled** on the property, and the property version is **active on production**.

{% hint style="info" %}
**Note:** Akamai may rename fields, relabel buttons, or rearrange this flow over time. If the exact wording or screen layout differs from this guide, the underlying logic is unchanged — create a stream, select the data set fields, set the format to JSON, point it at the Limy custom HTTPS endpoint without compression, and enable the DataStream behavior on your property.
{% endhint %}


# Fastly

### Overview

This setup leverages Fastly’s custom HTTPS endpoint to send real-time log data directly to the Agent Monitor API.\
To learn more, see the official Fastly documentation.

{% stepper %}
{% step %}
Navigate to the Service configuration tab.

<figure><img src="/files/vYkwfXrMn7oClsF7STb1" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Navigate to "Logging", find HTTPS and click "Create Endpoint"

<figure><img src="/files/RBEzs1PFCiJEWv8Be8QR" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Configure HTTPS Endpoint with the following info:

**Name: Limy Analytics**

**Placement:** Default

**URL:** `https://stream.getlimy.ai`

**Maximum logs & Maximum bytes:** Optional\
\
**Log format**: Paste the following log format. A different API might be rejected. Please contact us for any changes at `answers@limy.ai`

```json
{
    "timestamp": "%{strftime(\{"%Y-%m-%dT%H:%M:%S%z"\}, time.start)}V",
    "client_ip": "%{req.http.Fastly-Client-IP}V",
    "geo_country": "%{client.geo.country_name}V",
    "geo_city": "%{client.geo.city}V",
    "host": "%{if(req.http.Fastly-Orig-Host, req.http.Fastly-Orig-Host, req.http.Host)}V",
    "url": "%{json.escape(req.url)}V",
    "request_method": "%{json.escape(req.method)}V",
    "request_protocol": "%{json.escape(req.proto)}V",
    "request_referer": "%{json.escape(req.http.referer)}V",
    "request_user_agent": "%{json.escape(req.http.User-Agent)}V",
    "response_state": "%{json.escape(fastly_info.state)}V",
    "response_status": %{resp.status}V,
    "response_reason": %{if(resp.response, "%22"+json.escape(resp.response)+"%22", "null")}V,
    "response_body_size": %{resp.body_bytes_written}V,
    "fastly_server": "%{json.escape(server.identity)}V",
    "fastly_is_edge": %{if(fastly.ff.visits_this_service == 0, "true", "false")}V
  }
```

<figure><img src="/files/decHMgoi29bxlmFtMgAn" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Open " Advanced options" and configure as following:

* **Content type** - `application/json`
* **Custom header name** - `X-API-Key`
* **Custom header value** - Your API key `lmy_XXXXX`
* **Method** - `POST`
* **JSON log entry format** - `Array of JSON`
* **Select a log line format** - `Blank`

<figure><img src="/files/3YGU1TZaqOB2DAwAWbcB" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Need More Help?

* Contact `answers@limy.ai` for any further questions


# Netlify

### **Overview**

Netlify’s General HTTP Endpoint Log Drains allow you to stream traffic logs to external services for advanced monitoring and analysis.

{% hint style="info" %}
This integration requires a Netlify Enterprise plan — Log Drains are not available on lower tiers.
{% endhint %}

### Setup

{% stepper %}
{% step %}
Go to your project in the Netlify dashboard. On the left, click **Logs > Log Drains**

<figure><img src="/files/a1SMh62WrYq7vJsJ3qJG" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Click **Enable Log Drain** and select **General HTTP Endpoint**

<figure><img src="/files/1SSt2N2i6qzMX0AoV3kF" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
Uncheck:

* **Function logs**
* **Edge function logs**
* **Deploy logs**
* **WAF logs**

Choose **Traffic Logs** only for the log drain.

* URL: `https://stream.getlimy.ai`
* Authorization Header: `Bearer lmy_xxx`

**Log Drain Format** should be **JSON**

<figure><img src="/files/uQ8FYSPmgO6tOCQOkjPi" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}
**Connect**

{% hint style="danger" %}
If you get the following error:

`incorrectly configured GetURL, validation request returns status code 400`

Make sure you have prefixed your token with `Bearer`
{% endhint %}

<figure><img src="/files/gPX8WpHVakCL9SwoRDJI" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### Need More Help? <a href="#additional-resources" id="additional-resources"></a>

* Contact <kbd><answers@limy.ai></kbd> for any further question


# Netlify (Free Plan)

Netlify Access Log Forwarding with edge functions

Send access logs from any Netlify site to an HTTP endpoint using Edge Functions, Netlify Blobs, and a scheduled function. Logs are batched and sent every minute.

Works on **all Netlify plans** including the free tier.

Outbound requests to your log endpoint are authenticated via an `x-api-key` header. The key is stored as a Netlify secret env var (`LOG_API_KEY`) and never shipped to the browser — client logs go through a same-origin proxy function that injects the key server-side.

### Architecture

```
Request → Edge Function → Netlify Blobs (buffer)
                              ↓
         Scheduled Function (every 1 min) → batch POST (x-api-key) → your endpoint
                              ↓
         Cleanup: deletes sent entries from Blobs

Client SPA navigation → useAccessLog hook → /api/access-log proxy fn (adds x-api-key) → your endpoint
```

### Setup

#### 1. Create the Edge Function

Create `netlify/edge-functions/access-log.ts`:

```typescript
import { getStore } from "@netlify/blobs";
import type { Config, Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  const startTime = Date.now();
  const response = await context.next();
  const duration = Date.now() - startTime;
  const url = new URL(request.url);

  const logEntry = {
    timestamp: new Date().toISOString(),
    method: request.method,
    url: request.url,
    path: url.pathname,
    query: url.search,
    status_code: response.status,
    duration_ms: duration,
    client_ip: context.ip,
    user_agent: request.headers.get("user-agent"),
    referer: request.headers.get("referer"),
    content_type: response.headers.get("content-type"),
    country: context.geo.country?.code,
    city: context.geo.city,
    region: context.server.region,
    request_id: context.requestId,
    site: context.site.name,
    deploy_id: context.deploy.id,
  };

  const key = `${Date.now()}-${crypto.randomUUID()}`;

  context.waitUntil(
    getStore("access-logs")
      .setJSON(key, logEntry)
      .catch((err) => console.error("Failed to buffer access log:", err))
  );

  return response;
};

export const config: Config = {
  path: "/*",
};
```

> To exclude static assets, add `excludedPath` to the config:
>
> ```typescript
> export const config: Config = {
>   path: "/*",
>   excludedPath: ["/*.css", "/*.js", "/*.png", "/*.jpg", "/*.svg", "/*.woff2", "/*.ico"],
> };
> ```

#### 2. Create the Scheduled Flush Function

Create `netlify/functions/flush-access-logs.mts`:

```typescript
import { getStore } from "@netlify/blobs";
import type { Config } from "@netlify/functions";

const LOG_ENDPOINT = "https://stream.getlimy.ai";

export default async () => {
  const store = getStore("access-logs");
  const { blobs } = await store.list();

  if (blobs.length === 0) return;

  const entries = await Promise.all(
    blobs.map(async (blob) => {
      const data = await store.get(blob.key, { type: "json" });
      return { key: blob.key, data };
    })
  );

  const logEntries = entries
    .filter((e) => e.data !== null)
    .map((e) => e.data);

  if (logEntries.length === 0) return;

  const apiKey = process.env.LOG_API_KEY;
  if (!apiKey) {
    console.error("LOG_API_KEY env var not set; skipping flush");
    return;
  }

  const response = await fetch(LOG_ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "User-Agent": "LimyAnalyticsNetlifyAccessLogs",
      "x-api-key": apiKey,
    },
    body: JSON.stringify(logEntries),
  });

  if (!response.ok) {
    console.error(`Failed to flush logs: ${response.status} ${response.statusText}`);
    return;
  }

  // Only delete after successful send
  await Promise.all(blobs.map((blob) => store.delete(blob.key)));

  console.log(`Flushed ${logEntries.length} access log entries`);
};

export const config: Config = {
  schedule: "* * * * *", // Every minute
};
```

#### 3. Create the Client Log Proxy Function

The browser cannot ship the API key (it would leak in the bundle) and `navigator.sendBeacon` cannot set custom headers. Solution: a same-origin proxy function that adds `x-api-key` server-side.

Create `netlify/functions/log-proxy.mts`:

```typescript
import type { Context } from "@netlify/functions";

const LOG_ENDPOINT = "https://stream.getlimy.ai";

export default async (request: Request, _context: Context) => {
  if (request.method !== "POST") {
    return new Response("Method Not Allowed", { status: 405 });
  }

  const apiKey = process.env.LOG_API_KEY;
  if (!apiKey) {
    console.error("LOG_API_KEY env var not set");
    return new Response("Server misconfigured", { status: 500 });
  }

  const body = await request.text();

  const response = await fetch(LOG_ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "User-Agent": "LimyAnalyticsNetlifyAccessLogs",
      "x-api-key": apiKey,
    },
    body,
  });

  if (!response.ok) {
    console.error(`Log forward failed: ${response.status} ${response.statusText}`);
    return new Response("Upstream error", { status: 502 });
  }

  return new Response(null, { status: 204 });
};

export const config = {
  path: "/api/access-log",
};
```

#### 4. Update `netlify.toml`

Add the edge function declaration to your `netlify.toml`:

```toml
[[edge_functions]]
  function = "access-log"
  path = "/*"
```

#### 5. Install Dependencies

```bash
npm install @netlify/blobs @netlify/functions
```

#### 6. Configure the API Key Secret

Set `LOG_API_KEY` as a Netlify secret env var. Get your API Key from your Limy dashboard

**CLI:**

```bash
netlify env:set LOG_API_KEY "your-secret-key" --secret
```

#### 7. Deploy

Push to your connected git repo. Netlify deploys automatically. Make sure `LOG_API_KEY` is set in the target deploy context (production / deploy previews / branch).

### Configuration

| Setting                 | Where                                          | Default                          |
| ----------------------- | ---------------------------------------------- | -------------------------------- |
| Log endpoint URL        | `flush-access-logs.mts` + `log-proxy.mts`      | — (required)                     |
| API key                 | Netlify env var `LOG_API_KEY` (mark as secret) | — (required)                     |
| Flush interval (server) | `flush-access-logs.mts` → `config.schedule`    | Every minute (`* * * * *`)       |
| Flush interval (client) | `useAccessLog.ts` → `FLUSH_INTERVAL_MS`        | 60,000 ms                        |
| Client batch size       | `useAccessLog.ts` → `BATCH_SIZE`               | 100 events                       |
| Excluded paths          | `access-log.ts` → `config.excludedPath`        | None (all paths logged)          |
| Custom User-Agent       | `flush-access-logs.mts` headers                | `LimyAnalyticsNetlifyAccessLogs` |

### Payload Format

Your endpoint receives a JSON array of log entries:

```json
[
  {
    "timestamp": "2026-04-01T12:00:00.000Z",
    "method": "GET",
    "url": "https://yoursite.netlify.app/page",
    "path": "/page",
    "query": "",
    "status_code": 200,
    "duration_ms": 45,
    "client_ip": "1.2.3.4",
    "user_agent": "Mozilla/5.0...",
    "referer": null,
    "content_type": "text/html",
    "country": "US",
    "city": "San Francisco",
    "region": "us-west-2",
    "request_id": "...",
    "site": "your-site-name",
    "deploy_id": "..."
  }
]
```

Client-side navigation entries include `"type": "client_navigation"` and do not contain server-side fields like `client_ip`, `country`, or `region`.

### Free Tier Limits

| Resource                     | Limit           |
| ---------------------------- | --------------- |
| Edge Function invocations    | 3M / month      |
| Edge Function CPU time       | 50 ms / request |
| Scheduled Function execution | 30 seconds      |
| Netlify Blobs storage        | 1 GB            |


# Google Cloud CDN

How to integrate Google Cloud CDN into Limy

## Forward GCP Cloud CDN Access Logs to Limy Analytics

This guide walks you through forwarding Google Cloud CDN access logs to [Limy Analytics](https://limy.ai/) in near real-time.

```
Cloud CDN → Cloud Logging → Log Sink → Pub/Sub Topic → Push Subscription → Limy Analytics
```

Logs flow from Cloud CDN into Cloud Logging automatically. We create a pipeline that routes them to a Pub/Sub topic, which pushes each log entry to Limy Analytics ingestion endpoint within seconds.

***

### Prerequisites

* A GCP project with Cloud CDN and a load balancer already configured
* Cloud Logging API enabled
* Pub/Sub API enabled
* A Limy Analytics account

***

### Step 1: Create a Pub/Sub Topic

1. Open the [Google Cloud Console](https://console.cloud.google.com/)
2. Navigate to **Pub/Sub → Topics**
3. Click **Create Topic**
4. Set the **Topic ID** to - `limy-analytics-topic`
5. Uncheck 'Add a default subscription'
6. Click **Create**

<figure><img src="/files/hi9fBz4KR6O8MEqX0psG" alt=""><figcaption></figcaption></figure>

***

### Step 2: Create a Log Sink

The log sink routes matching log entries from Cloud Logging into the Pub/Sub topic.

1. Navigate to **Logging → Log Router**
2. Click **Create Sink**
3. Fill in the following:

#### Sink Details

| Field     | Value                 |
| --------- | --------------------- |
| Sink name | `limy-analytics-sink` |

Click **Next**.

#### Sink Destination

| Field               | Value                         |
| ------------------- | ----------------------------- |
| Sink service        | **Cloud Pub/Sub topic**       |
| Cloud Pub/Sub topic | Select `limy-analytics-topic` |

Click **Next**.

#### Choose Logs to Include

In the **inclusion filter** field, enter:

**For all load balancer logs:**

```
resource.type="http_load_balancer"
```

**For a specific load balancer**, add a filter on the forwarding rule or URL map name:

```
resource.type="http_load_balancer"
resource.labels.forwarding_rule_name="YOUR_FORWARDING_RULE_NAME"
```

or

```
resource.type="http_load_balancer"
resource.labels.url_map_name="YOUR_URL_MAP_NAME"
```

> **Tip:** To find the exact resource label values for your load balancer, go to **Logs Explorer**, filter by `resource.type="http_load_balancer"`, expand any log entry, and look under `resource.labels`.

**For multiple load balancers**, you can combine filters:

```
resource.type="http_load_balancer"
AND (
  (resource.labels.forwarding_rule_name="MY-FORWARDING-RULE-NAME"
  AND resource.labels.url_map_name="MY-URL-MAP-NAME")
  OR
  (resource.labels.forwarding_rule_name="MY-FORWARDING-RULE-NAME-2"
  AND resource.labels.url_map_name="MY-URL-MAP-NAME-2")
)
```

<figure><img src="/files/QQvPJjAAsYNGlnEPuhZM" alt=""><figcaption></figcaption></figure>

Click **Next**.

#### Choose Logs to Exclude (Optional)

Skip this step or add exclusion filters if needed.

4. Click **Create Sink**

***

### Step 3: Create a Push Subscription

The push subscription sends each message to Limy Analytics automatically.

1. Navigate to **Pub/Sub → Topics →** `limy-analytics-topic`
2. Click **Create Subscription**
3. Fill in the following:

| Field           | Value                       |
| --------------- | --------------------------- |
| Subscription ID | `limy-analytics-push`       |
| Delivery type   | **Push**                    |
| Endpoint URL    | <https://stream.getlimy.ai> |

<figure><img src="/files/kr5mdDwVk0iv5isgFcNu" alt=""><figcaption></figcaption></figure>

#### Add a Transform

The transform injects your Limy Analytics API key into each message and removes unused fields to reduce log export volume and improve performance.

7. Click **Add a Transform**
8. Enter `TransformGcpCdnToLimyFormat` as the function name
9. Paste the following code into the function body:

```javascript
function TransformGcpCdnToLimyFormat(message, metadata) {

    const data = JSON.parse(message.data);
    data['api_key'] = 'YOUR_LIMY_API_KEY';

    // remove unused fields
    delete data['insertId'];
    delete data['jsonPayload'];
    delete data['logName'];
    delete data['receiveTimestamp'];
    delete data['resource'];
    delete data['severity'];
    delete data['spanId'];
    delete data['trace'];

    message.data = JSON.stringify(data);

    return message;

}
```

> **Important:** Replace `YOUR_LIMY_API_KEY` with your actual Limy Analytics API key.

<figure><img src="/files/ojkvC3FFDInLJItXGf3u" alt=""><figcaption></figcaption></figure>

10. Click **Validate** to ensure the function is valid
11. Click **Create**


# Nginx

Nginx Access Log Streaming Setup Guide

This guide explains how to configure your existing Nginx server to stream access logs in JSON format to Limy's log ingestion endpoint.

### Overview

You will:

1. Update your Nginx configuration to output access logs in JSON format
2. Install and configure Fluent Bit to stream logs to the Limy endpoint

Choose the setup method that matches your environment:

* Bare Metal / VM Setup
* Kubernetes Setup

***

## Bare Metal / VM Setup

### Step 1: Configure Nginx JSON Access Logs

Add the following `log_format` directive to your Nginx configuration's `http` block (typically in `/etc/nginx/nginx.conf`):

```nginx
http {
    # JSON log format with all available fields
    log_format json_combined escape=json '{'
        '"log_type":"nginx_access",'
        '"timestamp":"$time_iso8601",'
        '"time_local":"$time_local",'
        '"msec":"$msec",'

        '"client_ip":"$remote_addr",'
        '"client_port":"$remote_port",'
        '"remote_user":"$remote_user",'

        '"request":"$request",'
        '"request_method":"$request_method",'
        '"request_uri":"$request_uri",'
        '"uri":"$uri",'
        '"args":"$args",'
        '"scheme":"$scheme",'
        '"server_protocol":"$server_protocol",'
        '"request_length":$request_length,'
        '"request_time":$request_time,'

        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"bytes_sent":$bytes_sent,'

        '"host":"$host",'
        '"server_addr":"$server_addr",'
        '"server_port":"$server_port",'
        '"server_name":"$server_name",'
        '"hostname":"$hostname",'
        '"nginx_version":"$nginx_version",'
        '"pid":"$pid",'

        '"connection":"$connection",'
        '"connection_requests":"$connection_requests",'
        '"pipe":"$pipe",'

        '"http_host":"$http_host",'
        '"http_user_agent":"$http_user_agent",'
        '"http_referer":"$http_referer",'
        '"http_accept":"$http_accept",'
        '"http_accept_encoding":"$http_accept_encoding",'
        '"http_accept_language":"$http_accept_language",'
        '"http_content_type":"$http_content_type",'
        '"http_content_length":"$http_content_length",'
        '"http_x_forwarded_for":"$http_x_forwarded_for",'
        '"http_x_forwarded_proto":"$http_x_forwarded_proto",'
        '"http_x_real_ip":"$http_x_real_ip",'
        '"http_x_request_id":"$http_x_request_id",'

        '"sent_http_content_type":"$sent_http_content_type",'
        '"sent_http_content_length":"$sent_http_content_length",'

        '"upstream_addr":"$upstream_addr",'
        '"upstream_status":"$upstream_status",'
        '"upstream_response_time":"$upstream_response_time",'
        '"upstream_response_length":"$upstream_response_length",'
        '"upstream_connect_time":"$upstream_connect_time",'
        '"upstream_header_time":"$upstream_header_time",'
        '"upstream_cache_status":"$upstream_cache_status",'

        '"ssl_protocol":"$ssl_protocol",'
        '"ssl_cipher":"$ssl_cipher",'
        '"ssl_session_reused":"$ssl_session_reused",'
        '"ssl_server_name":"$ssl_server_name",'

        '"gzip_ratio":"$gzip_ratio"'
    '}';

    # Use the JSON format for access logs
    access_log /var/log/nginx/access.log json_combined;

    # ... rest of your existing configuration
}
```

#### Apply the Nginx Configuration

```bash
# Test the configuration
sudo nginx -t

# Reload Nginx to apply changes
sudo systemctl reload nginx
```

***

### Step 2: Install Fluent Bit

#### Option A: Debian/Ubuntu

```bash
# Add the Fluent Bit repository
curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh

# Start and enable the service
sudo systemctl start fluent-bit
sudo systemctl enable fluent-bit
```

#### Option B: RHEL/CentOS/Amazon Linux

```bash
# Add the Fluent Bit repository
curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh

# Start and enable the service
sudo systemctl start fluent-bit
sudo systemctl enable fluent-bit
```

#### Option C: Docker

```bash
docker run -d \
  --name fluent-bit \
  -v /var/log/nginx:/var/log/nginx:ro \
  -v /path/to/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro \
  -v /path/to/parsers.conf:/fluent-bit/etc/parsers.conf:ro \
  fluent/fluent-bit:latest
```

***

### Step 3: Configure Fluent Bit

Create the Fluent Bit configuration file at `/etc/fluent-bit/fluent-bit.conf`:

```ini
[SERVICE]
    Flush         5
    Log_Level     info
    Daemon        off
    Parsers_File  parsers.conf
    HTTP_Server   On
    HTTP_Listen   0.0.0.0
    HTTP_Port     2020

[INPUT]
    Name              tail
    Path              /var/log/nginx/access.log
    Parser            nginx_json
    Tag               nginx.access
    Refresh_Interval  5
    Mem_Buf_Limit     10MB
    Skip_Long_Lines   On
    DB                /var/lib/fluent-bit/nginx.db

[OUTPUT]
    Name              http
    Match             nginx.*
    Host              stream.getlimy.ai
    Port              443
    URI               /
    Format            json
    TLS               On
    Header            X-API-Key YOUR_API_KEY_HERE
```

Create the parsers file at `/etc/fluent-bit/parsers.conf`:

```ini
[PARSER]
    Name        nginx_json
    Format      json
    Time_Key    timestamp
    Time_Format %Y-%m-%dT%H:%M:%S%z
    Time_Keep   On
```

#### Replace the API Key

Replace `YOUR_API_KEY_HERE` in the configuration with the API key provided to you by Limy.

***

### Step 4: Start Fluent Bit

```bash
# Restart Fluent Bit to apply the new configuration
sudo systemctl restart fluent-bit

# Verify it's running
sudo systemctl status fluent-bit

# Check logs for any errors
sudo journalctl -u fluent-bit -f
```

***

## Kubernetes Setup

### Step 1: Update Nginx ConfigMap

If you're using Nginx in Kubernetes, update your Nginx ConfigMap to include the JSON log format. The key change is logging to `/dev/stdout` so Kubernetes can capture the logs.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  namespace: <your-namespace>
data:
  nginx.conf: |
    user nginx;
    worker_processes auto;
    error_log /dev/stderr warn;
    pid /var/run/nginx.pid;

    events {
        worker_connections 1024;
    }

    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        # JSON log format with all available fields
        log_format json_combined escape=json '{'
            '"log_type":"nginx_access",'
            '"timestamp":"$time_iso8601",'
            '"time_local":"$time_local",'
            '"msec":"$msec",'

            '"client_ip":"$remote_addr",'
            '"client_port":"$remote_port",'
            '"remote_user":"$remote_user",'

            '"request":"$request",'
            '"request_method":"$request_method",'
            '"request_uri":"$request_uri",'
            '"uri":"$uri",'
            '"args":"$args",'
            '"scheme":"$scheme",'
            '"server_protocol":"$server_protocol",'
            '"request_length":$request_length,'
            '"request_time":$request_time,'

            '"status":$status,'
            '"body_bytes_sent":$body_bytes_sent,'
            '"bytes_sent":$bytes_sent,'

            '"host":"$host",'
            '"server_addr":"$server_addr",'
            '"server_port":"$server_port",'
            '"server_name":"$server_name",'
            '"hostname":"$hostname",'
            '"nginx_version":"$nginx_version",'
            '"pid":"$pid",'

            '"connection":"$connection",'
            '"connection_requests":"$connection_requests",'
            '"pipe":"$pipe",'

            '"http_host":"$http_host",'
            '"http_user_agent":"$http_user_agent",'
            '"http_referer":"$http_referer",'
            '"http_accept":"$http_accept",'
            '"http_accept_encoding":"$http_accept_encoding",'
            '"http_accept_language":"$http_accept_language",'
            '"http_content_type":"$http_content_type",'
            '"http_content_length":"$http_content_length",'
            '"http_x_forwarded_for":"$http_x_forwarded_for",'
            '"http_x_forwarded_proto":"$http_x_forwarded_proto",'
            '"http_x_real_ip":"$http_x_real_ip",'
            '"http_x_request_id":"$http_x_request_id",'

            '"sent_http_content_type":"$sent_http_content_type",'
            '"sent_http_content_length":"$sent_http_content_length",'

            '"upstream_addr":"$upstream_addr",'
            '"upstream_status":"$upstream_status",'
            '"upstream_response_time":"$upstream_response_time",'
            '"upstream_response_length":"$upstream_response_length",'
            '"upstream_connect_time":"$upstream_connect_time",'
            '"upstream_header_time":"$upstream_header_time",'
            '"upstream_cache_status":"$upstream_cache_status",'

            '"ssl_protocol":"$ssl_protocol",'
            '"ssl_cipher":"$ssl_cipher",'
            '"ssl_session_reused":"$ssl_session_reused",'
            '"ssl_server_name":"$ssl_server_name",'

            '"gzip_ratio":"$gzip_ratio"'
        '}';

        # Log to stdout for Kubernetes log collection
        access_log /dev/stdout json_combined;

        sendfile on;
        keepalive_timeout 65;

        # Include your server blocks or other config files
        include /etc/nginx/conf.d/*.conf;
    }
```

Mount this ConfigMap in your Nginx deployment:

```yaml
spec:
  containers:
    - name: nginx
      image: nginx:latest
      volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
  volumes:
    - name: nginx-config
      configMap:
        name: nginx-config
```

***

### Step 2: Create Fluent Bit Secret for API Key

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: limy-api-key
  namespace: <your-namespace>
type: Opaque
stringData:
  api-key: "YOUR_API_KEY_HERE"
```

Apply the secret:

```bash
kubectl apply -f limy-secret.yaml
```

***

### Step 3: Deploy Fluent Bit ConfigMap

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: <your-namespace>
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         5
        Log_Level     info
        Daemon        off
        Parsers_File  parsers.conf
        HTTP_Server   On
        HTTP_Listen   0.0.0.0
        HTTP_Port     2020

    [INPUT]
        Name              tail
        Path              /var/log/containers/*<your-nginx-app-label>*.log
        Parser            docker
        Tag               nginx.access
        Refresh_Interval  5
        Mem_Buf_Limit     10MB
        Skip_Long_Lines   On
        DB                /tmp/flb_nginx.db

    # Filter to only include nginx access logs (not startup messages)
    [FILTER]
        Name          grep
        Match         nginx.*
        Regex         log log_type

    # Parse the JSON from the log field
    [FILTER]
        Name          parser
        Match         nginx.*
        Key_Name      log
        Parser        nginx_json
        Reserve_Data  Off

    [OUTPUT]
        Name              http
        Match             nginx.*
        Host              stream.getlimy.ai
        Port              443
        URI               /
        Format            json
        TLS               On
        Header            X-API-Key ${LIMY_API_KEY}

  parsers.conf: |
    [PARSER]
        Name        docker
        Format      json
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L
        Time_Keep   On

    [PARSER]
        Name        nginx_json
        Format      json
        Time_Key    timestamp
        Time_Format %Y-%m-%dT%H:%M:%S%z
        Time_Keep   On
```

> **Note:** Replace `<your-nginx-app-label>` in the `Path` with your Nginx pod name or label pattern (e.g., `*nginx*` or `*my-app-nginx*`).

***

### Step 4: Deploy Fluent Bit DaemonSet

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: <your-namespace>
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluent-bit-read
rules:
  - apiGroups: [""]
    resources:
      - namespaces
      - pods
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluent-bit-read
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluent-bit-read
subjects:
  - kind: ServiceAccount
    name: fluent-bit
    namespace: <your-namespace>
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: <your-namespace>
  labels:
    app: fluent-bit
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      serviceAccountName: fluent-bit
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:latest
          ports:
            - containerPort: 2020
          env:
            - name: LIMY_API_KEY
              valueFrom:
                secretKeyRef:
                  name: limy-api-key
                  key: api-key
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: fluent-bit-config
              mountPath: /fluent-bit/etc/
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 100Mi
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
        - name: fluent-bit-config
          configMap:
            name: fluent-bit-config
```

***

### Step 5: Apply Kubernetes Resources

```bash
# Apply all resources
kubectl apply -f nginx-configmap.yaml
kubectl apply -f limy-secret.yaml
kubectl apply -f fluent-bit-configmap.yaml
kubectl apply -f fluent-bit-daemonset.yaml

# Restart Nginx pods to pick up the new config
kubectl rollout restart deployment/<your-nginx-deployment> -n <your-namespace>

# Verify Fluent Bit is running
kubectl get pods -l app=fluent-bit -n <your-namespace>

# Check Fluent Bit logs
kubectl logs -l app=fluent-bit -n <your-namespace> -f
```

***

### Alternative: Using Helm

If you prefer Helm, you can install Fluent Bit using the official chart:

```bash
helm repo add fluent https://fluent.github.io/helm-charts
helm repo update

helm install fluent-bit fluent/fluent-bit \
  --namespace <your-namespace> \
  --set config.inputs="[INPUT]\n    Name tail\n    Path /var/log/containers/*nginx*.log\n    Parser docker\n    Tag nginx.access" \
  --set config.outputs="[OUTPUT]\n    Name http\n    Match nginx.*\n    Host stream.getlimy.ai\n    Port 443\n    URI /\n    Format json\n    TLS On\n    Header X-API-Key YOUR_API_KEY_HERE"
```

For more complex configurations, create a `values.yaml` file and use:

```bash
helm install fluent-bit fluent/fluent-bit -f values.yaml -n <your-namespace>
```

***

## Verification

### 1. Verify Nginx is Logging in JSON Format

**Bare Metal / VM:**

```bash
tail -5 /var/log/nginx/access.log
```

**Kubernetes:**

```bash
kubectl logs <nginx-pod-name> -n <your-namespace> | head -5
```

You should see JSON-formatted log entries like:

```json
{"log_type":"nginx_access","timestamp":"2025-12-16T15:30:45+00:00","client_ip":"192.168.1.100","request_method":"GET","request_uri":"/api/health","status":200,...}
```

### 2. Verify Fluent Bit is Running

**Bare Metal / VM:**

```bash
curl http://localhost:2020/api/v1/metrics
```

**Kubernetes:**

```bash
kubectl port-forward <fluent-bit-pod> 2020:2020 -n <your-namespace>
# In another terminal:
curl http://localhost:2020/api/v1/metrics
```

### 3. Generate Test Traffic

```bash
# Make a test request to your Nginx server
curl -I http://<your-nginx-host>/
```

***

## Troubleshooting

### Nginx Configuration Errors

```bash
# Test configuration syntax
sudo nginx -t  # Bare metal
kubectl exec <nginx-pod> -- nginx -t  # Kubernetes

# Check error log
sudo tail -f /var/log/nginx/error.log  # Bare metal
kubectl logs <nginx-pod> -n <your-namespace>  # Kubernetes
```

### Fluent Bit Issues

**Bare Metal / VM:**

```bash
sudo journalctl -u fluent-bit -f
fluent-bit -c /etc/fluent-bit/fluent-bit.conf --dry-run
```

**Kubernetes:**

```bash
kubectl logs -l app=fluent-bit -n <your-namespace> -f
kubectl describe pod -l app=fluent-bit -n <your-namespace>
```

### Common Issues

| Issue                    | Solution                                                                                       |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| Logs not appearing       | Ensure `access_log` path matches Fluent Bit `Path`. In K8s, ensure Nginx logs to `/dev/stdout` |
| Permission denied        | Run Fluent Bit with appropriate permissions. In K8s, check RBAC and volume mounts              |
| Connection refused       | Verify network connectivity to `stream.getlimy.ai`. Check network policies in K8s              |
| 401 Unauthorized         | Check that `X-API-Key` header value is correct                                                 |
| No logs in K8s           | Verify the log path pattern matches your pod names. Check `kubectl logs` output directly       |
| Startup messages in logs | Ensure the `grep` filter is configured to filter by `log_type`                                 |

***

## Log Fields Reference

| Field                  | Description                         |
| ---------------------- | ----------------------------------- |
| `timestamp`            | ISO 8601 timestamp                  |
| `client_ip`            | Client IP address                   |
| `client_port`          | Client port                         |
| `remote_user`          | Authenticated username (Basic Auth) |
| `request`              | Full request line                   |
| `request_method`       | HTTP method (GET, POST, etc.)       |
| `request_uri`          | Request URI with query string       |
| `uri`                  | Request URI without query string    |
| `args`                 | Query string parameters             |
| `scheme`               | http or https                       |
| `server_protocol`      | HTTP version                        |
| `request_length`       | Request size in bytes               |
| `request_time`         | Request processing time (seconds)   |
| `status`               | HTTP response status code           |
| `body_bytes_sent`      | Response body size                  |
| `bytes_sent`           | Total response size                 |
| `host`                 | Request host                        |
| `server_addr`          | Server IP address                   |
| `server_port`          | Server port                         |
| `server_name`          | Server name from config             |
| `hostname`             | Machine hostname                    |
| `http_user_agent`      | User-Agent header                   |
| `http_referer`         | Referer header                      |
| `http_x_forwarded_for` | X-Forwarded-For header              |
| `upstream_*`           | Upstream/proxy metrics              |
| `ssl_*`                | TLS/SSL connection details          |
| `gzip_ratio`           | Compression ratio                   |

***

## Support

If you encounter any issues, please contact Limy support with:

* Fluent Bit version (`fluent-bit --version`)
* Nginx version (`nginx -v`)
* Your deployment type (Bare Metal, Docker, or Kubernetes)
* Relevant error logs
* Your configuration files (with API key redacted)


# Custom Log Shipping

### Overview

This integration lets you send access logs directly to Limy's ingestion endpoint via HTTP POST.

Use this when you don't have a CDN or want to send logs from your own infrastructure.

{% stepper %}
{% step %}
**Format your logs**

Send logs as a JSON array to:

```
POST https://stream.getlimy.ai
```

Include these headers:

```
X-API-KEY: lmy_xxxx
User-Agent: Limy-Custom-HTTP/1.0
```

Each log entry needs these fields:

| Field          | Required |
| -------------- | -------- |
| `timestamp`    | ✓        |
| `method`       | ✓        |
| `host`         | ✓        |
| `path`         | ✓        |
| `status_code`  | ✓        |
| `ip`           | ✓        |
| `user_agent`   | ✓        |
| `referer`      | ✓        |
| `query_params` |          |
| `bytes_sent`   |          |
| `duration_ms`  |          |

Max 20MB or 1,000 entries per request.
{% endstep %}

{% step %}
**Send the request**

Example payload:

```json
[
  {
    "timestamp": "2025-06-15T14:30:00Z",
    "method": "GET",
    "host": "domain.com",
    "path": "/shopping",
    "status_code": 200,
    "ip": "192.168.1.1",
    "user_agent": "Mozila...",
    "query_params": {
      "brand": "nike"
    },
    "referer": "https://chatgpt.com"
  }
]
```

{% endstep %}

{% step %}
**Verify it's working**

Navigate to your Limy dashboard and check the Agent Monitor.
{% endstep %}
{% endstepper %}

### Tips

* Batch up to 1,000 logs per request
* Limit request size to 20MB
* Send async so you don't block your app
* Implement retries for failures

### Need More Help?

* Contact `answers@limy.ai` for any further questions


# Wordpress

{% stepper %}
{% step %}

### Download the plugin from WordPress Plugin Directory

<https://wordpress.org/plugins/limy-agent-analytics/#installation> - Download
{% endstep %}

{% step %}

### Upload the limy-agent-analytics folder

Upload into `/wp-content/plugins/` - Inside your codebase
{% endstep %}

{% step %}

### Add the plugin from the plugin library

Download from the library activate via “Activate Tracking” checkbox and save -

<figure><img src="/files/I9bImTcwiF9J1cx0sdiJ" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Check configuration

Go to `Settings > Limy Agent Analytics`

Enable Activate tracking checkbox\
![](/files/SZsSQF8DK8q3EeNZgo3D)\
Insert the API key

Collector URL - <https://stream.getlimy.ai>\
![](/files/3s2zBxoNH1QSPCIVf7IP)\
\
Batch size (recommended is 500), Interval (recommended is 600)

Save Changes - ![](/files/IJ5k3E9xkLSK1CwYRgtM)
{% endstep %}
{% endstepper %}


# API Reference

The Limy REST API gives you programmatic access to some of the metadata and analytics that power the Limy applications. Use it to pull data into your own dashboards, warehouse, or workflows.

#### What you can do

* Read account metadata — prompts, topics, competitors etc.
* Pull visibility, source analysis metrics on demand.
* Build internal dashboards and alerts on top of Limy data.
* Feed Limy analytics into your data warehouse or BI tool.

#### Base URL

```
https://api.limy.ai
```

All endpoints are versioned (`/v1/...`).

#### Getting started

1. [**Create an API key**](/rest-api/create-an-api-key) from the Limy Account Dashboard Page.
2. [**Authenticate**](/rest-api/authentication) by exchanging the static key for a short-lived session token.
3. Call any endpoint listed&#x20;


# Authentication

## Exchange a static API key for a short-lived session JWT.

> Send your static API key as a Bearer token. Returns a short-lived session JWT to use on all subsequent API requests.

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"authentication"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/v1/auth/accesskey/exchange":{"post":{"tags":["authentication"],"summary":"Exchange a static API key for a short-lived session JWT.","description":"Send your static API key as a Bearer token. Returns a short-lived session JWT to use on all subsequent API requests.","responses":{"200":{"description":"Session JWT issued.","content":{"application/json":{"schema":{"type":"object","required":["keyId","sessionJwt"],"properties":{"keyId":{"type":"string","description":"Identifier of the static key used."},"sessionJwt":{"type":"string","description":"Bearer token for subsequent API calls."}}}}}},"401":{"description":"Static key is missing, invalid, or revoked."}}}}}}
```


# Accounts

## GET /v1/account

> Get account details

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"accounts"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"AccountResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}}},"required":["id","name","domains"]}}},"paths":{"/v1/account":{"get":{"operationId":"AccountController_getAccount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponseDto"}}}}},"summary":"Get account details","tags":["accounts"]}}}}
```


# Competitors

## GET /v1/competitors

> List competitors

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"competitors"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CompetitorsListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}}},"required":["id","name","domains"]}}},"required":["data"]}}},"paths":{"/v1/competitors":{"get":{"operationId":"CompetitorsController_getCompetitors","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetitorsListResponseDto"}}}}},"summary":"List competitors","tags":["competitors"]}}}}
```


# Prompts

## GET /v1/prompts

> List prompts

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"prompts"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"PromptsListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"prompt":{"type":"string"},"topicId":{"type":"string","format":"uuid"}},"required":["id","prompt","topicId"]}},"pagination":{"type":"object","properties":{"take":{"type":"integer","minimum":0,"maximum":9007199254740991},"skip":{"type":"integer","minimum":0,"maximum":9007199254740991},"hasMore":{"type":"boolean"}},"required":["take","skip","hasMore"]}},"required":["data","pagination"]}}},"paths":{"/v1/prompts":{"get":{"operationId":"PromptsController_getPrompts","parameters":[{"name":"take","required":false,"in":"query","schema":{"maximum":1000,"exclusiveMinimum":true,"default":50,"type":"integer","minimum":0}},{"name":"skip","required":false,"in":"query","schema":{"minimum":0,"maximum":9007199254740991,"default":0,"type":"integer"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptsListResponseDto"}}}}},"summary":"List prompts","tags":["prompts"]}}}}
```


# Topics

## GET /v1/topics

> List topics

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"topics"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"TopicsListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["data"]}}},"paths":{"/v1/topics":{"get":{"operationId":"TopicsController_getTopics","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopicsListResponseDto"}}}}},"summary":"List topics","tags":["topics"]}}}}
```


# Analytics

## Brand mention trends

> Returns a time series of how often your brand and tracked competitors appear in AI search engine answers

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"analytics"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"TimeSeriesRequestDto":{"type":"object","properties":{"interval":{"type":"string","enum":["daily","weekly","monthly"]},"filters":{"type":"object","properties":{"timeRange":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}},"required":["from","to"]},"providers":{"type":"array","items":{"type":"string","enum":["GEMINI","OPENAI","ANTHROPIC","PERPLEXITY","GROK","GOOGLE_AI_MODE","GOOGLE_AI_OVERVIEW","COPILOT"]}},"topics":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}},"required":["id"]}},"regions":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$","description":"ISO 3166-1 alpha-2 country code (case-insensitive). "}},"required":["code"]}}},"required":["timeRange"]}},"required":["interval","filters"]},"TimeSeriesResponseDto":{"type":"object","properties":{"interval":{"type":"string","enum":["daily","weekly","monthly"]},"timeRange":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}},"required":["from","to"]},"series":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"value":{"type":"number","nullable":true}},"required":["timestamp","value"]}}},"required":["id","name","data"]}}},"required":["interval","timeRange","series"]}}},"paths":{"/v1/analytics/brands/mentions":{"post":{"description":"Returns a time series of how often your brand and tracked competitors appear in AI search engine answers","operationId":"AnalyticsController_getBrandsMentions","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeSeriesRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeSeriesResponseDto"}}}}},"summary":"Brand mention trends","tags":["analytics"]}}}}
```

## Top cited sources

> Returns the domains or URLs most frequently cited in AI search answers for your account, ranked by citation volume

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"analytics"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SourceAnalysisRequestDto":{"type":"object","properties":{"limit":{"type":"integer","exclusiveMinimum":true,"maximum":100,"minimum":0},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991},"groupBy":{"type":"string","enum":["domain","url"]},"filters":{"type":"object","properties":{"timeRange":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}},"required":["from","to"]},"providers":{"type":"array","items":{"type":"string","enum":["GEMINI","OPENAI","ANTHROPIC","PERPLEXITY","GROK","GOOGLE_AI_MODE","GOOGLE_AI_OVERVIEW","COPILOT"]}},"topics":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}},"required":["id"]}},"regions":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$","description":"ISO 3166-1 alpha-2 country code (case-insensitive). "}},"required":["code"]}}},"required":["timeRange"]}},"required":["limit","offset","groupBy","filters"]},"SourceAnalysisResponseDto":{"type":"object","properties":{"groupBy":{"type":"string","enum":["domain","url"]},"sources":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"share":{"type":"number","minimum":0,"maximum":1},"citations":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["source","share","citations"]}},"pagination":{"type":"object","properties":{"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"type":"integer","exclusiveMinimum":true,"maximum":9007199254740991,"minimum":0},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991},"hasMore":{"type":"boolean"}},"required":["total","limit","offset","hasMore"]}},"required":["groupBy","sources","pagination"]}}},"paths":{"/v1/analytics/sources":{"post":{"description":"Returns the domains or URLs most frequently cited in AI search answers for your account, ranked by citation volume","operationId":"AnalyticsController_getSourceAnalysis","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceAnalysisRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceAnalysisResponseDto"}}}}},"summary":"Top cited sources","tags":["analytics"]}}}}
```


# Traffic

## Export captured actions

> Returns AI-referred events captured on your domain — any event type (page views, autocapture, custom) — each with its AI referrer (\`ai\_provider\`) and the raw captured properties. \*\*Beta:\*\* the response shape may change.

```json
{"openapi":"3.0.0","info":{"title":"Limy API","version":"1.0"},"tags":[{"name":"traffic"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CapturedActionsRequestDto":{"type":"object","properties":{"filters":{"type":"object","properties":{"timeRange":{"type":"object","properties":{"from":{"type":"string","format":"date-time","description":"Start of the export window (inclusive), ISO 8601 date-time."},"to":{"type":"string","format":"date-time","description":"End of the export window (exclusive), ISO 8601 date-time."}},"required":["from","to"]},"userIds":{"description":"Optional list of visitor IDs (distinctId) to filter by. Omit to return all visitors.","type":"array","items":{"type":"string"}}},"required":["timeRange"]},"limit":{"default":200,"description":"Maximum number of events to return per page (1–1000).","type":"integer","exclusiveMinimum":true,"maximum":1000,"minimum":0},"cursor":{"description":"Opaque keyset cursor from a previous response. Pass `pagination.nextCursor` to fetch the next page; omit for the first page.","type":"string"}},"required":["filters"]},"CapturedActionsResponseDto":{"type":"object","properties":{"actions":{"type":"array","items":{"type":"object","properties":{"uuid":{"type":"string"},"event":{"type":"string"},"timestamp":{"type":"string"},"team_id":{"type":"number"},"distinct_id":{"type":"string","description":"Stable visitor identifier. This is the value matched by the `filters.userIds` request filter."},"elements_chain":{"type":"string"},"created_at":{"type":"string"},"person_id":{"type":"string"},"person_created_at":{"type":"string"},"person_properties":{"type":"string"},"person_mode":{"type":"string"},"_timestamp":{"type":"string"},"inserted_at":{"type":"string","nullable":true},"ai_provider":{"type":"string","description":"AI engine that referred the visit (e.g. ChatGPT, Gemini, Perplexity). Only AI-referred events are returned."}},"required":["uuid","event","timestamp","team_id","distinct_id","elements_chain","created_at","person_id","person_created_at","person_properties","person_mode","_timestamp","inserted_at","ai_provider"],"additionalProperties":{}}},"pagination":{"type":"object","properties":{"nextCursor":{"type":"string","nullable":true},"hasMore":{"type":"boolean"}},"required":["nextCursor","hasMore"]}},"required":["actions","pagination"]}}},"paths":{"/v1/traffic/sdk/captured-actions":{"post":{"description":"Returns AI-referred events captured on your domain — any event type (page views, autocapture, custom) — each with its AI referrer (`ai_provider`) and the raw captured properties. **Beta:** the response shape may change.","operationId":"SdkTrafficController_getCapturedActions","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapturedActionsRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapturedActionsResponseDto"}}}}},"summary":"Export captured actions","tags":["traffic"]}}}}
```


# Create an API key

An API key authenticates your requests to the Limy REST API. You can generate, view, and revoke keys from the Limy Admin Dashboard

{% hint style="info" %}
Only account admins can generate or revoke API keys.
{% endhint %}

#### Generate a key

{% stepper %}
{% step %}
**Open the API Keys Page**

Go to **Admin Dashboard** and open the **API** entry
{% endstep %}

{% step %}
**Click Generate API key**

You'll find the button in the top-right of the page (and inside the empty state if you have no keys yet).
{% endstep %}

{% step %}
**Copy the key — it is only shown once**

After generation, Limy displays the full secret key.

Click **Copy key** to copy it to your clipboard, then store it somewhere safe&#x20;
{% endstep %}
{% endstepper %}

#### Limits

Every API key is bound to a **usage plan** that controls how often you can call the Limy API. The plan is assigned by Limy when the key is created and enforces two limits:

| Limit     | What it caps                                             | Behavior when exceeded  |
| --------- | -------------------------------------------------------- | ----------------------- |
| **Rate**  | Sustained requests per second + a short burst above that | `429 Too Many Requests` |
| **Quota** | Total requests per day, week, or month                   | `429 Too Many Requests` |

#### Revoke a key

If a key is compromised or no longer needed, open **Account settings → API**, find the key in the list, and click the trash icon. Revocation is immediate — any request using a revoked key will be rejected.

{% hint style="warning" %}
Revoked keys cannot be restored. Issue a new key if access is still needed.
{% endhint %}

#### Need help?

Reach out at `answers@limy.ai`.


# Authentication

## Authentication

Limy's REST API does **not** accept your static API key directly. Instead, you exchange the static key for a short-lived **session token** (a JWT), and use that session token on every API request. This two-step model lets us revoke compromised keys instantly without invalidating in-flight traffic, and keeps long-lived secrets off the wire on every call.

#### Step 1 — Exchange your static key for a session token

Send the static key as a Bearer token to the exchange endpoint:<br>

```bash
curl -X POST https://api.limy.ai/v1/auth/accesskey/exchange \
  -H "Authorization: Bearer <YOUR_STATIC_API_KEY>"
```

Successful response:

```json
{
  "keyId": "K3EfyZac4vZdEMC1VIodMSguN5Ro",
  "sessionJwt": "eyJhbGciOiJSUzI1NiIs..."
}
```

* `keyId` — identifier of the static key that issued this session. Useful for logging which key your service is currently using.
* `sessionJwt` — the token you'll send to the Limy API. Treat it like a password. Its expiry (`exp` claim) is encoded in the JWT itself&#x20;

{% hint style="info" %}
Always cache the JWT and reuse it until close to its `exp` time — do not exchange on every API call.
{% endhint %}

#### Step 2 — Call the Limy API with the session token

Send the `sessionJwt` as a Bearer token on every API request:

```bash
curl https://api.limy.ai/v1/<endpoint> \
  -H "Authorization: Bearer <SESSION_JWT>"
```

If the token is missing, malformed, or expired, the API returns `401 Unauthorized`.

#### Error responses

| Status                                              | Meaning                                       | What to do                                                                                                   |
| --------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `401 Unauthorized` on `/v1/auth/accesskey/exchange` | Static key is invalid, revoked, or expired    | Generate a new key in the admin panel                                                                        |
| `401 Unauthorized` on a Limy API call               | Session JWT is missing, malformed, or expired | Re-exchange the static key and retry                                                                         |
| `429 Too Many Requests`                             | API key rate/quota reached                    | Retry with exponential backoff on `429` rate limit errors. For daily quota exhaustion, retry after 24 hours. |

####


# Wix

### Overview

Integrate Limy AI with your Wix site to gain powerful insights into how AI agents interact with your content. By connecting Limy AI through the Wix App Market, you'll unlock:

* **Deep AI analytics** - Understand how ChatGPT, Perplexity, Gemini, Claude and other AI agents discover and interpret your site
* **Real time monitoring** - Track AI crawler activity as it happens
* **Performance insights** - Analyze traffic patterns, user behavior, and distinguish between bot and human visitors

### Prerequisites

Before you begin, ensure you have:

* **Wix account** with an active website
* **Site admin permissions** to install apps from the Wix App Market

### Configuration

{% stepper %}
{% step %}

### Step 1

Navigate to Apps in the left sidebar on your wix dashboard

<div align="left"><figure><img src="/files/9w5dflHgfgmXAB2YWZMV" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### Step 2

Search for **"Limy.ai"** in the search bar

<div align="left"><figure><img src="/files/ZUfujaAJpUasZr0H5kvO" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### Step 3

Click on the **Limy AI** app from the results

<div align="left"><figure><img src="/files/gj9enyENWK9OTo28GLZz" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### Step 4

Click **Add to Site**

<div align="left"><figure><img src="/files/6JCLsKVyIUGJorCrmH3I" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### Step 5

Click **Agree & Add**

<div align="left"><figure><img src="/files/Ec9I50LYq0s79m9OpM6f" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

### Step 6

Click **Go to Dashboard**

<div align="left"><figure><img src="/files/OYNBZKrmMaDMShJAa9Pr" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}

{% hint style="success" %}
That’s it! You have now successfully configured Limy router for your website.
{% endhint %}


# Limy AI Glossary

The Limy AI Glossary provides clear, concise definitions of key terms used across the agentic web ecosystem. As AI search engines, LLM-driven interactions, and autonomous agents reshape how users disc

<figure><img src="/files/nZ8t7eDsjg30POx1G8lX" alt=""><figcaption></figcaption></figure>

### Core concepts

* **Prompt**\
  The user’s query to an AI surface (e.g., “best running shoes for knee pain”).
* **Response**\
  The AI’s response to a prompt, which may contain links to your site.
* **Prompt Hash**\
  A stable ID (hash) representing a canonicalized prompt text used for grouping/analytics.
* **Answer ID**\
  A unique identifier for a specific AI answer snapshot.
* **Rank/Position**\
  The placement of your link within the AI answer (e.g., first link shown).

### Acquisition & tracking

* **AI Impression**\
  Recorded when a tracked prompt/answer containing (or likely leading to) your link is shown.
* **AI Click**\
  A click from the AI answer to your site (ideally via a decorated/redirected link).
* **Redirector / Link Decorator**\
  A shortlink service that wraps destination URLs to attach IDs/UTMs for deterministic attribution.
* **Destination URL (Dest URL)**\
  The specific page on your site that receives the AI-driven visit.
* **UTM Parameters**\
  Standard marketing tags (e.g., `utm_source=chatgpt`) persisted on arrival for analytics.
* **AI Params**\
  Non-UTM tags used by Limy (e.g., `answer_id`, `prompt_hash`, `link_id`, `ai_model`).
* **Referrer**\
  The page or service that led the user to your site (often the redirector domain).
* **Session**\
  A continuous period of user activity on your site.
* **Sessionization**\
  Logic that groups web events into sessions (server-side preferred).
* **Client ID (CID)**\
  First-party identifier stored in a cookie/local storage and joined to sessions.
* **Server Session ID**\
  A server-generated session key used to stitch events and reduce reliance on client cookies.
* **Event Collector (SDK/Edge)**\
  Limy’s server-side endpoint or edge function that receives web/app events.
* **Limy Pixel**\
  The lightweight snippet/SDK that initializes IDs and forwards events server-side.

### On-site events & commerce

* **Page View**\
  A view of any page; typically the first event after AI Click.
* **Key Event**\
  Milestone interactions (e.g., `view_item`, `add_to_cart`, `begin_checkout`, `lead_submit`).
* **Purchase**\
  Completed order with `order_id`, value, currency, items.
* **Order Value / Revenue**\
  Monetary value attributed to a purchase after discounts but before refunds.
* **Conversion (CVR)**\
  A desired outcome (often purchase or qualified lead) divided by a base (clicks or sessions).

### Identity & stitching

* **Identity Graph**\
  A table joining `client_id`, `server_session_id`, hashed email, and user/account IDs.
* **Deterministic Match**\
  High-certainty connection (e.g., redirector `link_id` + session, or logged-in user).
* **Semi-Deterministic Match**\
  Strong but not perfect signals (e.g., redirector referrer + near-instant session).
* **Probabilistic Match**\
  Statistical linkage using features like time gap, geo, device family, and URL similarity.
* **Cross-Device Stitching**\
  Merging identities across devices via hashed email, login, or server-side IDs.

### Attribution & models

* **Attribution**\
  The method of assigning credit (and revenue) to touchpoints that influenced a conversion.
* **Attribution Model (Selector)**\
  Switchable logic: Last-Touch, First-Touch, Position-Based (40/40/20), Time-Decay, or Data-Driven.
* **Last-Touch**\
  100% credit to the final touchpoint before conversion.
* **First-Touch**\
  100% credit to the initial AI touchpoint (first impression/click).
* **Position-Based (40/40/20)**\
  40% to first AI touch, 40% to last pre-purchase touch, 20% split across middle touches.
* **Time-Decay**\
  Credit weighted by recency (configurable half-life).
* **Data-Driven Attribution**\
  Algorithmic approach (e.g., Markov/Removal Effect or Shapley) estimating each touchpoint’s incremental impact.
* **Lookback Window**\
  Time span during which touches are eligible to receive credit (e.g., 7 days for clicks).
* **Attribution Confidence**\
  A 0–100 score reflecting certainty of the AI→session→order linkage.
* **Confidence Tiers**\
  High (≥0.80), Medium (0.60–0.79), Low (<0.60); used for reporting filters.
* **Attributed Revenue**\
  Revenue portion assigned to AI touchpoints under the active model and confidence threshold.

### Analytics objects

* **Journey / Path**\
  The ordered set of AI and on-site events leading to a conversion.
* **Journey ID**\
  A unique ID for a specific path instance used in deep links and audit trails.
* **Timeline**\
  UI visualization of a journey’s events and timestamps.
* **Funnel**\
  Aggregated steps from **Prompt Impressions → AI Clicks → Sessions → Key Events → Purchases**.
* **Category**\
  A thematic grouping of prompts (e.g., “Running”, “Accessories”), used for reporting and drill-downs.
* **Prompt Leaderboard**\
  A ranked table of prompts by revenue, conversions, or efficiency.
* **Top URLs**\
  Report listing destination pages receiving the most AI-driven traffic/conversions.
* **Anomaly**\
  A statistically significant spike or drop (e.g., day-over-day or week-over-week) with a likely cause (traffic vs CR).
* **Lift**\
  Performance change versus baseline/previous period (e.g., +12%).
* **KPIs**\
  Key metrics surfaced at the top of dashboards (e.g., Attributed Revenue, Conversions, Avg Confidence).

### Data model & fields

* **`ai_impressions`**\
  Table of prompt/answer exposures with `prompt_hash`, `answer_id`, model, position, time, URLs.
* **`ai_clicks`**\
  Click rows with `link_id`, `prompt_hash`, `answer_id`, click time, dest URL.
* **`web_sessions`**\
  Session metadata (first/last seen, geo, device family, first AI params).
* **`web_events`**\
  All tracked events with `event_name` and properties JSON.
* **`orders`**\
  Commerce records with value, currency, items, and session link.
* **`attribution_results`**\
  Computed credit per order by model, with confidence and explanations.
* **`link_id`**\
  Unique ID assigned by the redirector when generating decorated links.
* **`ai_model`**\
  The assistant model name (e.g., “gpt-4o”) stored with impressions/clicks.
* **`utm_source` / `utm_medium` / `utm_campaign` / `utm_content`**\
  Standard acquisition tags used alongside AI params.

### Governance, privacy & reliability

* **Consent Gate**\
  Logic that defers non-essential tracking until user consent is granted.
* **Data Retention**\
  How long raw versus aggregated data are stored (e.g., 25 months for raw).
* **Access Controls**\
  Role-based permissions for sensitive views (identity, raw payloads).
* **Sampling**\
  Processing a subset of events for speed/cost, with safeguards for accuracy.
* **Backfill**\
  Recomputing histories after schema/model changes or late-arriving data.
* **Deduplication**\
  Process to prevent multiple event records for the same user action.
* **Schema Versioning**\
  Version tags on event payloads/tables to manage change safely.

### UI & workflow terms

* **Model Picker**\
  Control to switch the active attribution model across widgets.
* **Confidence Threshold**\
  Filter to include/exclude low-certainty attributions from totals.
* **Drill-Down**\
  Navigation from aggregates (category) to specifics (prompts/URLs), then to journeys.
* **Export**\
  CSV/JSON/PNG outputs for tables and charts.
* **Virtualization**\
  Rendering technique that keeps tables fast with 1,200+ prompts.
* **Tooltip Copy**\
  Short definition text used in the UI (often derived from this glossary).


# a2a


# Akamai

### **Overview**

This documentation describes the implementation of an **Akamai EdgeWorker** designed to detect traffic from AI search engines and agentic web crawlers using User-Agent analysis.\
Instead of modifying content inline, the EdgeWorker **routes AI systems to machine-optimized endpoints**, improving how your website is consumed, interpreted, and referenced across the agentic web.

This approach enables:

* Clearer machine-readable signals for AI models
* Better control over how AI agents access and process your content
* Separation of human vs. AI requester behavior in your analytics
* Improved presence across AI-driven discovery platforms (ChatGPT, Perplexity, Gemini, Claude, Grok, etc.)

## **Architecture**

#### **High-Level Flow**

**Incoming Request → User-Agent Analysis → AI Bot Detection → Routing Decision → URL Rewrite**

#### **Key Components**

* **AI Bot Detection Engine**\
  Identifies modern agentic web crawlers via User-Agent rules.
* **Routing Logic**\
  Applies URL rewrites for AI-optimized content.
* **Configuration Management**\
  Centralized bot patterns and routing rules.
* **Logging & Observability**\
  Tracks how AI agents browse, fetch, and interpret your content.

## **AI Search Engine Bot Detection & Routing — Akamai**

### **Introduction**

This EdgeWorker implementation identifies traffic from **AI search engines and agentic web crawlers** such as ChatGPT, Perplexity, Gemini, Claude, Grok, DeepSeek, and more.

As AI models increasingly pull, structure, and reuse web content to answer user prompts, this routing layer allows you to:

* Serve AI-specific variants of content
* Improve clarity of machine-consumable signals
* Understand how AI systems traverse and interpret your site
* Strengthen your visibility across the agentic web

## **Supported AI Search Engines**

#### Introduction

This Netlify Edge Function implementation provides intelligent detection and routing of **AI search engine traffic**.\
The system identifies requests from major AI-powered search services and routes them to **specialized content endpoints optimized for AI consumption**.

#### Supported AI Search Engines

ChatGPT / OpenAI, Gemini / Google AI, AI Overviews, Perplexity, Claude, Grok, DeepSeek, and generic AI modes.

## **Configuration**

#### **AI Bot Pattern Configuration**

```json
{
  "aiSearchBots": {
    "chatgpt": ["chatgpt-user", "gptbot", "openai-searchbot", "chatgpt-search", "openai-imagesbot"],
    "gemini": ["gemini-bot", "google-extended", "geminibot", "bard", "googlebot-ai"],
    "aiOverviews": ["google-inspectiontool", "google-ai-overview", "google-sge"],
    "perplexity": ["perplexitybot", "perplexity-search", "perplexityai"],
    "claude": ["claude-web", "claudebot", "claude-search", "anthropic-ai"],
    "grok": ["grok-bot", "grokbot", "xai-bot", "x-ai-bot"],
    "deepseek": ["deepseekbot", "deepseek-search", "deepseek-ai"],
    "aiMode": ["ai-mode-bot", "aimode", "intelligent-agent"]
  }
}
```

#### **Routing Rules Configuration**

```json
{
  "routingRules": {
    "chatgpt": { "method": "path_prefix", "target": "/chatgpt", "preserveQuery": true },
    "gemini": { "method": "path_prefix", "target": "/gemini", "preserveQuery": true },
    "aiOverviews": { "method": "path_prefix", "target": "/ai-overview", "preserveQuery": true },
    "perplexity": { "method": "path_prefix", "target": "/perplexity", "preserveQuery": true },
    "claude": { "method": "path_prefix", "target": "/claude", "preserveQuery": true },
    "grok": { "method": "path_prefix", "target": "/grok", "preserveQuery": true },
    "deepseek": { "method": "path_prefix", "target": "/deepseek", "preserveQuery": true },
    "aiMode": { "method": "path_prefix", "target": "/ai-mode", "preserveQuery": true }
  }
}
```

## **Implementation**

#### **Core Detection Function**

```js
function detectAISearchBot(userAgent) {
    const ua = userAgent.toLowerCase();

    if (ua.includes('chatgpt') || ua.includes('gptbot') || ua.includes('openai'))
        return 'chatgpt';

    if (ua.includes('gemini') || ua.includes('bard') || ua.includes('google-extended'))
        return 'gemini';

    if (ua.includes('google-sge') || ua.includes('ai-overview'))
        return 'aiOverviews';

    if (ua.includes('perplexity'))
        return 'perplexity';

    if (ua.includes('claude') || ua.includes('anthropic'))
        return 'claude';

    if (ua.includes('grok') || ua.includes('xai-bot'))
        return 'grok';

    if (ua.includes('deepseek'))
        return 'deepseek';

    if (ua.includes('ai-mode') || ua.includes('intelligent-agent'))
        return 'aiMode';

    return null;
}
```

## **URL Routing Function**

```js
function generateAIBotUrl(originalUrl, aiBot, config) {
    const url = new URL(originalUrl);
    const rule = config.routingRules[aiBot];

    if (!rule) return originalUrl;

    switch (rule.method) {
        case 'path_prefix':
            url.pathname = rule.target + url.pathname;
            break;
        case 'subdomain':
            url.hostname = rule.target;
            break;
        case 'parameter':
            url.searchParams.set(rule.param || 'ai', rule.value || aiBot);
            break;
    }

    if (!rule.preserveQuery) {
        url.search = '';
    }

    return url.toString();
}
```

## **Complete EdgeWorker Implementation**

```js
import { aiSearchBots, routingRules } from './config.js';

export async function onClientRequest(request) {
    const userAgent = request.getHeader('User-Agent') || '';
    const originalUrl = request.url;

    // Detect AI search engine or agentic web crawler
    const aiBot = detectAISearchBot(userAgent);

    if (!aiBot) {
        // Human traffic — no special handling needed
        return;
    }

    // Generate AI-optimized URL
    const targetUrl = generateAIBotUrl(originalUrl, aiBot, { routingRules });

    // Add request metadata for analytics
    request.setHeader('X-AI-Bot', aiBot);
    request.setHeader('X-Original-URL', originalUrl);
    request.setHeader('X-AI-Optimized', 'true');

    // Route the AI agent to specialized content
    request.url = targetUrl;

    // Observability
    console.log(`Agentic Web Routing: ${aiBot} -> ${targetUrl}`);
}
```

## **Routing Methods (Agentic Web–Ready)**

#### **Path Prefix Routing**

Best for agentic clarity:

* `/article/ai-trends`\
  → `/chatgpt/article/ai-trends`

#### **Subdomain Routing**

Useful for large multi-property ecosystems:

* `www.example.com/page`\
  → `perplexity.example.com/page`

#### **Parameter Routing**

Lightweight AI classification:

* `/documentation`\
  → `/documentation?ai=claude`

## **Environment Configuration**

#### Production

Focus on stability, clarity for AI models, and observability.

#### Staging

Used for expanding bot patterns, experimenting with new AI indexing behaviors, etc.

*(all JSON examples updated to reflect agentic terminology; no SEO terms.)*

## **Monitoring & Analytics**

#### AI-Specific Headers

* `X-AI-Bot` - Identifies which AI system accessed the content
* `X-AI-Optimized` - Indicates that agentic routing occurred
* `X-Original-URL` - For reconstructing navigation flow

#### Example Log

```json
{
  "timestamp": "2025-01-01T12:00:00Z",
  "aiService": "chatgpt",
  "originalUrl": "/article/example",
  "routedUrl": "/chatgpt/article/example",
  "userAgent": "ChatGPT-User/1.0",
  "routingMethod": "path_prefix"
}
```

## **Deployment**

* Upload EdgeWorker bundle
* Apply routing rule via Property Manager
* Enable monitoring
* Deploy to staging, validate AI routing
* Roll out progressively to production

## **Performance & Security (Agentic Web Context)**

* AI-specific rate limits
* Pattern validation
* Known bot IP verification
* Agent behavior anomaly detection

That’s it! You have now successfully configured Limy router for your website. Data should begin to populate on your dashboard within an hour.


# Cloudflare

##

### Overview

This Cloudflare Worker automatically detects and routes AI search engine bot traffic to different URLs based on User-Agent analysis. It differentiates between AI-powered search engines and human users, allowing you to serve optimized content to AI crawlers while providing the full experience to human visitors.

### Integrations;

* **AI Bot Detection**: Identifies AI search engine bots using User-Agent pattern matching
* **Traffic Segmentation**: Separates AI search engine traffic from human users
* **Flexible Routing**: Supports subdomain, path-based, and domain-based routing
* **Performance Optimized**: Lightweight detection with minimal latency impact

### Supported AI Search Engine Bots

### Installation

#### Prerequisites

* Cloudflare account
* Wrangler CLI installed (`npm install -g wrangler`)

#### Deployment Steps

1. **Initialize Worker**:

   ```bash
   wrangler init ai-bot-router
   cd ai-bot-router
   ```
2. **Replace Code**: Copy the provided code into `src/index.js`
3. **Deploy**:

   ```bash
   wrangler deploy
   ```
4. **Configure Routes**: In Cloudflare Dashboard → Workers → Routes, add:

   ```
   yourdomain.com/*
   ```

### Usage Examples

#### Example 1: Subdomain Routing

AI search engines are routed to a dedicated subdomain:

**Result**: `yourdomain.com/content` → `ai.yourdomain.com/content`

#### Example 2: Path-Based Routing

AI bots get a specific path prefix:

**Result**: `yourdomain.com/article` → `yourdomain.com/ai/article`

#### Example 3: Separate Domain

AI bots are routed to a completely different domain:

**Result**: AI bot sees `yourdomain.com` but gets content from `ai-content.yourdomain.com`

### Advanced Customization

#### Adding New AI Bot Patterns

Extend the `botPatterns` array in `detectBot()`:

```javascript
const botPatterns = [
  // ... existing AI bot patterns
  /newaibot/i,
  /custom-ai-crawler/i,
  /your-ai-tool/i
];
```

#### Custom AI Bot Handling

Modify the routing logic to handle specific AI bots differently:

```javascript
async function handleBotTraffic(request, url, userAgent) {
  if (/GPTBot/i.test(userAgent)) {
    // Special handling for ChatGPT
    return handleChatGPTBot(request, url);
  }
  
  // Default AI bot handling
  return handleGenericAIBot(request, url);
}
```

### Support

For issues and questions:

* Check Cloudflare Worker logs in the dashboard
* Monitor response codes and AI bot behavior
* Test with various AI search engine User-Agents
* Review detection patterns in logs for accuracy

### Version History

* **v1.0**: Initial release with AI search engine bot detection and routing
* Current implementation focuses specifically on AI-powered search engines and crawlers

Select HTTP requests dataset, and choose the following fields to send:

* Request
  * `ClientIP` - IP Address of the client.
  * `ClientRequestHost` - Host requested by the client.
  * `ClientRequestMethod` - HTTP method of client request.
  * `ClientRequestReferer` - HTTP request referrer.
  * `ClientRequestURI` - URI requested by the client.
  * `ClientRequestUSerAgent` - User agent reported by the client
* Performance
  * `EdgeStartTimestamp` - Timestamp at which the edge received request from the client.
  * `EdgeEndTimestamp` - Timestamp at which the edge finished sending response to the client.
* Response
  * `EdgeResponseBytes` - Number of bytes returned by the edge to the client.
  * `EdgeReponseStatus` - HTTP status code returned by Cloudflare to the client.

That’s it! You have now successfully configured Limy router for your website. Data should begin to populate on your dashboard within an hour.


# Fastly

### Overview

This documentation describes a Fastly Compute\@Edge implementation that detects traffic from AI search engines and agentic web crawlers using User-Agent analysis. The function routes AI systems to machine-optimized endpoints, improving how your content is consumed, interpreted, and referenced across the agentic web.

### Architecture

**Incoming Request → User-Agent Analysis → AI Bot Detection → Routing Decision → URL Rewrite**

#### Key Components

* AI Bot Detection Engine
* Routing Logic
* Configuration Management
* Logging & Observability

## AI Search Engine Bot Detection

### Introduction

This implementation identifies traffic from AI search engines and agentic web crawlers such as ChatGPT, Perplexity, Gemini, Claude, Grok, and DeepSeek. The system routes these agents to specialized endpoints optimized for AI consumption, improving machine readability, model context accuracy, and your overall presence across the agentic web.

## Configuration

#### AI Bot Pattern Configuration (config.json)

```json
{
  "aiSearchBots": {
    "chatgpt": [
      "chatgpt-user",
      "gptbot",
      "openai-searchbot",
      "chatgpt-search",
      "openai-imagesbot"
    ],
    "gemini": [
      "gemini-bot",
      "google-extended",
      "geminibot",
      "bard",
      "googlebot-ai"
    ],
    "aiOverviews": [
      "google-inspectiontool",
      "google-ai-overview",
      "google-sge"
    ],
    "perplexity": [
      "perplexitybot",
      "perplexity-search",
      "perplexityai"
    ],
    "claude": [
      "claude-web",
      "claudebot",
      "claude-search",
      "anthropic-ai"
    ],
    "grok": [
      "grok-bot",
      "grokbot",
      "xai-bot",
      "x-ai-bot"
    ],
    "deepseek": [
      "deepseekbot",
      "deepseek-search",
      "deepseek-ai"
    ],
    "aiMode": [
      "ai-mode-bot",
      "aimode",
      "intelligent-agent"
    ]
  }
}
```

#### Routing Rules Configuration (routing.json)

```json
{
  "routingRules": {
    "chatgpt": {
      "method": "path_prefix",
      "target": "/chatgpt",
      "preserveQuery": true
    },
    "gemini": {
      "method": "path_prefix",
      "target": "/gemini",
      "preserveQuery": true
    },
    "aiOverviews": {
      "method": "path_prefix",
      "target": "/ai-overview",
      "preserveQuery": true
    },
    "perplexity": {
      "method": "path_prefix",
      "target": "/perplexity",
      "preserveQuery": true
    },
    "claude": {
      "method": "path_prefix",
      "target": "/claude",
      "preserveQuery": true
    },
    "grok": {
      "method": "path_prefix",
      "target": "/grok",
      "preserveQuery": true
    },
    "deepseek": {
      "method": "path_prefix",
      "target": "/deepseek",
      "preserveQuery": true
    },
    "aiMode": {
      "method": "path_prefix",
      "target": "/ai-mode",
      "preserveQuery": true
    }
  }
}
```

## Implementation

#### aiDetection.ts

```ts
export type AIBotType =
  | "chatgpt"
  | "gemini"
  | "aiOverviews"
  | "perplexity"
  | "claude"
  | "grok"
  | "deepseek"
  | "aiMode"
  | null;

export function detectAISearchBot(userAgent: string): AIBotType {
  const ua = userAgent.toLowerCase();

  if (ua.includes("chatgpt") || ua.includes("gptbot") || ua.includes("openai"))
    return "chatgpt";

  if (ua.includes("gemini") || ua.includes("bard") || ua.includes("google-extended"))
    return "gemini";

  if (ua.includes("google-sge") || ua.includes("ai-overview"))
    return "aiOverviews";

  if (ua.includes("perplexity"))
    return "perplexity";

  if (ua.includes("claude") || ua.includes("anthropic"))
    return "claude";

  if (ua.includes("grok") || ua.includes("xai-bot"))
    return "grok";

  if (ua.includes("deepseek"))
    return "deepseek";

  if (ua.includes("ai-mode") || ua.includes("intelligent-agent"))
    return "aiMode";

  return null;
}
```

#### routing.ts

```ts
import type { AIBotType } from "./aiDetection";

interface RoutingRule {
  method: "path_prefix" | "subdomain" | "parameter";
  target: string;
  preserveQuery: boolean;
  param?: string;
  value?: string;
}

interface RoutingConfig {
  routingRules: Record<string, RoutingRule>;
}

export function generateAIBotUrl(
  originalUrl: string,
  aiBot: AIBotType,
  config: RoutingConfig
): string {
  if (!aiBot) return originalUrl;

  const url = new URL(originalUrl);
  const rule = config.routingRules[aiBot];

  if (!rule) return originalUrl;

  switch (rule.method) {
    case "path_prefix":
      url.pathname = rule.target + url.pathname;
      break;
    case "subdomain":
      url.hostname = rule.target;
      break;
    case "parameter":
      url.searchParams.set(rule.param || "ai", rule.value || aiBot);
      break;
  }

  if (!rule.preserveQuery) {
    url.search = "";
  }

  return url.toString();
}
```

#### main.ts (Fastly Compute\@Edge)

```ts
import { detectAISearchBot } from "./aiDetection";
import { generateAIBotUrl } from "./routing";
import routingRules from "./routing.json";

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event: FetchEvent): Promise<Response> {
  const request = event.request;
  const userAgent = request.headers.get("user-agent") || "";
  const originalUrl = request.url;

  const aiBot = detectAISearchBot(userAgent);
  if (!aiBot) {
    return fetch(request);
  }

  const targetUrl = generateAIBotUrl(originalUrl, aiBot, { routingRules });

  const newRequest = new Request(targetUrl, request);
  newRequest.headers.set("X-AI-Bot", aiBot);
  newRequest.headers.set("X-Original-URL", originalUrl);
  newRequest.headers.set("X-AI-Optimized", "true");

  return fetch(newRequest);
}
```

## Routing Methods

#### Path Prefix Routing

`/docs/intro` → `/chatgpt/docs/intro`

#### Subdomain Routing

`example.com/page` → `claude.example.com/page`

#### Parameter Routing

`/guide` → `/guide?ai=perplexity`

## Monitoring & Analytics

### Headers

* `X-AI-Bot`
* `X-Original-URL`
* `X-AI-Optimized`

### Log Format

```json
{
  "timestamp": "2025-01-01T12:00:00Z",
  "aiService": "chatgpt",
  "originalUrl": "/article/example",
  "routedUrl": "/chatgpt/article/example",
  "userAgent": "ChatGPT-User/1.0",
  "routingMethod": "path_prefix"
}
```

## Deployment

1. Add files to your Compute\@Edge project
2. Build the package
3. Upload & activate your service
4. Verify routing in staging
5. Deploy to production

That’s it! You have now successfully configured Limy router for your website. Data should begin to populate on your dashboard within an hour.


# Support


# FAQ's

<details>

<summary><strong>What does the tracking pixel do?</strong><br></summary>

The pixel we embed in your website HTML monitors interactions from AI bots and crawlers, including ChatGPT, Gemini, and other LLM-related traffic.

</details>

<details>

<summary><strong>Why is the pixel needed to track AI bots?</strong><br></summary>

The pixel allows us to detect and log real-time visits from AI bots that may not appear in standard analytics tools, providing visibility into how your content is being accessed and referenced.

</details>

<details>

<summary><strong>How will this help my brand or website?</strong><br></summary>

By revealing which pages are fetched by AI and how often, the pixel helps you fine-tune content to improve visibility in AI-generated answers and boost discoverability.

</details>

<details>

<summary><strong>Is personal data being tracked?</strong><br></summary>

No. The pixel only tracks non-human (bot) activity. We do not collect or process any personal or identifiable user data.

</details>

<details>

<summary><strong>What is your privacy policy?</strong><br></summary>

We comply with GDPR and other global privacy regulations. You can review our full privacy policy for details on how the pixel functions and how data is handled.

</details>

<details>

<summary><strong>Will installing the pixel affect my website's performance?</strong><br></summary>

Not at all. The pixel is lightweight and designed to have no impact on page load times or user experience.

</details>

<details>

<summary><strong>How often is pixel data updated?</strong><br></summary>

The pixel logs bot activity continuously, with updates reflected in your dashboard in near real-time for up-to-date analysis.

</details>

<details>

<summary><strong>Can I see which specific pages the bots are visiting?</strong><br></summary>

Yes. The pixel tracks bot visits at the page level, so you can see exactly which URLs are being accessed and how frequently.

</details>

***

For any questions please contact `answers@limy.ai` .


# Reference

## Coming soon.. stay tuned


