---
title: "API Integration"
description: "Add AI impression tracking to any website or framework with a single server-side API call."
---

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

# API Integration

Send a `POST` request to the Airefs events endpoint on each page visit. It needs to run server-side — not in the browser — so AI crawlers are captured even when they don’t execute JavaScript. The call is fire-and-forget: don’t await it in your critical path, and swallow any errors silently so a tracking failure never affects your users.

This integration tracks [AI Impressions](/docs/impressions/) — visits from AI crawlers like GPTBot and ClaudeBot. To also track [Clicks](/docs/clicks/) (real users arriving from AI answers), add the [WordPress plugin](/docs/integrations/wordpress/) or the client-side tracking script from **Site Settings → Tracking Script**.

Your access token is in your Airefs account under **Site Settings → Access Token**.

## The request

```http
POST https://api.getairefs.com/v1/events
Authorization: Bearer <your-access-token>
Content-Type: application/json
```

For server-side pageview tracking, send these fields:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | Yes | Must be the event name `“pageview”` |
| `url` | string | Yes | Full URL of the page visited |
| `headers` | object<string, string> | Yes | Incoming request headers as string values — used to identify AI crawlers and referrers |
| `timestamp` | string | No  | ISO 8601 timestamp; defaults to the current time |

Forward the incoming request headers as string key/value pairs. Airefs uses `user-agent` to identify AI crawlers and `referer` to classify traffic sources. The API returns `200` on success, `401` for an invalid access token, `400` for a malformed body, and `500` when analytics ingestion fails.

## Examples

### curl

```bash
curl -X POST https://api.getairefs.com/v1/events \
  -H "Authorization: Bearer <your-access-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "pageview",
    "url": "https://mysite.com/blog/post",
    "headers": {
      "user-agent": "Mozilla/5.0 ...",
      "referer": "https://chatgpt.com/"
    }
  }'
```

### Node.js

```js
async function trackPageView(req) {
  const headers = Object.fromEntries(req.headers.entries());
  fetch('https://api.getairefs.com/v1/events', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.AIREFS_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'pageview',
      url: req.url,
      headers,
    }),
  }).catch(() => {});
}
```

### Express.js

```js
app.use((req, res, next) => {
  const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
  const headers = Object.fromEntries(
    Object.entries(req.headers).flatMap(([key, value]) =>
      typeof value === "string" ? [[key, value]] : Array.isArray(value) ? [[key, value.join(", ")]] : [],
    ),
  );

  res.on('finish', () => {
    fetch('https://api.getairefs.com/v1/events', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.AIREFS_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'pageview',
        url,
        headers,
      }),
    }).catch(() => {});
  });
  next();
});
```

### Next.js Proxy

In Next.js 16 and later, the old `middleware.ts` file convention is now `proxy.ts`.

```ts
// proxy.ts
import { NextResponse } from 'next/server';
import type { NextFetchEvent, NextRequest } from 'next/server';

export function proxy(request: NextRequest, event: NextFetchEvent) {
  const headers = Object.fromEntries(request.headers.entries());
  event.waitUntil(
    fetch('https://api.getairefs.com/v1/events', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.AIREFS_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'pageview',
        url: request.url,
        headers,
      }),
    }).catch(() => {})
  );
  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'],
};
```

### Cloudflare Workers

```js
export default {
  async fetch(request, env, ctx) {
    const headers = Object.fromEntries(request.headers.entries());
    ctx.waitUntil(
      fetch('https://api.getairefs.com/v1/events', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.AIREFS_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          name: 'pageview',
          url: request.url,
          headers,
        }),
      })
    );
    return fetch(request);
  },
}
```

### Python (FastAPI)

```python
async def send_pageview(url: str, headers: dict[str, str]):
    try:
        async with httpx.AsyncClient(timeout=5.0) as client:
            await client.post(
                "https://api.getairefs.com/v1/events",
                headers={
                    "Authorization": f"Bearer {os.environ['AIREFS_TOKEN']}",
                    "Content-Type": "application/json",
                },
                json={
                    "name": "pageview",
                    "url": url,
                    "headers": headers,
                },
            )
    except httpx.HTTPError:
        pass

@app.middleware("http")
async def track_pageview(request: Request, call_next):
    response = await call_next(request)
    asyncio.create_task(send_pageview(str(request.url), dict(request.headers)))
    return response
```

### Python (Flask)

```python
@app.after_request
def track_pageview(response):
    data = {
        "name": "pageview",
        "url": request.url,
        "headers": dict(request.headers),
    }
    thread = threading.Thread(
        target=requests.post,
        args=("https://api.getairefs.com/v1/events",),
        kwargs={
            "json": data,
            "headers": {
                "Authorization": f"Bearer {os.environ['AIREFS_TOKEN']}",
            },
        }
    )
    thread.daemon = True
    thread.start()
    return response
```

### Django middleware

```python
class AirefsMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        thread = threading.Thread(
            target=requests.post,
            args=("https://api.getairefs.com/v1/events",),
            kwargs={
                "json": {
                    "name": "pageview",
                    "url": request.build_absolute_uri(),
                    "headers": dict(request.headers),
                },
                "headers": {
                    "Authorization": f"Bearer {os.environ['AIREFS_TOKEN']}",
                },
            }
        )
        thread.daemon = True
        thread.start()
        return response
```

## Security

Store your access token as an environment variable — never hardcode it in source files or commit it to version control. The server-side call does not set cookies. Airefs uses incoming headers to classify visits, filters sensitive header values from the stored request header payload, and anonymizes human visitor signals before analytics storage.

On WordPress? Use the [WordPress plugin](/docs/integrations/wordpress/) instead — it handles all of this automatically. On [Cloudflare](/docs/integrations/cloudflare/), [AWS CloudFront](/docs/integrations/cloudfront/), [Vercel](/docs/integrations/vercel/), [Fastly](/docs/integrations/fastly/), [Netlify](/docs/integrations/netlify/), or [Akamai](/docs/integrations/akamai/)? Those platform integrations can stream your traffic to Airefs with little or no code. Building a custom log forwarder? Use the batch-friendly [HTTP Log API](/docs/integrations/http-api/).

Source: /docs/integrations/api/
