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/jsonFor server-side pageview tracking, send these fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Must be the event name |
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 -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/"
}
}'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(() => {});
}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();
});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).*)'],
};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);
},
}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@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 responseclass 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 responseSecurity
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. On Cloudflare, AWS CloudFront, Vercel, Fastly, Netlify, or 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.
