Send an event
Send events with a single HTTP request. POST a JSON payload with an event_name and your data becomes queryable within seconds. No SDK, no pipeline and no schema to register upfront.
Send your first event
Nothing to install. Create an API key, send your first event and see it appear in the dashboard within seconds.
Settings → API keys → New API key. You use that key to authenticate your requests.
Copy the example below, replace the event_name and properties with your own and send the request.
Your event appears in Events within a few seconds. Once events start flowing, your dashboard panels will populate automatically.
curl -X POST https://useampersand.io/collect \ -H "Authorization: Bearer $AMP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_name": "page_view", "client_id": "usr_8f21c", "context": { "page": { "url": "https://example.com/pricing" } } }'
HTTP/1.1 202 Accepted content-length: 0
Properties
Only event_name is required. Every other field adds a dimension you can filter and break down by. Unknown keys are kept on the event and never reject it.
context.client.user_agent and context.client.ip are derivation inputs only — evaluated once and never stored, logged or shared. Sending them means you are processing your end user’s IP address and user agent, so make sure you have a legal basis for that under the rules that apply to you.
Build your event
Pick the fields you want to send and add only the properties you need. You can start with event_name and add more context as your tracking evolves.
{ "event_name": "page_view", "client_id": "usr_8f21c", "context": { "page": { "url": "https://example.com/pricing", "referrer": "https://news.ycombinator.com/" }, "client": { "ip": "203.0.113.7", "user_agent": "Mozilla/5.0 (Macintosh; …)" } } }
Track crawlers and AI bots
Because crawlers don't run JavaScript, browser-based tracking can't detect them. Send an event from your server instead, including the incoming request's user_agent and ip. Use Ampersand can then distinguish search engines, AI training crawlers, and AI assistants, and tell real crawlers from fake ones.
Crawler classification relies on this value. Without it, the event is counted as a regular visit.
We check crawlers that publish their IP ranges against them, so a fake Googlebot isn’t counted as one.
Crawler events won’t affect visitor or session counts when you leave out client_id.
Send the event in the background so the page can return right away.
Events default to prod, so set the environment explicitly to keep crawlers on staging domains out of your production numbers.
Middleware at the edge runs before your CDN cache, so it can track crawler requests even when the CDN serves a page without sending it to your server.
// middleware.ts (in Next.js 16: proxy.ts, export function proxy) import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server"; const BOTS = /bot|crawl|spider|slurp|ChatGPT-User|Claude-User|Perplexity-User|meta-externalagent|anthropic-ai/i; export function middleware(request: NextRequest, event: NextFetchEvent) { const userAgent = request.headers.get("user-agent") ?? ""; const ip = request.headers.get("x-forwarded-for") ?? undefined; if (request.method === "GET" && BOTS.test(userAgent)) { event.waitUntil( fetch("https://useampersand.io/collect", { method: "POST", headers: { "Authorization": `Bearer ${process.env.AMP_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ event_name: "page_view", environment: "prod", context: { page: { url: request.url }, client: { user_agent: userAgent, ip } } }) }).catch(() => {}) ); } return NextResponse.next(); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };
If you don't use a CDN, add the same check to your app's request handling. Pages served from cache never reach this code, so those crawler visits won't be recorded.
// Express, before your routes // Behind a proxy or load balancer: app.set("trust proxy", true), so req.ip is the visitor const BOTS = /bot|crawl|spider|slurp|ChatGPT-User|Claude-User|Perplexity-User|meta-externalagent|anthropic-ai/i; app.use((req, res, next) => { const userAgent = req.get("user-agent") ?? ""; if (req.method === "GET" && BOTS.test(userAgent)) { fetch("https://useampersand.io/collect", { method: "POST", headers: { "Authorization": `Bearer ${process.env.AMP_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ event_name: "page_view", environment: "prod", context: { page: { url: `${req.protocol}://${req.get("host")}${req.originalUrl}` }, client: { user_agent: userAgent, ip: req.ip } } }) }).catch(() => {}); } next(); });
Once crawler events arrive, the overview shows a Bot traffic card with the share of each group. Hover a segment for its requests and the change against the previous period.
Response codes
Send the same event_id twice and the second call is ignored, so retries are safe.
/collect is pinned to v1 and never moves to a new major version without notice. If you would rather control the version yourself, post to the versioned form instead: POST https://useampersand.io/api/v1/events. Both behave identically today.