API Integration

Add AI impression tracking to any website or framework with a single server-side API call.

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 — visits from AI crawlers like GPTBot and ClaudeBot. To also track Clicks (real users arriving from AI answers), add the WordPress plugin 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

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

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

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

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.

// 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

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)

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)

@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

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 instead — it handles all of this automatically.