Define what “track a coding agent” means
At the server boundary, tracking means observing an eligible request and estimating whether it belongs to machine activity. It does not mean identifying a developer, employer, exact model, or task.
The limitation comes from how coding agents reach the web. Claude Code's CLI can run permitted shell tools. Codex cloud internet access is disabled by default and can be enabled with domain and HTTP-method restrictions. The same OpenAI documentation includes a security example where a Codex shell command reaches a destination with User-Agent: curl/8.5.0.
Cursor Agent has a web-search tool, but its public documentation does not define a stable destination-facing User-Agent. Traffic associated with a Cursor workflow therefore need not contain the word “Cursor.” This is an architectural limitation, not a claim about every Cursor request.
Use three reporting buckets: supported agent evidence, likely machine traffic, and unknown. Product names in a dashboard are classifications, not identity documents.
Put collection on a real Next.js server route
Browser analytics starts after client JavaScript runs. A coding agent may fetch Markdown, llms.txt, an OpenAPI document, or an API endpoint without executing any page script. Collection therefore belongs in the server runtime that handles the request.
Next.js App Router Route Handlers are public HTTP endpoints built on the Web Request and Response APIs. That makes a real Route Handler a clean place to observe a public agent-facing surface. A static export has no request-time server, so this pattern requires a deployment mode where Next.js receives the request.
Next.js 16 renamed Middleware to Proxy. The current Pulse README does not ask you to invent a global Proxy integration; it provides a confirmed withPulse() wrapper for a Route Handler. Use that published contract first. It is easier to verify and keeps the measured surface explicit.
Install Pulse and keep its key server-only
Install the package:
npm install @apostl-dev/pulse-sdk
Set both variables in the deployed server runtime:
APOSTL_PULSE_ENDPOINT=https://ingest.apostl.dev
APOSTL_PULSE_API_KEY=your_server_key
Keep the key outside NEXT_PUBLIC_, public diagnostics, rendered HTML, logs, and Client Components. The Pulse README makes the same boundary explicit because createPulse() reads the server environment directly.
Create lib/pulse.ts:
import 'server-only';
import { createPulse } from '@apostl-dev/pulse-sdk';
export const pulse = createPulse();
The server-only import gives Next.js a build-time guard against accidental client use. The API key still needs ordinary secret management in your deployment platform.
Wrap the public llms.txt Route Handler
Create or update app/llms.txt/route.ts with the current SDK quickstart:
import { withPulse } from '@apostl-dev/pulse-sdk/next';
import { pulse } from '../../lib/pulse';
export const GET = withPulse(
pulse,
async () => new Response('# Agent docs\n', {
headers: { 'content-type': 'text/markdown; charset=utf-8' },
}),
() => ({ surface: 'llms', surfaceName: 'llms-index' }),
);
Wrap the route that actually serves your content. A fake llms.txt added solely to make an analytics chart move gives agents nothing useful. The response should point to accurate, current resources, and the deployed public URL should return the expected content type.
The wrapper observes the response after the handler runs. Pulse treats an exact /llms.txt visit as immediate evidence for an estimated agent journey. The same public SDK pattern can be applied to other supported Route Handlers, but each surface should have a real purpose and a stable name.
Understand what Pulse records and excludes
Pulse records the canonical origin and path for eligible public traffic. Query parameters, fragments, request bodies, cookies, and authorization headers are not sent. The SDK sends trusted client IP and full User-Agent data because those signals participate in journey grouping and classification.
Eligible public health and API GET and HEAD responses are included. Auth and account routes, assets, mutations, and 5xx responses are excluded. Most /api/* routes are ignored unless their public prefix is configured; /api/mcp and /api/turnstile-config are documented safe defaults.
This boundary keeps the dataset focused on public content demand. It also means Pulse is not a replacement for your access logs, error tracker, security telemetry, or authenticated product analytics. If a route returns 500, diagnose it in operational logs. If a signed-in account completes an integration, record that outcome in the product.
Proxy headers need the same care. Use cf-connecting-ip only when the origin accepts traffic through Cloudflare, and configure trust for the proxy or CDN you actually control. Trusting an arbitrary forwarded header lets a client manufacture the network signal used for grouping.
Read classification as evidence, not attribution
Pulse groups eligible requests with the same project, trusted IP, and full User-Agent until a 30-minute inactivity window expires. An exact /llms.txt request qualifies immediately. A generic non-browser client can qualify after it requests two distinct machine-readable surfaces within ten minutes, such as openapi.json and a Markdown page.
The evidence determines the strongest defensible label:
| Evidence at the public server | Defensible report | Unsupported claim |
|---|---|---|
An exact /llms.txt request | Immediate evidence for an estimated agent journey | The exact product, model, developer, employer, or task |
| A generic non-browser client requests two distinct machine-readable surfaces within ten minutes | An estimated journey; for example, demand across openapi.json and a Markdown page | “Codex visited pricing” or another exact product label |
| The request carries stable, documented product evidence | Supported agent evidence and an explicit product label | The private prompt or product UI upstream |
| The request carries weaker or ambiguous evidence | Likely machine traffic or unknown | Confident product attribution |
The official Codex security example reaches the destination through curl. Claude Code can run shell tools, while Cursor exposes web search without documenting a stable destination-facing identifier. Keep raw request count, estimated journey count, page demand, and explicit product labels separate. Page-demand conclusions remain useful even when the source product stays unknown.
Prove the first event before reading the dashboard
A successful deployment is not proof that telemetry arrived. The SDK README provides a bounded verification chain:
- Start the production build with both server environment variables present.
- Request the real public
/llms.txtroute with the deployment's trusted client-IP header and a full User-Agent. - Flush once through a temporary private test hook or graceful shutdown.
- Check
pulse.diagnostics()forconfigured=true,accepted>=1,sent>=1,droppedDelivery=0, andlastError=null.
diagnostics() returns counters, not the API key, captured headers, IP addresses, or request URLs. Keep the diagnostics route protected and temporary because the counters are operational metadata. Remove or disable it after the test.
Record the deployed origin, path, request time, response status, and diagnostics result together. If the dashboard is empty later, that proof separates an ingestion problem from a filter or reporting-window problem.
Turn page demand into one concrete change
Review the public routes with the most estimated agent activity and compare them with the resources agents need to complete common developer tasks. Check whether each route returns useful content without client-only rendering, whether links resolve, and whether the machine-readable version matches the human page.
Choose one change per review: repair a broken schema link, add a missing Markdown representation, clarify an authentication example, or expose a stable llms.txt index. Watch the same routes after the change. A request shift is traffic evidence; it is not proof of task completion or conversion.
Explore Apostl Pulse to add server-side agent traffic analytics to Next.js. The useful outcome is a defensible map of public machine demand, including the requests that remain unknown, not a chart that pretends every generic client announced its name.