> For the complete documentation index, see [llms.txt](https://docs.airops.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.airops.com/insights/settings/cloudflare-worker-agent-analytics.md).

# Cloudflare Worker Agent Analytics

This guide walks you through configuring Cloudflare to stream into AirOps via **Workers**. Once connected, AirOps automatically analyzes your traffic, classifies AI bot visits (ChatGPT, Claude, Perplexity, Google, and more), and surfaces insights in your Agent Analytics dashboard.

***

## Prerequisites

* An active **AirOps** account with Agent Analytics enabled
* A **Cloudflare** account serving your website that is either at the Enterprise tier or has the Workers Paid add-on
* A development machine with Node.js installed
* Your **Workspace API key**, available in `Settings → Workspace`
* Your **Brand Kit ID**, available in the URL by navigating to `Context → Brand Kits`. The URL takes the form `https://app.airops.com/<YOUR_WORKSPACE_SLUG>/data/brand_kits/<YOUR_BRAND_KIT_ID>`

***

## Workers Batching

The batching integration is the **recommended** approach for most websites. It uses a Cloudflare Queue to buffer per-request log events and flush them to AirOps in batches, keeping you well under the AirOps rate limit even at high traffic volumes.

### Step 1: Initialize the Cloudflare Worker

For this process, we will use version `2.68.2` of the Cloudflare CLI. Other minor versions may work, but there may be differences in the CLI prompts.

{% hint style="info" %}
Depending on your development environment, you may need to manually install the following node packages: `node-addon-api`, `node-gyp`, and `sharp`.
{% endhint %}

```bash
npm create cloudflare@2.68.2
```

Set the directory to `airops-log-collector`.

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

Set the starting example to `Hello World example`.

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

Set the template to `Queue consumer & producer Worker`.

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

Set the language to `TypeScript`.

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

Choose **Yes** for using git for version control.

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

Choose **No** for deploying your application (we will do this later).

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

### Step 2: Configure the Cloudflare Worker Route

Edit the `wrangler.json` file and configure your `pattern`, `zone_name`, queue bindings, and `AIROPS_API_URL`.

{% hint style="info" %}
The `pattern` should be your actual domain or target site URL, while the `zone_name` should be the canonical domain. For example, if your target site is `https://blog.example.com`, the `pattern` should be `blog.example.com` and your `zone_name` should be `example.com`.
{% endhint %}

```json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "airops-log-collector",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-18",
  "observability": {
    "enabled": true
  },
  "queues": {
    "consumers": [
      {
        "queue": "airops-log-collector-queue",
        "max_batch_size": 1000,
        "max_batch_timeout": 60
      }
    ],
    "producers": [
      {
        "binding": "AIROPS_LOG_COLLECTOR_QUEUE",
        "queue": "airops-log-collector-queue"
      }
    ]
  },
  "upload_source_maps": true,
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "route": {
    "pattern": "example.com/*",
    "zone_name": "example.com"
  },
  "vars": {
    "AIROPS_API_URL": "https://ingest.airops.com/api/v1/cloudflare-worker/<YOUR_BRAND_KIT_ID>/ingest"
  }
}
```

### Step 3: Configure the Cloudflare Worker Code

Replace the contents of the `src/index.ts` file with the following code.

```typescript
export interface Env {
  AIROPS_API_URL: string;
  AIROPS_API_KEY: string;
  AIROPS_LOG_COLLECTOR_QUEUE: Queue;
}

export default {
  // The queue producer is invoked on each page visit and saves request metadata to the queue
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Save the time the request started
    const startTime = Date.now();

    // Make the request
    const response = await fetch(request);

    // Clone the response so it can be reused
    const responseClone = response.clone();

    ctx.waitUntil(handleRequest(startTime, request, responseClone, env));
    return response;
  },

  // The queue consumer reads messages from the queue and sends periodic batches to AirOps
  async queue(batch: MessageBatch<string>, env: Env): Promise<void> {
    const body = batch.messages.map(m => m.body).join('\n');

    await fetch(env.AIROPS_API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': env.AIROPS_API_KEY,
      },
      body,
    }).catch(error => console.error('Failed to send logs:', error))
  }
} satisfies ExportedHandler<Env, string>;

async function handleRequest(startTime: number, request: Request, response: Response, env: Env) {
  const logData = {
    request_start_time: startTime,
    request_end_time: Date.now(),
    status_code: response.status,
    response_bytes: response.bytes.length,
    method: request.method,
    url: request.url,
    client_ip: request.headers.get('cf-connecting-ip'),
    user_agent: request.headers.get('user-agent'),
    referer: request.headers.get('referer'),
  };

  await env.AIROPS_LOG_COLLECTOR_QUEUE.send(JSON.stringify(logData));
}
```

### Step 4: Configure the AirOps API Key

{% hint style="info" %}
Your `AIROPS_API_KEY` should be set to the same value as your Workspace API key, available in `Settings → Workspace`.
{% endhint %}

```bash
# Login to Cloudflare
npx wrangler login

# Configure the API key
npx wrangler secret put AIROPS_API_KEY
```

### Step 5: Create the Cloudflare Queue

```bash
npx wrangler queues create airops-log-collector-queue
```

### Step 6: Deploy the Cloudflare Worker

```bash
# Deploy the Cloudflare Worker
npx wrangler deploy
```

### Step 7: Verify the Connection

After deploying the Cloudflare Worker, data should begin flowing within **2-5 minutes**.

1. Generate some traffic to your website.
2. In your **AirOps dashboard**, navigate to **Agent Analytics**. Traffic data should appear shortly.

***

## Workers Alternate

The alternate integration approach sends an event to AirOps on every page visit. This is best suited for websites with fewer page visits to avoid exceeding rate limits and therefore is not recommended for most websites.

### Step 1: Initialize the Cloudflare Worker

For this process, we will use version `2.68.2` of the Cloudflare CLI. Other minor versions may work, but there may be differences in the CLI prompts.

{% hint style="info" %}
Depending on your development environment, you may need to manually install the following node packages: `node-addon-api`, `node-gyp`, and `sharp`.
{% endhint %}

```bash
npm create cloudflare@2.68.2
```

Set the directory to `airops-log-collector`.

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

Set the starting example to `Hello World example`.

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

Set the template to `Worker only`.

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

Set the language to `TypeScript`.

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

Choose **Yes** for using git for version control.

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

Choose **No** for deploying your application (we will do this later).

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

### Step 2: Configure the Cloudflare Worker Route

Edit the `wrangler.json` file and configure your `pattern`, `zone_name`, and `AIROPS_API_URL`.

{% hint style="info" %}
The `pattern` should be your actual domain or target site URL, while the `zone_name` should be the canonical domain. For example, if your target site is `https://blog.example.com`, the `pattern` should be `blog.example.com` and your `zone_name` should be `example.com`.
{% endhint %}

```json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "airops-log-collector",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-18",
  "observability": {
    "enabled": true
  },
  "upload_source_maps": true,
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "route": {
    "pattern": "example.com/*",
    "zone_name": "example.com"
  },
  "vars": {
    "AIROPS_API_URL": "https://ingest.airops.com/api/v1/cloudflare-worker/<YOUR_BRAND_KIT_ID>/ingest"
  }
}
```

### Step 3: Configure the Cloudflare Worker Code

Replace the contents of the `src/index.ts` file with the following code.

```typescript
export interface Env {
  AIROPS_API_URL: string;
  AIROPS_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Save the time the request started
    const startTime = Date.now();

    // Make the request
    const response = await fetch(request);

    // Clone the response so it can be reused
    const responseClone = response.clone();

    ctx.waitUntil(handleRequest(startTime, request, responseClone, env));
    return response;
  }
} satisfies ExportedHandler<Env>;

async function handleRequest(startTime: number, request: Request, response: Response, env: Env) {
	const logData = {
		request_start_time: startTime,
		request_end_time: Date.now(),
		status_code: response.status,
		response_bytes: response.bytes.length,
		method: request.method,
		url: request.url,
		client_ip: request.headers.get('cf-connecting-ip'),
		user_agent: request.headers.get('user-agent'),
		referer: request.headers.get('referer'),
    }

  await fetch(env.AIROPS_API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': env.AIROPS_API_KEY
    },
    body: JSON.stringify(logData)
  }).catch(error => console.error('Failed to send logs:', error))
}
```

### Step 4: Configure the AirOps API Key

{% hint style="info" %}
Your `AIROPS_API_KEY` should be set to the same value as your Workspace API key, available in `Settings → Workspace`.
{% endhint %}

```bash
# Login to Cloudflare
npx wrangler login

# Configure the API key
npx wrangler secret put AIROPS_API_KEY
```

### Step 5: Deploy the Cloudflare Worker

```bash
# Deploy the Cloudflare Worker
npx wrangler deploy
```

### Step 6: Verify the Connection

After deploying the Cloudflare Worker, data should begin flowing within **2-5 minutes**.

1. Generate some traffic to your website.
2. In your **AirOps dashboard**, navigate to **Agent Analytics**. Traffic data should appear shortly.

***

## Rate Limits

The AirOps ingestor enforces a rate limit of **5 requests per minute per API key**. The real-time Worker sends one request per page visit, so this option is best suited for lower-traffic sites. For high-traffic sites, batch requests or use a buffered ingestion option.

***

## What AirOps Tracks

Once connected, AirOps automatically:

* **Ingests all Cloudflare requests**: page views, assets, and API calls
* **Classifies AI bot traffic**: identifies visits from ChatGPT, Claude, Google, Perplexity, Meta, Apple, Bing, Bytedance, Common Crawl, and others
* **Categorizes crawl behavior**: distinguishes between AI training crawlers, AI search fetches, AI indexing, and user-initiated AI browsing
* **Surfaces analytics**: traffic breakdowns, bot-vs-human ratios, and crawl trends in your dashboard


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.airops.com/insights/settings/cloudflare-worker-agent-analytics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
