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.