# Agent Analytics Docs > Guides, installation pages, reference pages, and the full API reference for Agent Analytics. ## Guides URL: https://docs.agentanalytics.sh/guides/ Task-oriented guides for setting up Agent Analytics and running experiments through your AI agent. Use these guides when you want to tell an AI agent what to do, not manually stitch together the low-level steps yourself. ## Start with the right guide If you need environment-specific setup, use the [installation hub](/installation/). If you need low-level browser or endpoint details, continue to [Tracker.js](/reference/tracker-js/) or the [API reference](/api/). ## Getting Started URL: https://docs.agentanalytics.sh/getting-started/ Create a project, install Agent Analytics in your agent, and verify the first query. This is the shortest path from zero to a working Agent Analytics setup. If your agent is already installed and you want the clearest create-project, place-snippet, and verify-first-page-view walkthrough, use [First Project in 5 Minutes](/guides/first-project-in-5-minutes/). ## 1. Get your API key Sign up at [app.agentanalytics.sh](https://app.agentanalytics.sh) and generate an API key from the dashboard. - Use the API key (`aak_*`) for reads, project management, and agent skill setup. - Use the project token (`aat_*`) in the tracking snippet that goes on your site. ## 2. Pick and complete an install path Go to the [installation hub](/installation/) and complete the setup for the environment you actually use: - [Claude Code](/installation/claude-code/) - [Claude Desktop / Cowork](/installation/claude-desktop-cowork/) - [Cursor](/installation/cursor/) - [OpenClaw](/installation/openclaw/) - [OpenAI Codex](/installation/openai-codex/) If none of those are a fit, the [API reference](/api/) stays available for direct integration. ## 3. Create your first project After your agent is connected, ask it to create the first project for you: - `Create a project called my-site.com` - `Create a project called my-site.com and give me the tracking snippet` If your agent has code write access to your site, ask it: - `Set up analytics for my-site.com` Your agent should create the project and either: - return the tracker snippet for you to paste, or - install the tracker itself if it can edit the site ## 4. Add the tracker manually if needed If your agent already added the tracker to your site, skip this step. If it only created the project and returned a snippet, add the script before ``: ```html ``` Page views are tracked automatically. Add custom events later with `data-aa-event` attributes or `window.aa.track()`. If your site uses Astro, add `is:inline` to that tracker tag. For advanced tracker options like declarative events, cross-domain identity, consent, scroll depth, vitals, and error tracking, use the [Tracker.js guide](/reference/tracker-js/). If your app is a SPA, use [SPA and Virtual Page Tracking](/guides/spa-and-virtual-page-tracking/) to understand what Agent Analytics auto-tracks and when to send a manual virtual `page_view`. If you want your agent to launch your first browser-side A/B test after setup, continue with [AI Agent Experiment Tracking](/guides/ai-agent-experiment-tracking/). ## 5. Verify the loop Once the install is working and the tracker is live, ask your agent one of these: - `List my projects` - `How is my-site doing this week?` - `What are the top pages for my-site this week?` - `Show bot traffic for my-site this week` If the setup is correct, the agent should answer without you hand-writing requests. ## Next - Use [Installation](/installation/) for the fastest per-agent setup. - Use [First Project in 5 Minutes](/guides/first-project-in-5-minutes/) when you want the full activation walkthrough from installed agent to first live project. - Use [SPA and Virtual Page Tracking](/guides/spa-and-virtual-page-tracking/) when your app changes screens client-side and you need accurate page tracking without double-counting. - Use [AI Agent Experiment Tracking](/guides/ai-agent-experiment-tracking/) when you want your agent to launch and read browser-side experiments for you. - Use [Tracker.js](/reference/tracker-js/) for browser-side tracking options. - Use [Bot Traffic](/reference/bot-traffic/) to inspect filtered automated callers separately from normal analytics. - Use [Authentication](/reference/authentication/) when you need the read token vs write token rules. - Use [API Reference](/api/) when you need endpoint-level details. ## First Project in 5 Minutes URL: https://docs.agentanalytics.sh/guides/first-project-in-5-minutes/ Go from installed agent to a live Agent Analytics project with the tracker placed, the first page view verified, and one real analytics question answered. Use this guide when your agent install is already done and you want the clearest path to your first real success loop. If you still need to connect your agent first, go back to the [installation hub](/installation/) or the short [Getting Started](/getting-started/) overview. ## 1. Confirm your agent install is already working Ask your agent: ```text List my Agent Analytics projects and confirm you can reach my account. ``` If the agent cannot see your account yet, stop here and finish the right [installation path](/installation/). ## 2. Create the first project and get the tracker snippet Ask your agent: ```text Create a project called my-site.com and give me the tracking snippet. ``` At this point you want one of two outcomes: - the agent creates the project and returns the tracker snippet for you to paste - the agent creates the project and can place the snippet itself ## 3. Choose the path that matches your setup ### Agent can edit code If your agent can change your site, ask it: ```text Create a project called my-site.com, add the Agent Analytics tracking snippet to the site, and tell me exactly which file you changed. ``` Then ask: ```text Show me where you placed the snippet and confirm it will load on the page I want to track first. ``` If your site uses Astro, tell the agent to add `is:inline` to the tracker ` ``` Required attributes: - `data-project`: your project name - `data-token`: the public project token (`aat_*`) The tracker automatically collects page URL, pathname, referrer, browser, OS, device, language, timezone, UTM params, session count, and first-touch attribution. No cookies are required. Automated traffic that reaches the tracker is filtered out of your normal analytics. Use [Bot Traffic](/reference/bot-traffic/) if you want to inspect those automated requests separately. If your agent can edit code, ask it to add the snippet for you. If not, use [First Project in 5 Minutes](/guides/first-project-in-5-minutes/) to create the project, get the snippet, and verify the first page view. ## Common options | Attribute | What it does | | --- | --- | | `data-link-domains="example.com"` | Link anonymous identity across sibling domains or subdomains | | `data-do-not-track="true"` | Respect the browser Do Not Track signal | | `data-heartbeat="15"` | Measure active time on page while the tab is visible | | `data-track-outgoing="true"` | Track external link clicks as `outgoing_link` | | `data-track-clicks="true"` | Track `` and ` ``` That fires a `signup` event with `{ plan: "pro" }`. This is usually the easiest path for agents too. They can add attributes to existing markup instead of wiring `onclick` handlers or editing application code. ## Impressions Track whether a section was actually seen: ```html
...
``` When the element becomes visible, the tracker sends an `$impression` event. ## `window.aa` API Use the JavaScript API when the event depends on runtime state: ```js window.aa?.track('checkout_started', { plan: 'pro' }); window.aa?.identify('user_123'); window.aa?.set({ plan: 'pro', team: 'acme' }); ``` Useful methods: - `aa.track(event, properties)`: send a custom event - `aa.page(name)`: manually send a page view - `aa.identify(id)`: link anonymous behavior to a known user ID - `aa.set(properties)`: attach global properties to future events - `aa.experiment(name, variants)`: assign variants deterministically client-side - `aa.grantConsent()` / `aa.revokeConsent()`: manage consent mode ## SPA routing and virtual pages Agent Analytics automatically tracks `page_view` when the browser URL changes through `history.pushState()`, `history.replaceState()`, `popstate`, or `hashchange`. That covers most client-side routers, including hash-based SPAs. If your UI changes without changing the path, query string, or hash, the tracker does not try to guess that a new screen appeared. In that case, send a manual `page_view`. Preferred implementation order: 1. Real URL changes 2. Hash routing 3. Manual virtual `page_view` Use `aa.page(name)` when the current browser URL already matches the screen you want to label. It sends a `page_view` with a `page` property, but `path` and `url` still come from the current browser location. For true virtual pages with no URL change, send a manual `page_view` and override the route fields yourself: ```js window.aa?.track('page_view', { page: 'Checkout Step 2', path: '/checkout/step-2', url: `${location.origin}/checkout/step-2` }); ``` Do not combine manual page tracking with router-driven transitions that Agent Analytics already auto-tracks, or you will double-count the same screen change. If you want a prompt-first workflow for deciding between router tracking and manual virtual pages, use [SPA and Virtual Page Tracking](/guides/spa-and-virtual-page-tracking/). ## Common recipes ### Cross-domain identity ```html ``` ### Privacy and consent ```html ``` ```js window.aa?.grantConsent(); window.aa?.revokeConsent(); ``` ### Experiments ```html

Start your free trial

``` If you want the full prompt-first workflow for creating, wiring, QAing, and reading an experiment through your agent, use [AI Agent Experiment Tracking](/guides/ai-agent-experiment-tracking/). ### Local development On localhost, the tracker switches to dev mode and logs activity to the browser console instead of sending production data. That keeps local testing out of your real analytics. ## Related - [Getting Started](/getting-started/) - [First Project in 5 Minutes](/guides/first-project-in-5-minutes/) - [SPA and Virtual Page Tracking](/guides/spa-and-virtual-page-tracking/) - [AI Agent Experiment Tracking](/guides/ai-agent-experiment-tracking/) - [Bot Traffic](/reference/bot-traffic/) - [Authentication](/reference/authentication/) - [API Reference](/api/) ## Bot Traffic URL: https://docs.agentanalytics.sh/reference/bot-traffic/ See which automated callers hit your tracker, how many events were filtered, and which actors generated that traffic. Bot Traffic shows automated requests that reached Agent Analytics and were filtered out of your normal analytics. Use it when you want to answer questions like: - Did ChatGPT or another agent actually hit this site? - Are search crawlers or preview bots touching the tracker? - How many analytics events were dropped because they came from automation? ## Important scope This feature is intentionally narrower than CDN or reverse-proxy logs. - It is **not** full site traffic visibility like Cloudflare. - It only includes automated requests that reached `POST /track` or `POST /track/batch`. - It stores daily aggregates, not raw request logs. - It does **not** store IPs. - It does **not** add bot hits to your normal event analytics, sessions, or billing. ## What you get back Project and account views return: - `automated_requests`: how many filtered tracking requests were received - `dropped_events`: how many analytics events were discarded inside those requests - `categories`: grouped buckets like `ai_agent`, `search_crawler`, `social_preview`, `monitoring_perf` - `actors`: normalized sources like `ChatGPT-User`, `Googlebot`, `ClaudeBot`, `curl` - `time_series`: zero-filled daily rollup for the selected period ## Access paths ### CLI ```bash npx @agent-analytics/cli bot-traffic my-site --period 7d --limit 5 npx @agent-analytics/cli bot-traffic --all --period 7d --limit 10 ``` ### MCP Use: - `bot_traffic_overview` for one project - `all_sites_bot_traffic` for account scope ### API ```bash curl "https://api.agentanalytics.sh/bot-traffic?project=my-site&period=7d&limit=5" \ -H "X-API-Key: aak_..." ``` ```bash curl "https://api.agentanalytics.sh/account/bot-traffic?period=7d&limit=10" \ -H "X-API-Key: aak_..." ``` ## Project vs account scope Use project scope when you want top actors and category breakdown for one site. Use account scope when you want: - total filtered automation across all active projects - which projects are seeing that traffic - a lightweight all-sites overview instead of per-project details Deleted projects are excluded from account rankings and totals. ## Availability Bot traffic overview is available on both hosted free and pro plans through API, CLI, and MCP. ## Related - [Tracker.js](/reference/tracker-js/) - [CLI vs MCP vs API](/reference/cli-mcp-api/) - [Rate Limits](/reference/rate-limits/) - [API Reference](/api/) ## Authentication URL: https://docs.agentanalytics.sh/reference/authentication/ Understand the difference between API keys and project tokens before you wire an agent or a tracker. Agent Analytics uses two different credentials. They serve different jobs and should not be swapped. ## API key (`aak_*`) Use the API key for: - reading analytics data - creating or listing projects - account-level endpoints - direct API access from scripts, tools, and agents Pass it with the `X-API-Key` header or the `?key=` query parameter. ```bash curl "https://api.agentanalytics.sh/stats?project=my-site&since=7d" \ -H "X-API-Key: aak_..." ``` Treat it as secret material. ## Project token (`aat_*`) Use the project token for: - `POST /track` - `POST /track/batch` - the browser tracker snippet embedded on your site The token is public by design. It identifies the project for event ingestion and is expected to appear in HTML. ```html ``` ## Common mistake Do not put the API key in the client-side tracker. The tracker uses the public project token only. ## CLI auth helpers If you use the official CLI, it provides two local auth convenience commands around the API key: - `npx @agent-analytics/cli login --token aak_...` saves the API key locally for later CLI reads. - `npx @agent-analytics/cli logout` clears the saved local CLI auth. `logout` does not revoke the API key on the server. Use `revoke-key` when you want to invalidate the old key and issue a new one. If you set `AGENT_ANALYTICS_API_KEY` in your shell environment, the CLI will continue to use that env var even after `logout` until you unset it. ## Related - [Getting Started](/getting-started/) - [CLI vs MCP vs API](/reference/cli-mcp-api/) - [Error Format](/reference/error-format/) - [API Reference](/api/) ## CLI vs MCP vs API URL: https://docs.agentanalytics.sh/reference/cli-mcp-api/ Agent Analytics can be used through MCP, the CLI, or raw HTTP. Choose the interface that fits the environment your agent already has. Agent Analytics has three real access modes: - `MCP` for chat-native and editor-native tool use - `CLI` for shell-oriented agent workflows - `API` for raw HTTP control The CLI is a convenience wrapper around the same public HTTP API. If an environment is strict about transient package execution or security scanners dislike `npx`, use the API docs directly and keep the same underlying workflows. Which one is best depends on what your agent can already do. ## When to use each ### MCP Use MCP when your agent already runs inside a tool that supports connectors or MCP servers, such as Claude Desktop, Cowork, Cursor, or Claude Code plugin flows. MCP is usually the best fit when: - you want the install to feel native inside chat - you want tool calls instead of shell commands - you do not want to hand-roll auth headers or request payloads - you want quick project or account summaries like `analytics_overview`, `bot_traffic_overview`, or `all_sites_bot_traffic` Tradeoff: - MCP often adds more latency and token overhead than skill + CLI flows because the model has to manage more tool-call round trips and tool result payloads. ### CLI Use the CLI when your agent already has terminal access and is comfortable executing commands. CLI is usually the best fit when: - your agent already lives in a shell-first environment - you want predictable command output - you prefer command composition over tool integration - you want lower overhead than MCP in editor-style agents like Cursor - you want simple local auth helpers like `login` and `logout` around the same API ### API Use the API when you want strict control over requests, retries, and response parsing. API is usually the best fit when: - you are integrating from your own code - you need exact HTTP-level behavior - you are debugging auth or payload shape directly ## CLI to API mapping Most CLI workflows map directly to an HTTP endpoint. The main exception is local auth convenience commands such as `logout`, which only modify local CLI state: | CLI Command | API Endpoint | | --- | --- | | `npx @agent-analytics/cli stats my-site` | `GET /stats?project=my-site` | | `npx @agent-analytics/cli all-sites --period 7d` | `GET /account/all-sites?period=7d` | | `npx @agent-analytics/cli bot-traffic my-site --period 7d` | `GET /bot-traffic?project=my-site&period=7d` | | `npx @agent-analytics/cli bot-traffic --all --period 7d` | `GET /account/bot-traffic?period=7d` | | `npx @agent-analytics/cli events my-site` | `GET /events?project=my-site` | | `npx @agent-analytics/cli query --project my-site --metrics event_count` | `POST /query` | | `npx @agent-analytics/cli funnel my-site --steps "page_view,signup,purchase"` | `POST /funnel` | | `npx @agent-analytics/cli retention my-site --period week --cohorts 8` | `GET /retention?project=my-site&period=week&cohorts=8` | | `npx @agent-analytics/cli experiments list my-site` | `GET /experiments?project=my-site` | | `npx @agent-analytics/cli experiments create my-site --name signup_cta --variants control,new_cta --goal signup` | `POST /experiments` | | `npx @agent-analytics/cli experiments get exp_abc123` | `GET /experiments/{id}` | | `npx @agent-analytics/cli projects` | `GET /projects` | | `npx @agent-analytics/cli logout` | None. Local-only command that clears saved CLI auth and does not call the API. | `logout` clears the API key saved by the CLI on disk. It does not revoke the key on the server. If you exported `AGENT_ANALYTICS_API_KEY` in your shell, the CLI will still authenticate with that environment variable until you unset it. ## Quick rule of thumb - Choose `CLI` first in shell-capable environments like Cursor when the agent can run commands directly. - Choose `MCP` when you specifically want native connector-style tool use or do not have a good shell path. - Choose `API` when you need full control or lower-level debugging. ## Related - [Bot Traffic](/reference/bot-traffic/) - [Installation Overview](/installation/) - [Authentication](/reference/authentication/) - [API Reference](/api/) ## Rate Limits URL: https://docs.agentanalytics.sh/reference/rate-limits/ Hosted Agent Analytics uses account-level API limits and event-volume limits that vary by plan. ## Standard limits | Tier | Requests / minute | Event limit | Data retention | | --- | --- | --- | --- | | Free | 10 | 100,000 / month | 90 days | | Pro | 1,000 | Unlimited | 365 days | Free hosted accounts also get: - up to 2 projects - 500 agent/API read actions per month - MCP access for `list_projects`, `create_project`, `all_sites_overview`, `analytics_overview`, `bot_traffic_overview`, and `all_sites_bot_traffic` Bot traffic overview stays available on both hosted tiers through API, CLI, and MCP. Paid plans unlock the full API, CLI, and MCP surface. Free accounts receive `PRO_REQUIRED` on query, funnels, retention, experiments, and the richer analytics endpoints. ## Streaming limits | Limit | Value | | --- | --- | | Concurrent SSE streams | 10 per account | | `/live` rate | Standard API rate limit | | Inactivity timeout | 30 minutes | | Ring buffer | 5 minutes / 10,000 events | ## What to do if you hit a limit - back off and retry instead of hammering the same endpoint - reduce polling frequency for overview queries - batch ingestion where possible - move experiments and heavier analysis to a pro account if that is the bottleneck ## Related - [Bot Traffic](/reference/bot-traffic/) - [Error Format](/reference/error-format/) - [API Reference](/api/) ## Error Format URL: https://docs.agentanalytics.sh/reference/error-format/ All API errors return a machine-readable code plus a human-readable message. All errors return JSON with an `error` code and a `message`. ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ## Common error codes - `AUTH_REQUIRED` - `FORBIDDEN` - `PROJECT_REQUIRED` - `MISSING_FIELDS` - `RATE_LIMITED` - `INVALID_TOKEN` - `BATCH_TOO_LARGE` - `INVALID_METRIC` - `INVALID_GROUP_BY` - `INVALID_FILTER_OP` - `INVALID_PROPERTY_KEY` - `NOT_FOUND` - `EXPERIMENT_NOT_FOUND` - `EXPERIMENT_NAME_EXISTS` - `INVALID_VARIANTS` - `INVALID_EXPERIMENT_STATUS` - `PRO_REQUIRED` - `STREAM_LIMIT` - `INVALID_FUNNEL_STEPS` ## How to debug 1. check the `error` field first and branch on that in your code 2. use the `message` field for logs and human-readable tool output 3. confirm whether the failure came from auth, a missing project, or invalid query shape before retrying ## Related - [Authentication](/reference/authentication/) - [Rate Limits](/reference/rate-limits/) - [API Reference](/api/) ## API Reference # Agent Analytics API - **OpenAPI Version:** `3.1.0` - **API Version:** `1.0.0` Web analytics platform with an HTTP API that AI agents can query. **Docs:** [Home](https://docs.agentanalytics.sh) · [Getting Started](https://docs.agentanalytics.sh/getting-started/) · [Installation](https://docs.agentanalytics.sh/installation/) · [API Reference](https://docs.agentanalytics.sh/api/) · [OpenAPI Spec](https://docs.agentanalytics.sh/openapi.yaml) **Quick start:** 1. Sign up at [app.agentanalytics.sh](https://app.agentanalytics.sh) and get an API key. 2. Create a project with `POST /projects`. 3. Add the returned tracker snippet to your site. 4. Query analytics with `GET /stats`. **Authentication:** - `aak_*` API keys are secret and used for reads plus project management. - `aat_*` project tokens are public and used for ingestion only. Installation guides for Claude Code, Claude Desktop / Cowork, Cursor, and OpenAI Codex now live in the docs site instead of this OpenAPI intro block. ## Servers - **URL:** `https://api.agentanalytics.sh` - **Description:** Production (hosted) ## Operations ### Track a single event - **Method:** `POST` - **Path:** `/track` - **Tags:** Ingestion Record an event. Called automatically by the `tracker.js` script running in your visitors' browsers — you don't need to call this directly. Add the tracker to your site and it handles everything: ```html ``` The response is `202 Accepted` — events are queued and written asynchronously. Automated traffic that reaches this endpoint is filtered out of normal analytics and reported separately via `/bot-traffic` and `/account/bot-traffic`. #### Request Body ##### Content-Type: application/json - **`event` (required)** `string` — Event name (max 256 chars) - **`token` (required)** `string` — Project token (\`aat\_\*\`) - **`properties`** `object | null` — Arbitrary key-value properties (max 8KB JSON). The tracker auto-populates \`path\`, \`url\`, \`hostname\`, \`title\`, \`referrer\`, \`screen\`, \`browser\`, \`browser\_version\`, \`os\`, \`device\`, \`language\`, \`timezone\` (IANA), \`utm\_\*\` params, \`session\_count\`, \`days\_since\_first\_visit\`, and \`first\_utm\_\*\` (first-touch attribution). - **`session_id`** `string | null` — Session identifier (max 256 chars). The tracker auto-generates one via sessionStorage with 30-min inactivity timeout. - **`timestamp`** `number | null` — Unix timestamp in milliseconds. Defaults to \`Date.now()\`. Must be within 30 days ago to 5 minutes in the future. - **`user_id`** `string | null` — Anonymous user identifier (max 256 chars). The tracker auto-generates one via localStorage. **Example:** ```json { "token": "aat_51da22cdcab084ae1cdb50fd4841c642f0dafdb1d9adfa6b", "event": "page_view", "properties": { "path": "/pricing", "referrer": "https://google.com", "browser": "Chrome", "os": "macOS" }, "user_id": "usr_abc123", "session_id": "ses_xyz789", "timestamp": 1706745600000 } ``` #### Responses ##### Status: 202 Event queued ###### Content-Type: application/json - **`ok`** `boolean` - **`queued`** `boolean` **Example:** ```json { "ok": true, "queued": true } ``` ##### Status: 400 Validation error (missing fields, invalid event name, properties too large) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Invalid or missing token ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 429 Rate limited ###### Content-Type: application/json **All of:** - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message * **`reason`** `string`, possible values: `"rate_limit", "lifetime_event_limit", "monthly_spend_cap"` **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required", "reason": "rate_limit" } ``` ### Track multiple events - **Method:** `POST` - **Path:** `/track/batch` - **Tags:** Ingestion Record up to 100 events in a single request. All events must use the same project token. Like `/track`, this is called by the browser tracker — not by agents or CLI. Automated traffic that reaches this endpoint is filtered out of normal analytics and reported separately via `/bot-traffic` and `/account/bot-traffic`. #### Request Body ##### Content-Type: application/json - **`events` (required)** `array` — Array of events (max 100 per batch) **Items:** - **`event` (required)** `string` - **`token` (required)** `string` — Project token (\`aat\_\*\`). All events must use the same token. - **`properties`** `object | null` - **`session_id`** `string | null` - **`timestamp`** `number | null` - **`user_id`** `string | null` **Example:** ```json { "events": [ { "token": "aat_51da22cdcab084ae1cdb50fd4841c642f0dafdb1d9adfa6b", "event": "page_view", "properties": null, "user_id": null, "session_id": null, "timestamp": null } ] } ``` #### Responses ##### Status: 202 Events queued ###### Content-Type: application/json - **`count`** `integer` - **`ok`** `boolean` - **`queued`** `boolean` **Example:** ```json { "ok": true, "queued": true, "count": 5 } ``` ##### Status: 400 Validation error (empty array, exceeds 100 events, invalid event) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Invalid or missing token ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 429 Rate limited ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Link anonymous user to authenticated identity - **Method:** `POST` - **Path:** `/identify` - **Tags:** Ingestion Maps `previous_id` (typically an anonymous tracker ID like `anon_xxx`) to `user_id` (the authenticated identity). All historical events and sessions with `previous_id` are backfilled to use `user_id`. Called automatically by the tracker's `aa.identify("user_123")` method. Can also be called server-side after authentication to stitch identities. - **Idempotent**: sending the same mapping twice is a no-op - **Re-identify**: updating the mapping triggers a new backfill - **Project-scoped**: only affects events/sessions in the specified project #### Request Body ##### Content-Type: application/json - **`previous_id` (required)** `string` — The anonymous or previous user ID to replace - **`token` (required)** `string` — Project token (\`aat\_\*\`) - **`user_id` (required)** `string` — The authenticated user ID to assign **Example:** ```json { "token": "aat_abc123", "previous_id": "anon_k7x9m2abc", "user_id": "user_12345" } ``` #### Responses ##### Status: 202 Identity mapping queued for processing ###### Content-Type: application/json - **`ok`** `boolean` - **`queued`** `boolean` **Example:** ```json { "ok": true, "queued": true } ``` ##### Status: 400 Validation error (missing fields, same IDs, exceeds length) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Invalid or missing token ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 429 Rate limited ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Aggregated stats overview - **Method:** `GET` - **Path:** `/stats` - **Tags:** Analytics Returns an overview of a project's analytics: time series, top events, session metrics, and totals. Bounce metrics are computed from event names at query time. A session is a bounce when it has only non-interactive events: `page_view`, `$impression`, `$scroll_depth`, `$error`, `$time_on_page`, `$performance`, `$web_vitals`. **CLI:** `npx @agent-analytics/cli stats my-site --since 7d` #### Responses ##### Status: 200 Stats overview ###### Content-Type: application/json - **`events`** `array` — Top event names by count (max 20) **Items:** - **`count`** `integer` - **`event`** `string` - **`unique_users`** `integer` - **`period`** `object` - **`from`** `string` - **`groupBy`** `string`, possible values: `"hour", "day", "week", "month"` - **`to`** `string` - **`project`** `string` - **`sessions`** `object` - **`avg_duration`** `integer` — Average session duration in milliseconds - **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`pages_per_session`** `number` - **`sessions_per_user`** `number` - **`total_sessions`** `integer` - **`timeSeries`** `array` **Items:** - **`bucket`** `string` - **`total_events`** `integer` - **`unique_users`** `integer` - **`totals`** `object` - **`total_events`** `integer` - **`unique_users`** `integer` **Example:** ```json { "project": "my-site", "period": { "from": "2025-01-01", "to": "2025-01-07", "groupBy": "day" }, "totals": { "unique_users": 142, "total_events": 1247 }, "timeSeries": [ { "bucket": "2025-01-01", "unique_users": 1, "total_events": 1 } ], "events": [ { "event": "page_view", "count": 1, "unique_users": 1 } ], "sessions": { "total_sessions": 1, "bounce_rate": 0.45, "avg_duration": 1, "pages_per_session": 1, "sessions_per_user": 1 } } ``` ##### Status: 400 Missing project parameter ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Raw event log - **Method:** `GET` - **Path:** `/events` - **Tags:** Analytics Returns individual events, newest first. Filter by event name and/or session ID. **CLI:** `npx @agent-analytics/cli events my-site --event page_view --since 7d --limit 50` #### Responses ##### Status: 200 Event list ###### Content-Type: application/json - **`events`** `array` **Items:** - **`country`** `string | null` — ISO 3166-1 alpha-2 country code derived from the request IP at ingestion time - **`date`** `string` - **`event`** `string` - **`id`** `string` - **`project_id`** `string` - **`properties`** `object | null` - **`session_id`** `string | null` - **`timestamp`** `number` - **`user_id`** `string | null` - **`project`** `string` **Example:** ```json { "project": "", "events": [ { "id": "", "project_id": "", "event": "", "properties": null, "user_id": null, "session_id": null, "timestamp": 1, "date": "", "country": "US" } ] } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Flexible analytics query - **Method:** `POST` - **Path:** `/query` - **Tags:** Analytics Run custom analytics queries with metrics, grouping, filtering, and ordering. This is the most powerful endpoint for building custom reports. **Metrics:** `event_count`, `unique_users`, `session_count`, `bounce_rate`, `avg_duration` **Group by:** `event`, `date`, `user_id`, `session_id`, `country` **Filter operators:** `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `contains` **Filterable fields:** `event`, `user_id`, `date`, `country`, `session_id`, `timestamp`, and any `properties.*` field (e.g. `properties.path`, `properties.browser`) **CLI:** `npx @agent-analytics/cli query --project my-site --metrics event_count --group-by event --filter '[{"field":"country","op":"eq","value":"US"}]'` #### Request Body ##### Content-Type: application/json - **`project` (required)** `string` — Project name - **`date_from`** `string` — Start date (ISO 8601 or \`Nd\` shorthand). Defaults to 7 days ago. - **`date_to`** `string` — End date (ISO 8601). Defaults to today. - **`filters`** `array` **Items:** - **`field`** `string` — Field to filter on. Built-in: \`event\`, \`user\_id\`, \`date\`, \`country\`, \`session\_id\`, \`timestamp\`. Property fields: \`properties.path\`, \`properties.browser\`, etc. - **`op`** `string`, possible values: `"eq", "neq", "gt", "lt", "gte", "lte", "contains"` - **`value`** `object` — Value to compare against - **`group_by`** `array`, default: `[]` — Fields to group results by **Items:** `string`, possible values: `"event", "date", "user_id", "session_id", "country"` - **`limit`** `integer`, default: `100` - **`metrics`** `array`, default: `["event_count"]` — Metrics to compute **Items:** `string`, possible values: `"event_count", "unique_users", "session_count", "bounce_rate", "avg_duration"` - **`order`** `string`, possible values: `"asc", "desc"`, default: `"desc"` - **`order_by`** `string`, possible values: `"event_count", "unique_users", "session_count", "date", "event"` — Field to sort by **Example:** ```json { "project": "my-site", "metrics": [ "event_count" ], "group_by": [], "filters": [ { "field": "event", "op": "eq", "value": "page_view" } ], "date_from": "2025-01-01", "date_to": "2025-01-07", "order_by": "event_count", "order": "desc", "limit": 100 } ``` #### Responses ##### Status: 200 Query results ###### Content-Type: application/json - **`count`** `integer` - **`group_by`** `array` **Items:** `string` - **`metrics`** `array` **Items:** `string` - **`period`** `object` - **`from`** `string` - **`to`** `string` - **`project`** `string` - **`rows`** `array` **Items:** **Example:** ```json { "project": "", "period": { "from": "", "to": "" }, "metrics": [ "" ], "group_by": [ "" ], "rows": [ { "additionalProperty": "anything" } ], "count": 1 } ``` ##### Status: 400 Invalid query (bad metric, group\_by, filter, or missing project) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Discover event names and property keys - **Method:** `GET` - **Path:** `/properties` - **Tags:** Analytics Returns all event names with counts, and a list of known property keys from recent events. Useful for building dynamic filters and understanding what data is available. **CLI:** `npx @agent-analytics/cli properties my-site` #### Responses ##### Status: 200 Event names and property keys ###### Content-Type: application/json - **`events`** `array` **Items:** - **`count`** `integer` - **`event`** `string` - **`first_seen`** `string` - **`last_seen`** `string` - **`unique_users`** `integer` - **`project`** `string` - **`property_keys`** `array` **Items:** `string` **Example:** ```json { "project": "", "events": [ { "event": "page_view", "count": 1, "unique_users": 1, "first_seen": "", "last_seen": "" } ], "property_keys": [ "browser", "device", "hostname", "language", "os", "path", "referrer", "screen", "title", "url" ] } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Property keys by event name - **Method:** `GET` - **Path:** `/properties/received` - **Tags:** Analytics Returns which property keys appear on which event names, sampled from recent events. More detailed than `/properties` — shows the event-to-property mapping. #### Responses ##### Status: 200 Property-to-event mapping ###### Content-Type: application/json - **`project`** `string` - **`properties`** `array` **Items:** - **`event`** `string` - **`key`** `string` - **`sample_size`** `integer` - **`since`** `string` **Example:** ```json { "project": "", "sample_size": 5000, "since": "", "properties": [ { "key": "path", "event": "page_view" } ] } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### List sessions - **Method:** `GET` - **Path:** `/sessions` - **Tags:** Analytics Returns individual session records with duration, entry/exit pages, event count, and bounce status. Filter by user ID and/or bounce status. `is_bounce` is computed from event names at query time. `1` means the session has only non-interactive events: `page_view`, `$impression`, `$scroll_depth`, `$error`, `$time_on_page`, `$performance`, `$web_vitals`. #### Responses ##### Status: 200 Session list ###### Content-Type: application/json - **`project`** `string` - **`sessions`** `array` **Items:** - **`date`** `string` - **`duration`** `integer` — Duration in milliseconds - **`end_time`** `number` - **`entry_page`** `string | null` - **`event_count`** `integer` - **`exit_page`** `string | null` - **`is_bounce`** `integer`, possible values: `0, 1` — Computed from event names at query time. \`1\` means the session has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`project_id`** `string` - **`session_id`** `string` - **`start_time`** `number` - **`user_id`** `string | null` **Example:** ```json { "project": "", "sessions": [ { "session_id": "", "user_id": null, "project_id": "", "start_time": 1, "end_time": 1, "duration": 1, "entry_page": null, "exit_page": null, "event_count": 1, "is_bounce": 0, "date": "" } ] } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Top property values - **Method:** `GET` - **Path:** `/breakdown` - **Tags:** Analytics Breaks down events by a specific property, showing the top values by count. Useful for top pages, referrers, browsers, countries, UTM sources, etc. Use `property=country` to see visitor geography (ISO 3166-1 alpha-2 codes). Country is stored as a dedicated column for fast queries. **CLI:** `npx @agent-analytics/cli breakdown my-site --property path --event page_view` #### Responses ##### Status: 200 Property breakdown ###### Content-Type: application/json - **`event`** `string | null` - **`project`** `string` - **`property`** `string` - **`total_events`** `integer` - **`total_with_property`** `integer` - **`values`** `array` **Items:** - **`count`** `integer` - **`unique_users`** `integer` - **`value`** `string` **Example:** ```json { "project": "", "property": "path", "event": null, "values": [ { "value": "/pricing", "count": 342, "unique_users": 201 } ], "total_events": 1, "total_with_property": 1 } ``` ##### Status: 400 Missing property parameter or invalid property key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Period-over-period comparison - **Method:** `GET` - **Path:** `/insights` - **Tags:** Analytics Compares the current period against the previous period of the same length. Returns change metrics and an overall trend indicator (`growing`, `stable`, `declining`). `bounce_rate` is computed from event names at query time. A session is a bounce when it has only non-interactive events: `page_view`, `$impression`, `$scroll_depth`, `$error`, `$time_on_page`, `$performance`, `$web_vitals`. **CLI:** `npx @agent-analytics/cli insights my-site --period 7d` #### Responses ##### Status: 200 Insights with trend ###### Content-Type: application/json - **`current_period`** `object` - **`from`** `string` - **`to`** `string` - **`metrics`** `object` - **`avg_duration`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`bounce_rate`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`total_events`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`total_sessions`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`unique_users`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`previous_period`** `object` - **`from`** `string` - **`to`** `string` - **`project`** `string` - **`trend`** `string`, possible values: `"growing", "stable", "declining"` **Example:** ```json { "project": "", "current_period": { "from": "", "to": "" }, "previous_period": { "from": "", "to": "" }, "metrics": { "total_events": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "unique_users": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "total_sessions": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "bounce_rate": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "avg_duration": { "current": 1, "previous": 1, "change": 1, "change_pct": null } }, "trend": "growing" } ``` ##### Status: 400 Invalid period ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Entry/exit page stats - **Method:** `GET` - **Path:** `/pages` - **Tags:** Analytics Returns top entry pages, exit pages, or both — with session count, bounce rate, and average duration per page. `bounce_rate` is computed from event names at query time. A session is a bounce when it has only non-interactive events: `page_view`, `$impression`, `$scroll_depth`, `$error`, `$time_on_page`, `$performance`, `$web_vitals`. **CLI:** `npx @agent-analytics/cli pages my-site --type entry` #### Responses ##### Status: 200 Page stats ###### Content-Type: application/json - **`entry_pages`** `array` **Items:** - **`avg_duration`** `number` — Average session duration in milliseconds - **`avg_events`** `number` - **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`bounces`** `integer` - **`page`** `string` - **`sessions`** `integer` - **`exit_pages`** `array` **Items:** - **`avg_duration`** `number` — Average session duration in milliseconds - **`avg_events`** `number` - **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`bounces`** `integer` - **`page`** `string` - **`sessions`** `integer` - **`project`** `string` **Example:** ```json { "project": "", "entry_pages": [ { "page": "/pricing", "sessions": 1, "bounces": 1, "bounce_rate": 0.45, "avg_duration": 1, "avg_events": 1 } ], "exit_pages": [ { "page": "/pricing", "sessions": 1, "bounces": 1, "bounce_rate": 0.45, "avg_duration": 1, "avg_events": 1 } ] } ``` ##### Status: 400 Invalid page type ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Session duration histogram - **Method:** `GET` - **Path:** `/sessions/distribution` - **Tags:** Analytics Returns a histogram of session durations in predefined buckets, with the median bucket and engaged session percentage (sessions >= 30 seconds). `bounces` in each bucket are computed from event names at query time. A session is a bounce when it has only non-interactive events: `page_view`, `$impression`, `$scroll_depth`, `$error`, `$time_on_page`, `$performance`, `$web_vitals`. **CLI:** `npx @agent-analytics/cli sessions distribution my-site` #### Responses ##### Status: 200 Session duration distribution ###### Content-Type: application/json - **`distribution`** `array` **Items:** - **`avg_events`** `number` - **`bounces`** `integer` — Number of bounce sessions in this duration bucket (computed from event names at query time). - **`bucket`** `string`, possible values: `"0s", "1-10s", "10-30s", "30-60s", "1-3m", "3-10m", "10m+"` - **`pct`** `number` — Percentage of total sessions - **`sessions`** `integer` - **`engaged_pct`** `number` — Percentage of sessions >= 30 seconds - **`median_bucket`** `string | null` - **`project`** `string` **Example:** ```json { "project": "", "distribution": [ { "bucket": "0s", "sessions": 1, "bounces": 1, "avg_events": 1, "pct": 1 } ], "median_bucket": null, "engaged_pct": 1 } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Day-of-week x hour traffic grid - **Method:** `GET` - **Path:** `/heatmap` - **Tags:** Analytics Returns a grid of events by day-of-week and hour-of-day (UTC), with peak detection. Useful for understanding when your users are most active. **CLI:** `npx @agent-analytics/cli heatmap my-site` #### Responses ##### Status: 200 Heatmap grid ###### Content-Type: application/json - **`busiest_day`** `string | null` - **`busiest_hour`** `integer | null` - **`heatmap`** `array` **Items:** - **`day`** `integer` — Day of week (0=Sunday, 6=Saturday) - **`day_name`** `string` - **`events`** `integer` - **`hour`** `integer` — Hour of day (0-23) - **`users`** `integer` - **`peak`** `object | null` - **`day`** `integer` - **`day_name`** `string` - **`events`** `integer` - **`hour`** `integer` - **`users`** `integer` - **`project`** `string` **Example:** ```json { "project": "", "heatmap": [ { "day": 1, "day_name": "Monday", "hour": 1, "events": 1, "users": 1 } ], "peak": { "day": 1, "day_name": "", "hour": 1, "events": 1, "users": 1 }, "busiest_day": "Monday", "busiest_hour": 14 } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Funnel analysis - **Method:** `POST` - **Path:** `/funnel` - **Tags:** Funnels Ad-hoc funnel analysis: track where users drop off across a sequence of 2-8 events. Each step can have optional property filters (e.g. only count `page_view` events where `path=/pricing`). The conversion window limits how long users have from entering step 1 to completing the final step. Step 1 is capped at 10,000 users to prevent unbounded scans. **CLI:** `npx @agent-analytics/cli funnel my-site --steps "page_view,signup,purchase" --window 168 --breakdown country` #### Request Body ##### Content-Type: application/json - **`project` (required)** `string` — Project name - **`steps` (required)** `array` — Funnel steps (2-8). Each step has an event name and optional property filters. **Items:** - **`event` (required)** `string` — Event name for this step - **`filters`** `array` — Optional property filters for this step **Items:** - **`op` (required)** `string`, possible values: `"eq", "neq", "contains"` — Filter operator - **`property` (required)** `string` — Property key (alphanumeric + underscores, max 128 chars) - **`value` (required)** `string` — Filter value - **`breakdown`** `string` — Optional property key to segment funnel by (e.g. 'variant', 'country'). Extracted from step 1 events only. - **`breakdown_limit`** `number`, default: `10` — Max breakdown groups, ordered by step 1 users descending (1-50, default 10) - **`conversion_window_hours`** `number`, default: `168` — Max hours from step 1 entry to final step (1-8760, default 168 = 7 days) - **`count_by`** `string`, possible values: `"user_id", "session_id"`, default: `"user_id"` — Count by unique users or sessions - **`since`** `string`, default: `"30d"` — Lookback period (ISO date or shorthand like '30d'). Default: 30d **Example:** ```json { "project": "my-site", "steps": [ { "event": "page_view", "filters": [ { "property": "path", "op": "eq", "value": "/pricing" } ] } ], "conversion_window_hours": 168, "since": "30d", "count_by": "user_id", "breakdown": "country", "breakdown_limit": 10 } ``` #### Responses ##### Status: 200 Funnel analysis results ###### Content-Type: application/json - **`breakdowns`** `array` — Per-group funnel results (only present when \`breakdown\` param is set). Ordered by step 1 users descending. **Items:** - **`overall_conversion_rate`** `number` — End-to-end conversion for this group - **`steps`** `array` — Same shape as top-level steps array **Items:** - **`avg_time_to_next_ms`** `number | null` - **`conversion_rate`** `number` - **`drop_off_rate`** `number` - **`event`** `string` - **`step`** `integer` - **`users`** `integer` - **`value`** `string | null` — Breakdown property value (null for events missing the property) - **`overall_conversion_rate`** `number` — Fraction of step 1 users that completed the final step - **`steps`** `array` **Items:** - **`avg_time_to_next_ms`** `number | null` — Average time in ms from this step to the next step (null for last step) - **`conversion_rate`** `number` — Fraction of users from previous step that reached this step (step 1 is always 1.0) - **`drop_off_rate`** `number` — Fraction of users lost from previous step (step 1 is always 0) - **`event`** `string` - **`step`** `integer` - **`users`** `integer` — Number of users (or sessions) that reached this step **Example:** ```json { "steps": [ { "step": 1, "event": "page_view", "users": 500, "conversion_rate": 0.6, "drop_off_rate": 0.4, "avg_time_to_next_ms": 3600000 } ], "overall_conversion_rate": 0.12, "breakdowns": [ { "value": "US", "steps": [ { "step": 1, "event": "", "users": 1, "conversion_rate": 1, "drop_off_rate": 1, "avg_time_to_next_ms": null } ], "overall_conversion_rate": 0.15 } ] } ``` ##### Status: 400 Validation error (invalid steps, filters, window, or count\_by) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Cohort retention analysis - **Method:** `GET` - **Path:** `/retention` - **Tags:** Analytics Track what percentage of users return over time using cohort analysis. "Of users who first appeared in week X, what % came back in subsequent weeks?" By default uses session-based retention — a user is "retained" if they have any return visit (session) in a subsequent period. When `event` is specified, switches to event-based retention and only counts that specific event for first-seen and return. Capped at 10,000 users per query. Older cohorts appear first (top-down). **CLI:** `npx @agent-analytics/cli retention my-site --period week --cohorts 8` #### Responses ##### Status: 200 Cohort retention data ###### Content-Type: application/json - **`average_rates`** `array` — Weighted average retention rate per period offset (weighted by cohort size) **Items:** `number` - **`cohorts`** `array` **Items:** - **`date`** `string`, format: `date` — Start date of this cohort period - **`rates`** `array` — Retention rate per period (retained\[i] / users, rounded to 3 decimals) **Items:** `number` - **`retained`** `array` — Users active in each subsequent period (index 0 = full cohort) **Items:** `integer` - **`users`** `integer` — Number of users who first appeared in this period - **`period`** `string`, possible values: `"day", "week", "month"` - **`users_analyzed`** `integer` — Total users across all cohorts (capped at 10K per query) **Example:** ```json { "period": "week", "cohorts": [ { "date": "2026-01-27", "users": 142, "retained": [ 142, 64, 55, 46 ], "rates": [ 1, 0.451, 0.387, 0.324 ] } ], "average_rates": [ 1, 0.441, 0.373, 0.324 ], "users_analyzed": 560 } ``` ##### Status: 400 Invalid period or cohorts value ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Live event stream (SSE) - **Method:** `GET` - **Path:** `/stream` - **Tags:** Streaming Server-Sent Events stream of real-time events as they arrive. Events are delivered as `event: track` SSE messages. Optionally filter by event name and/or property values. The stream sends a `:heartbeat` comment every 30 seconds as a keepalive. Auto-disconnects after 30 minutes of no matching events. **curl example:** ```bash curl -N "https://api.agentanalytics.sh/stream?project=my-site&events=page_view,signup" \ -H "X-API-Key: aak_..." ``` #### Responses ##### Status: 200 SSE event stream ###### Content-Type: text/event-stream `string` — SSE stream with events: - \`event: connected\` — sent once on connection with active filters - \`event: track\` — each matching event with \`id\` for reconnection - \`:heartbeat\` — keepalive comment every 30s **Example:** ```json true ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 429 Too many concurrent streams (max 10 per account) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Live snapshot (real-time) - **Method:** `GET` - **Path:** `/live` - **Tags:** Streaming Returns a point-in-time snapshot of active visitors, sessions, events per minute, top pages, top events, and recent events. Reads from an in-memory ring buffer — no D1 query. Data is available only while events are being streamed (the ring buffer evicts events older than 5 minutes). **curl example:** ```bash curl "https://api.agentanalytics.sh/live?project=my-site&window=60" \ -H "X-API-Key: aak_..." ``` #### Responses ##### Status: 200 Live snapshot ###### Content-Type: application/json - **`active_sessions`** `integer` — Unique session\_ids in the time window - **`active_visitors`** `integer` — Unique user\_ids in the time window - **`events_per_minute`** `integer` - **`project`** `string` - **`recent_events`** `array` — Last 10 events, newest first **Items:** - **`event`** `string` - **`properties`** `object | null` - **`timestamp`** `number` - **`user_id`** `string | null` - **`timestamp`** `number` - **`top_events`** `array` **Items:** - **`count`** `integer` - **`event`** `string` - **`top_pages`** `array` **Items:** - **`path`** `string` - **`visitors`** `integer` - **`window_seconds`** `integer` **Example:** ```json { "project": "my-site", "window_seconds": 60, "timestamp": 1708000060000, "active_visitors": 12, "active_sessions": 8, "events_per_minute": 47, "top_pages": [ { "path": "/", "visitors": 5 } ], "top_events": [ { "event": "page_view", "count": 32 } ], "recent_events": [ { "event": "", "properties": null, "user_id": null, "timestamp": 1 } ] } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### List all projects - **Method:** `GET` - **Path:** `/projects` - **Tags:** Projects Returns all projects under your account with event/read counts and tier info. **CLI:** `npx @agent-analytics/cli projects` #### Responses ##### Status: 200 Project list ###### Content-Type: application/json - **`has_api_key`** `boolean` - **`projects`** `array` **Items:** - **`allowed_origins`** `string` - **`id`** `string` - **`name`** `string` - **`project_token`** `string` - **`total_events`** `integer` - **`total_reads`** `integer` - **`tier`** `string`, possible values: `"free", "pro"` **Example:** ```json { "projects": [ { "id": "", "name": "my-site", "project_token": "aat_...", "allowed_origins": "*", "total_events": 1, "total_reads": 1 } ], "has_api_key": true, "tier": "free" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Create a new project - **Method:** `POST` - **Path:** `/projects` - **Tags:** Projects Creates a new project and provisions a D1 database if needed. Returns the project token and a ready-to-use tracking snippet. Project names must be alphanumeric with hyphens, underscores, or dots (max 64 chars). If a project with the same name already exists, returns the existing project (idempotent). **CLI:** `npx @agent-analytics/cli init my-new-site` #### Request Body ##### Content-Type: application/json - **`name` (required)** `string` — Project name (alphanumeric, hyphens, underscores, dots; max 64 chars) - **`allowed_origins`** `string`, default: `"*"` — Allowed origins for CORS. Use \`\*\` for any origin. **Example:** ```json { "name": "my-new-site", "allowed_origins": "https://example.com" } ``` #### Responses ##### Status: 200 Project already exists (returned existing project) ###### Content-Type: application/json - **`existing`** `boolean` - **`id`** `string` - **`name`** `string` - **`project_token`** `string` **Example:** ```json { "id": "", "name": "", "project_token": "", "existing": true } ``` ##### Status: 201 Project created ###### Content-Type: application/json - **`allowed_origins`** `string` - **`api_example`** `string` — Example curl command for querying stats - **`id`** `string` - **`name`** `string` - **`project_token`** `string` - **`snippet`** `string` — Ready-to-use HTML tracking snippet **Example:** ```json { "id": "", "name": "", "project_token": "aat_...", "allowed_origins": "", "snippet": "", "api_example": "" } ``` ##### Status: 400 Missing name or invalid project name ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 409 Duplicate origin ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Get project details - **Method:** `GET` - **Path:** `/projects/{id}` - **Tags:** Projects Returns a single project with its configuration and today's usage stats. #### Responses ##### Status: 200 Project details ###### Content-Type: application/json - **`allowed_origins`** `string` - **`id`** `string` - **`name`** `string` - **`project_token`** `string` - **`usage_today`** `object` **Example:** ```json { "id": "", "name": "", "project_token": "", "allowed_origins": "", "usage_today": {} } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 403 Not your project ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Update a project - **Method:** `PATCH` - **Path:** `/projects/{id}` - **Tags:** Projects Update a project's name and/or allowed origins. **Note:** Project name cannot be changed after events have been recorded. #### Request Body ##### Content-Type: application/json - **`allowed_origins`** `string` — New allowed origins - **`name`** `string` — New project name **Example:** ```json { "name": "", "allowed_origins": "" } ``` #### Responses ##### Status: 200 Updated project ###### Content-Type: application/json - **`allowed_origins`** `string` - **`id`** `string` - **`name`** `string` - **`project_token`** `string` **Example:** ```json { "id": "", "name": "", "project_token": "", "allowed_origins": "" } ``` ##### Status: 400 Invalid name or no fields to update ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 409 Name locked (events recorded) or duplicate name/origin ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Delete a project - **Method:** `DELETE` - **Path:** `/projects/{id}` - **Tags:** Projects Soft-deletes a project. Revokes the project token (no new events can be written). Event data is preserved in the database but no longer accessible via API. #### Responses ##### Status: 200 Project deleted ###### Content-Type: application/json - **`deleted`** `string` — Project ID - **`ok`** `boolean` **Example:** ```json { "ok": true, "deleted": "" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 403 Not your project ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Get account info - **Method:** `GET` - **Path:** `/account` - **Tags:** Account Returns your account details, tier, limits, and project count. **CLI:** `npx @agent-analytics/cli whoami` #### Responses ##### Status: 200 Account info ###### Content-Type: application/json - **`created_at`** `string` - **`email`** `string` - **`github_login`** `string | null` - **`id`** `string` - **`monthly_spend_cap_dollars`** `number | null` - **`projects_count`** `integer` - **`tier`** `string`, possible values: `"free", "pro"` - **`tier_limits`** `object` - **`max_events_per_month`** `integer` - **`max_projects`** `integer` - **`max_reads_per_month`** `integer` - **`rate_limit_rpm`** `integer` - **`retention_days`** `integer` **Example:** ```json { "id": "acc_free123", "email": "free@example.com", "github_login": "freeuser", "tier": "free", "created_at": "2026-02-01T12:00:00.000Z", "projects_count": 2, "tier_limits": { "max_events_per_month": 100000, "max_projects": 2, "max_reads_per_month": 500, "retention_days": 90, "rate_limit_rpm": 10 }, "monthly_spend_cap_dollars": null } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Filtered automated traffic for one project - **Method:** `GET` - **Path:** `/bot-traffic` - **Tags:** Analytics Returns a project-scoped summary of automated traffic that reached Agent Analytics and was filtered out of normal analytics. This endpoint reports daily aggregates only: automated request count, dropped event count, category breakdown, top actors, and a zero-filled time series for the selected period. This is **not** full site bot visibility like CDN logs. It only includes automated requests that actually reached `POST /track` or `POST /track/batch`. **CLI:** `npx @agent-analytics/cli bot-traffic my-site --period 7d --limit 5` **MCP:** `bot_traffic_overview` #### Responses ##### Status: 200 Project bot traffic overview ###### Content-Type: application/json - **`actors`** `array` **Items:** - **`actor`** `string` - **`category`** `string` - **`dropped_events`** `integer` - **`last_seen_at`** `object` - **`requests`** `integer` - **`categories`** `array` **Items:** - **`category`** `string` - **`dropped_events`** `integer` - **`requests`** `integer` - **`share_pct`** `number` - **`period`** `object` - **`from`** `string`, format: `date` - **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` - **`previous_from`** `string`, format: `date` - **`previous_to`** `string`, format: `date` - **`to`** `string`, format: `date` - **`project`** `string` - **`scope`** `string`, possible values: `"project"` - **`summary`** `object` - **`automated_requests`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`dropped_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`last_seen_at`** `object` - **`time_series`** `array` **Items:** - **`date`** `string`, format: `date` - **`dropped_events`** `integer` - **`requests`** `integer` **Example:** ```json { "scope": "project", "project": "my-site", "period": { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" }, "summary": { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "last_seen_at": 1773459600000 }, "categories": [ { "category": "ai_agent", "requests": 3, "dropped_events": 5, "share_pct": 60 } ], "actors": [ { "actor": "ChatGPT-User", "category": "ai_agent", "requests": 3, "dropped_events": 5, "last_seen_at": 1773459600000 } ], "time_series": [ { "date": "2026-03-13", "requests": 5, "dropped_events": 7 } ] } ``` ##### Status: 400 Invalid period or missing project ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found or unauthorized ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Historical all-sites overview - **Method:** `GET` - **Path:** `/account/all-sites` - **Tags:** Account Returns a lightweight historical summary across all active projects in your account. This endpoint is optimized for a single account-level dashboard view: total event trend, active project count, daily event time series, and a top-project list. Use `/live` for real-time account-wide activity. Use project-scoped endpoints like `/stats` and `/insights` when you want deeper analysis for one project. **CLI:** `npx @agent-analytics/cli all-sites --period 7d` #### Responses ##### Status: 200 Account-wide historical overview ###### Content-Type: application/json - **`period`** `object` - **`from`** `string`, format: `date` - **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` - **`previous_from`** `string`, format: `date` - **`previous_to`** `string`, format: `date` - **`to`** `string`, format: `date` - **`projects`** `array` **Items:** - **`events`** `integer` - **`id`** `string` - **`last_active_date`** `object` - **`name`** `string` - **`share_pct`** `number` - **`remaining_projects`** `integer` - **`scope`** `string`, possible values: `"account"` - **`summary`** `object` - **`active_projects`** `integer` - **`total_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`total_projects`** `integer` - **`time_series`** `array` **Items:** - **`date`** `string`, format: `date` - **`events`** `integer` **Example:** ```json { "scope": "account", "period": { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" }, "summary": { "total_projects": 3, "active_projects": 2, "total_events": { "current": 120, "previous": 90, "change": 30, "change_pct": 33 } }, "time_series": [ { "date": "2026-03-09", "events": 40 } ], "projects": [ { "id": "proj_123", "name": "my-site", "events": 80, "share_pct": 66.7, "last_active_date": "2026-03-09" } ], "remaining_projects": 1 } ``` ##### Status: 400 Invalid period ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Filtered automated traffic across all active projects - **Method:** `GET` - **Path:** `/account/bot-traffic` - **Tags:** Account Returns an account-scoped summary of automated traffic that reached Agent Analytics and was filtered out of normal analytics. This endpoint is optimized for a lightweight all-sites view: total filtered requests, dropped events, active-project count, top-project list, category breakdown, and a zero-filled daily time series. Deleted projects are excluded from the totals and rankings. **CLI:** `npx @agent-analytics/cli bot-traffic --all --period 7d --limit 10` **MCP:** `all_sites_bot_traffic` #### Responses ##### Status: 200 Account-wide bot traffic overview ###### Content-Type: application/json - **`categories`** `array` **Items:** - **`category`** `string` - **`dropped_events`** `integer` - **`requests`** `integer` - **`share_pct`** `number` - **`period`** `object` - **`from`** `string`, format: `date` - **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` - **`previous_from`** `string`, format: `date` - **`previous_to`** `string`, format: `date` - **`to`** `string`, format: `date` - **`projects`** `array` **Items:** - **`dropped_events`** `integer` - **`id`** `string` - **`last_seen_at`** `object` - **`name`** `string` - **`requests`** `integer` - **`share_pct`** `number` - **`remaining_projects`** `integer` - **`scope`** `string`, possible values: `"account"` - **`summary`** `object` - **`active_projects`** `integer` - **`automated_requests`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`dropped_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`last_seen_at`** `object` - **`total_projects`** `integer` - **`time_series`** `array` **Items:** - **`date`** `string`, format: `date` - **`dropped_events`** `integer` - **`requests`** `integer` **Example:** ```json { "scope": "account", "period": { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" }, "summary": { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "active_projects": 1, "total_projects": 4, "last_seen_at": 1773459600000 }, "categories": [ { "category": "ai_agent", "requests": 3, "dropped_events": 5, "share_pct": 60 } ], "projects": [ { "id": "proj_123", "name": "my-site", "requests": 3, "dropped_events": 4, "share_pct": 60, "last_seen_at": 1773459600000 } ], "remaining_projects": 3, "time_series": [ { "date": "2026-03-13", "requests": 5, "dropped_events": 7 } ] } ``` ##### Status: 400 Invalid period ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Revoke and regenerate API key - **Method:** `POST` - **Path:** `/account/revoke-key` - **Tags:** Account Revokes the current API key and generates a new one. The old key stops working immediately. The new key is returned in the response — this is the only time it's shown. #### Responses ##### Status: 200 New API key ###### Content-Type: application/json - **`api_key`** `string` — The new API key. Save this — it won't be shown again. - **`ok`** `boolean` **Example:** ```json { "ok": true, "api_key": "aak_..." } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Get experiment config for tracker.js - **Method:** `GET` - **Path:** `/experiments/config` - **Tags:** Experiments Returns active experiments for a project, used by `tracker.js` to assign variants client-side. This is a **public** endpoint — authenticated by project token (same as `/track`), not API key. You don't call this directly — the tracker fetches it automatically on page load. #### Responses ##### Status: 200 Active experiments for this project ###### Content-Type: application/json - **`experiments`** `array` **Items:** - **`key`** `string` - **`variants`** `array` **Items:** - **`key`** `string` - **`weight`** `integer` **Example:** ```json { "experiments": [ { "key": "signup_cta", "variants": [ { "key": "", "weight": 1 } ] } ] } ``` ##### Status: 400 Missing token ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Create an A/B experiment - **Method:** `POST` - **Path:** `/experiments` - **Tags:** Experiments Creates a new experiment for the specified project. **Pro tier only.** Variants are assigned client-side by `tracker.js` using a deterministic hash — same user always gets the same variant. Exposure events (`$experiment_exposure`) are tracked automatically. **CLI:** `npx @agent-analytics/cli experiments create my-site --name signup_cta --variants control,new_cta --goal signup` #### Request Body ##### Content-Type: application/json - **`goal_event` (required)** `string` — Event name to measure conversion (max 256 chars) - **`name` (required)** `string` — Experiment name (alphanumeric, hyphens, underscores; max 64 chars) - **`project` (required)** `string` — Project name - **`variants` (required)** `array` — 2-4 variant keys **Items:** `string` - **`weights`** `array` — Optional traffic weights per variant (must sum to 100). Defaults to equal split. **Items:** `integer` **Example:** ```json { "project": "my-site", "name": "signup_cta", "variants": [ "control", "new_cta" ], "goal_event": "signup", "weights": [ 50, 50 ] } ``` #### Responses ##### Status: 201 Experiment created ###### Content-Type: application/json - **`account_id`** `string` - **`completed_at`** `string | null` - **`created_at`** `string` - **`goal_event`** `string` - **`id`** `string` - **`name`** `string` - **`project_id`** `string` - **`status`** `string`, possible values: `"active", "paused", "completed"` - **`updated_at`** `string` - **`variants`** `array` **Items:** - **`key`** `string` - **`weight`** `integer` - **`winner`** `string | null` — Variant key, set when status is completed **Example:** ```json { "id": "exp_a1b2c3d4e5f6g7h8", "project_id": "", "account_id": "", "name": "signup_cta", "variants": [ { "key": "control", "weight": 50 } ], "goal_event": "signup", "status": "active", "winner": null, "created_at": "", "updated_at": "", "completed_at": null } ``` ##### Status: 400 Validation error (missing fields, invalid variants, duplicate name) ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 403 Pro tier required ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 409 Experiment name already exists in this project ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### List experiments - **Method:** `GET` - **Path:** `/experiments` - **Tags:** Experiments Returns all experiments for a project (active, paused, and completed). **CLI:** `npx @agent-analytics/cli experiments list my-site` #### Responses ##### Status: 200 Experiment list ###### Content-Type: application/json - **`experiments`** `array` **Items:** - **`account_id`** `string` - **`completed_at`** `string | null` - **`created_at`** `string` - **`goal_event`** `string` - **`id`** `string` - **`name`** `string` - **`project_id`** `string` - **`status`** `string`, possible values: `"active", "paused", "completed"` - **`updated_at`** `string` - **`variants`** `array` **Items:** - **`key`** `string` - **`weight`** `integer` - **`winner`** `string | null` — Variant key, set when status is completed **Example:** ```json { "experiments": [ { "id": "exp_a1b2c3d4e5f6g7h8", "project_id": "", "account_id": "", "name": "signup_cta", "variants": [ { "key": "control", "weight": 50 } ], "goal_event": "signup", "status": "active", "winner": null, "created_at": "", "updated_at": "", "completed_at": null } ] } ``` ##### Status: 400 Missing project parameter ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Project not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Get experiment with live results - **Method:** `GET` - **Path:** `/experiments/{id}` - **Tags:** Experiments Returns experiment config plus live Bayesian A/B test results. Results include `probability_best` (probability each variant is the winner), `lift` (relative improvement over baseline), `sufficient_data` (whether enough exposures exist), and a human-readable `recommendation`. The system needs \~100 exposures per variant before results are statistically significant. **CLI:** `npx @agent-analytics/cli experiments get exp_abc123` #### Responses ##### Status: 200 Experiment with results ###### Content-Type: application/json **All of:** - **`account_id`** `string` - **`completed_at`** `string | null` - **`created_at`** `string` - **`goal_event`** `string` - **`id`** `string` - **`name`** `string` - **`project_id`** `string` - **`status`** `string`, possible values: `"active", "paused", "completed"` - **`updated_at`** `string` - **`variants`** `array` **Items:** - **`key`** `string` - **`weight`** `integer` - **`winner`** `string | null` — Variant key, set when status is completed * **`results`** `object` - **`lift`** `object` - **`probability_best`** `object` - **`recommendation`** `string` - **`sufficient_data`** `boolean` - **`variants`** `array` **Items:** - **`conversion_rate`** `number` - **`conversions`** `integer` - **`exposures`** `integer` - **`key`** `string` - **`unique_users`** `integer` **Example:** ```json { "id": "exp_a1b2c3d4e5f6g7h8", "project_id": "", "account_id": "", "name": "signup_cta", "variants": [ { "key": "control", "weight": 50 } ], "goal_event": "signup", "status": "active", "winner": null, "created_at": "", "updated_at": "", "completed_at": null, "results": { "variants": [ { "key": "control", "exposures": 520, "unique_users": 480, "conversions": 48, "conversion_rate": 0.1 } ], "probability_best": { "control": 0.06, "new_cta": 0.94 }, "lift": { "control": -0.32, "new_cta": 0.47 }, "sufficient_data": true, "recommendation": "'new_cta' is the winner with 94% probability (47% lift over baseline)" } } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 403 Not your experiment ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Experiment not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Update experiment status - **Method:** `PATCH` - **Path:** `/experiments/{id}` - **Tags:** Experiments Update an experiment's status. Valid transitions: - `active` → `paused` or `completed` - `paused` → `active` or `completed` - `completed` is terminal (no further changes) When completing, optionally set `winner` to a variant key. **CLI:** - `npx @agent-analytics/cli experiments pause exp_abc123` - `npx @agent-analytics/cli experiments resume exp_abc123` - `npx @agent-analytics/cli experiments complete exp_abc123 --winner new_cta` #### Request Body ##### Content-Type: application/json - **`status` (required)** `string`, possible values: `"active", "paused", "completed"` — New status - **`winner`** `string` — Variant key to declare as winner (only when completing) **Example:** ```json { "status": "active", "winner": "" } ``` #### Responses ##### Status: 200 Updated experiment ###### Content-Type: application/json - **`account_id`** `string` - **`completed_at`** `string | null` - **`created_at`** `string` - **`goal_event`** `string` - **`id`** `string` - **`name`** `string` - **`project_id`** `string` - **`status`** `string`, possible values: `"active", "paused", "completed"` - **`updated_at`** `string` - **`variants`** `array` **Items:** - **`key`** `string` - **`weight`** `integer` - **`winner`** `string | null` — Variant key, set when status is completed **Example:** ```json { "id": "exp_a1b2c3d4e5f6g7h8", "project_id": "", "account_id": "", "name": "signup_cta", "variants": [ { "key": "control", "weight": 50 } ], "goal_event": "signup", "status": "active", "winner": null, "created_at": "", "updated_at": "", "completed_at": null } ``` ##### Status: 400 Invalid status transition or missing status ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 403 Not your experiment ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Experiment not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### Delete an experiment - **Method:** `DELETE` - **Path:** `/experiments/{id}` - **Tags:** Experiments Permanently deletes an experiment. Event data (exposures, conversions) is preserved in the events table — only the experiment config is removed. **CLI:** `npx @agent-analytics/cli experiments delete exp_abc123` #### Responses ##### Status: 200 Experiment deleted ###### Content-Type: application/json - **`deleted`** `string` — Experiment ID - **`ok`** `boolean` **Example:** ```json { "ok": true, "deleted": "" } ``` ##### Status: 401 Missing or invalid API key ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 403 Not your experiment ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ##### Status: 404 Experiment not found ###### Content-Type: application/json - **`error` (required)** `string` — Machine-readable error code - **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### JavaScript tracker script - **Method:** `GET` - **Path:** `/tracker.js` - **Tags:** Tracking Returns the lightweight JavaScript tracker used for browser-side analytics. Embed it in your HTML: ```html ``` Required attributes: - `data-project`: project name - `data-token`: project token (`aat_*`) The tracker auto-collects page URL, pathname, referrer, browser, OS, device, language, timezone, UTM parameters, session count, and first-touch attribution. SPA routing is detected automatically. No cookies are required. Full tracker documentation now lives in the docs site: #### Responses ##### Status: 200 JavaScript file ###### Content-Type: application/javascript `string` **Example:** ```json true ``` ##### Status: 404 Not found ## Schemas ### Error - **Type:**`object` * **`error` (required)** `string` — Machine-readable error code * **`message` (required)** `string` — Human-readable error message **Example:** ```json { "error": "AUTH_REQUIRED", "message": "API key required" } ``` ### TrackRequest - **Type:**`object` * **`event` (required)** `string` — Event name (max 256 chars) * **`token` (required)** `string` — Project token (\`aat\_\*\`) * **`properties`** `object | null` — Arbitrary key-value properties (max 8KB JSON). The tracker auto-populates \`path\`, \`url\`, \`hostname\`, \`title\`, \`referrer\`, \`screen\`, \`browser\`, \`browser\_version\`, \`os\`, \`device\`, \`language\`, \`timezone\` (IANA), \`utm\_\*\` params, \`session\_count\`, \`days\_since\_first\_visit\`, and \`first\_utm\_\*\` (first-touch attribution). * **`session_id`** `string | null` — Session identifier (max 256 chars). The tracker auto-generates one via sessionStorage with 30-min inactivity timeout. * **`timestamp`** `number | null` — Unix timestamp in milliseconds. Defaults to \`Date.now()\`. Must be within 30 days ago to 5 minutes in the future. * **`user_id`** `string | null` — Anonymous user identifier (max 256 chars). The tracker auto-generates one via localStorage. **Example:** ```json { "token": "aat_51da22cdcab084ae1cdb50fd4841c642f0dafdb1d9adfa6b", "event": "page_view", "properties": { "path": "/pricing", "referrer": "https://google.com", "browser": "Chrome", "os": "macOS" }, "user_id": "usr_abc123", "session_id": "ses_xyz789", "timestamp": 1706745600000 } ``` ### TrackBatchRequest - **Type:**`object` * **`events` (required)** `array` — Array of events (max 100 per batch) **Items:** - **`event` (required)** `string` - **`token` (required)** `string` — Project token (\`aat\_\*\`). All events must use the same token. - **`properties`** `object | null` - **`session_id`** `string | null` - **`timestamp`** `number | null` - **`user_id`** `string | null` **Example:** ```json { "events": [ { "token": "aat_51da22cdcab084ae1cdb50fd4841c642f0dafdb1d9adfa6b", "event": "page_view", "properties": null, "user_id": null, "session_id": null, "timestamp": null } ] } ``` ### StatsResponse - **Type:**`object` * **`events`** `array` — Top event names by count (max 20) **Items:** - **`count`** `integer` - **`event`** `string` - **`unique_users`** `integer` * **`period`** `object` - **`from`** `string` - **`groupBy`** `string`, possible values: `"hour", "day", "week", "month"` - **`to`** `string` * **`project`** `string` * **`sessions`** `object` - **`avg_duration`** `integer` — Average session duration in milliseconds - **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`pages_per_session`** `number` - **`sessions_per_user`** `number` - **`total_sessions`** `integer` * **`timeSeries`** `array` **Items:** - **`bucket`** `string` - **`total_events`** `integer` - **`unique_users`** `integer` * **`totals`** `object` - **`total_events`** `integer` - **`unique_users`** `integer` **Example:** ```json { "project": "my-site", "period": { "from": "2025-01-01", "to": "2025-01-07", "groupBy": "day" }, "totals": { "unique_users": 142, "total_events": 1247 }, "timeSeries": [ { "bucket": "2025-01-01", "unique_users": 1, "total_events": 1 } ], "events": [ { "event": "page_view", "count": 1, "unique_users": 1 } ], "sessions": { "total_sessions": 1, "bounce_rate": 0.45, "avg_duration": 1, "pages_per_session": 1, "sessions_per_user": 1 } } ``` ### EventRow - **Type:**`object` * **`country`** `string | null` — ISO 3166-1 alpha-2 country code derived from the request IP at ingestion time * **`date`** `string` * **`event`** `string` * **`id`** `string` * **`project_id`** `string` * **`properties`** `object | null` * **`session_id`** `string | null` * **`timestamp`** `number` * **`user_id`** `string | null` **Example:** ```json { "id": "", "project_id": "", "event": "", "properties": null, "user_id": null, "session_id": null, "timestamp": 1, "date": "", "country": "US" } ``` ### QueryRequest - **Type:**`object` * **`project` (required)** `string` — Project name * **`date_from`** `string` — Start date (ISO 8601 or \`Nd\` shorthand). Defaults to 7 days ago. * **`date_to`** `string` — End date (ISO 8601). Defaults to today. * **`filters`** `array` **Items:** - **`field`** `string` — Field to filter on. Built-in: \`event\`, \`user\_id\`, \`date\`, \`country\`, \`session\_id\`, \`timestamp\`. Property fields: \`properties.path\`, \`properties.browser\`, etc. - **`op`** `string`, possible values: `"eq", "neq", "gt", "lt", "gte", "lte", "contains"` - **`value`** `object` — Value to compare against * **`group_by`** `array`, default: `[]` — Fields to group results by **Items:** `string`, possible values: `"event", "date", "user_id", "session_id", "country"` * **`limit`** `integer`, default: `100` * **`metrics`** `array`, default: `["event_count"]` — Metrics to compute **Items:** `string`, possible values: `"event_count", "unique_users", "session_count", "bounce_rate", "avg_duration"` * **`order`** `string`, possible values: `"asc", "desc"`, default: `"desc"` * **`order_by`** `string`, possible values: `"event_count", "unique_users", "session_count", "date", "event"` — Field to sort by **Example:** ```json { "project": "my-site", "metrics": [ "event_count" ], "group_by": [], "filters": [ { "field": "event", "op": "eq", "value": "page_view" } ], "date_from": "2025-01-01", "date_to": "2025-01-07", "order_by": "event_count", "order": "desc", "limit": 100 } ``` ### QueryResponse - **Type:**`object` * **`count`** `integer` * **`group_by`** `array` **Items:** `string` * **`metrics`** `array` **Items:** `string` * **`period`** `object` - **`from`** `string` - **`to`** `string` * **`project`** `string` * **`rows`** `array` **Items:** **Example:** ```json { "project": "", "period": { "from": "", "to": "" }, "metrics": [ "" ], "group_by": [ "" ], "rows": [ { "additionalProperty": "anything" } ], "count": 1 } ``` ### SessionRow - **Type:**`object` * **`date`** `string` * **`duration`** `integer` — Duration in milliseconds * **`end_time`** `number` * **`entry_page`** `string | null` * **`event_count`** `integer` * **`exit_page`** `string | null` * **`is_bounce`** `integer`, possible values: `0, 1` — Computed from event names at query time. \`1\` means the session has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. * **`project_id`** `string` * **`session_id`** `string` * **`start_time`** `number` * **`user_id`** `string | null` **Example:** ```json { "session_id": "", "user_id": null, "project_id": "", "start_time": 1, "end_time": 1, "duration": 1, "entry_page": null, "exit_page": null, "event_count": 1, "is_bounce": 0, "date": "" } ``` ### BreakdownResponse - **Type:**`object` * **`event`** `string | null` * **`project`** `string` * **`property`** `string` * **`total_events`** `integer` * **`total_with_property`** `integer` * **`values`** `array` **Items:** - **`count`** `integer` - **`unique_users`** `integer` - **`value`** `string` **Example:** ```json { "project": "", "property": "path", "event": null, "values": [ { "value": "/pricing", "count": 342, "unique_users": 201 } ], "total_events": 1, "total_with_property": 1 } ``` ### InsightsResponse - **Type:**`object` * **`current_period`** `object` - **`from`** `string` - **`to`** `string` * **`metrics`** `object` - **`avg_duration`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`bounce_rate`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`total_events`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`total_sessions`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` - **`unique_users`** `object` - **`change`** `number` - **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. - **`current`** `number` - **`previous`** `number` * **`previous_period`** `object` - **`from`** `string` - **`to`** `string` * **`project`** `string` * **`trend`** `string`, possible values: `"growing", "stable", "declining"` **Example:** ```json { "project": "", "current_period": { "from": "", "to": "" }, "previous_period": { "from": "", "to": "" }, "metrics": { "total_events": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "unique_users": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "total_sessions": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "bounce_rate": { "current": 1, "previous": 1, "change": 1, "change_pct": null }, "avg_duration": { "current": 1, "previous": 1, "change": 1, "change_pct": null } }, "trend": "growing" } ``` ### DeltaMetric - **Type:**`object` * **`change`** `number` * **`change_pct`** `integer | null` — Percentage change. Null when previous period was 0 but current is > 0. * **`current`** `number` * **`previous`** `number` **Example:** ```json { "current": 1, "previous": 1, "change": 1, "change_pct": null } ``` ### PagesResponse - **Type:**`object` * **`entry_pages`** `array` **Items:** - **`avg_duration`** `number` — Average session duration in milliseconds - **`avg_events`** `number` - **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`bounces`** `integer` - **`page`** `string` - **`sessions`** `integer` * **`exit_pages`** `array` **Items:** - **`avg_duration`** `number` — Average session duration in milliseconds - **`avg_events`** `number` - **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. - **`bounces`** `integer` - **`page`** `string` - **`sessions`** `integer` * **`project`** `string` **Example:** ```json { "project": "", "entry_pages": [ { "page": "/pricing", "sessions": 1, "bounces": 1, "bounce_rate": 0.45, "avg_duration": 1, "avg_events": 1 } ], "exit_pages": [ { "page": "/pricing", "sessions": 1, "bounces": 1, "bounce_rate": 0.45, "avg_duration": 1, "avg_events": 1 } ] } ``` ### PageRow - **Type:**`object` * **`avg_duration`** `number` — Average session duration in milliseconds * **`avg_events`** `number` * **`bounce_rate`** `number` — Bounce rate computed from event names at query time. A session is a bounce when it has only non-interactive events: \`page\_view\`, \`$impression\`, \`$scroll\_depth\`, \`$error\`, \`$time\_on\_page\`, \`$performance\`, \`$web\_vitals\`. * **`bounces`** `integer` * **`page`** `string` * **`sessions`** `integer` **Example:** ```json { "page": "/pricing", "sessions": 1, "bounces": 1, "bounce_rate": 0.45, "avg_duration": 1, "avg_events": 1 } ``` ### SessionDistributionResponse - **Type:**`object` * **`distribution`** `array` **Items:** - **`avg_events`** `number` - **`bounces`** `integer` — Number of bounce sessions in this duration bucket (computed from event names at query time). - **`bucket`** `string`, possible values: `"0s", "1-10s", "10-30s", "30-60s", "1-3m", "3-10m", "10m+"` - **`pct`** `number` — Percentage of total sessions - **`sessions`** `integer` * **`engaged_pct`** `number` — Percentage of sessions >= 30 seconds * **`median_bucket`** `string | null` * **`project`** `string` **Example:** ```json { "project": "", "distribution": [ { "bucket": "0s", "sessions": 1, "bounces": 1, "avg_events": 1, "pct": 1 } ], "median_bucket": null, "engaged_pct": 1 } ``` ### HeatmapResponse - **Type:**`object` * **`busiest_day`** `string | null` * **`busiest_hour`** `integer | null` * **`heatmap`** `array` **Items:** - **`day`** `integer` — Day of week (0=Sunday, 6=Saturday) - **`day_name`** `string` - **`events`** `integer` - **`hour`** `integer` — Hour of day (0-23) - **`users`** `integer` * **`peak`** `object | null` - **`day`** `integer` - **`day_name`** `string` - **`events`** `integer` - **`hour`** `integer` - **`users`** `integer` * **`project`** `string` **Example:** ```json { "project": "", "heatmap": [ { "day": 1, "day_name": "Monday", "hour": 1, "events": 1, "users": 1 } ], "peak": { "day": 1, "day_name": "", "hour": 1, "events": 1, "users": 1 }, "busiest_day": "Monday", "busiest_hour": 14 } ``` ### FunnelRequest - **Type:**`object` * **`project` (required)** `string` — Project name * **`steps` (required)** `array` — Funnel steps (2-8). Each step has an event name and optional property filters. **Items:** - **`event` (required)** `string` — Event name for this step - **`filters`** `array` — Optional property filters for this step **Items:** - **`op` (required)** `string`, possible values: `"eq", "neq", "contains"` — Filter operator - **`property` (required)** `string` — Property key (alphanumeric + underscores, max 128 chars) - **`value` (required)** `string` — Filter value * **`breakdown`** `string` — Optional property key to segment funnel by (e.g. 'variant', 'country'). Extracted from step 1 events only. * **`breakdown_limit`** `number`, default: `10` — Max breakdown groups, ordered by step 1 users descending (1-50, default 10) * **`conversion_window_hours`** `number`, default: `168` — Max hours from step 1 entry to final step (1-8760, default 168 = 7 days) * **`count_by`** `string`, possible values: `"user_id", "session_id"`, default: `"user_id"` — Count by unique users or sessions * **`since`** `string`, default: `"30d"` — Lookback period (ISO date or shorthand like '30d'). Default: 30d **Example:** ```json { "project": "my-site", "steps": [ { "event": "page_view", "filters": [ { "property": "path", "op": "eq", "value": "/pricing" } ] } ], "conversion_window_hours": 168, "since": "30d", "count_by": "user_id", "breakdown": "country", "breakdown_limit": 10 } ``` ### FunnelResponse - **Type:**`object` * **`breakdowns`** `array` — Per-group funnel results (only present when \`breakdown\` param is set). Ordered by step 1 users descending. **Items:** - **`overall_conversion_rate`** `number` — End-to-end conversion for this group - **`steps`** `array` — Same shape as top-level steps array **Items:** - **`avg_time_to_next_ms`** `number | null` - **`conversion_rate`** `number` - **`drop_off_rate`** `number` - **`event`** `string` - **`step`** `integer` - **`users`** `integer` - **`value`** `string | null` — Breakdown property value (null for events missing the property) * **`overall_conversion_rate`** `number` — Fraction of step 1 users that completed the final step * **`steps`** `array` **Items:** - **`avg_time_to_next_ms`** `number | null` — Average time in ms from this step to the next step (null for last step) - **`conversion_rate`** `number` — Fraction of users from previous step that reached this step (step 1 is always 1.0) - **`drop_off_rate`** `number` — Fraction of users lost from previous step (step 1 is always 0) - **`event`** `string` - **`step`** `integer` - **`users`** `integer` — Number of users (or sessions) that reached this step **Example:** ```json { "steps": [ { "step": 1, "event": "page_view", "users": 500, "conversion_rate": 0.6, "drop_off_rate": 0.4, "avg_time_to_next_ms": 3600000 } ], "overall_conversion_rate": 0.12, "breakdowns": [ { "value": "US", "steps": [ { "step": 1, "event": "", "users": 1, "conversion_rate": 1, "drop_off_rate": 1, "avg_time_to_next_ms": null } ], "overall_conversion_rate": 0.15 } ] } ``` ### RetentionResponse - **Type:**`object` * **`average_rates`** `array` — Weighted average retention rate per period offset (weighted by cohort size) **Items:** `number` * **`cohorts`** `array` **Items:** - **`date`** `string`, format: `date` — Start date of this cohort period - **`rates`** `array` — Retention rate per period (retained\[i] / users, rounded to 3 decimals) **Items:** `number` - **`retained`** `array` — Users active in each subsequent period (index 0 = full cohort) **Items:** `integer` - **`users`** `integer` — Number of users who first appeared in this period * **`period`** `string`, possible values: `"day", "week", "month"` * **`users_analyzed`** `integer` — Total users across all cohorts (capped at 10K per query) **Example:** ```json { "period": "week", "cohorts": [ { "date": "2026-01-27", "users": 142, "retained": [ 142, 64, 55, 46 ], "rates": [ 1, 0.451, 0.387, 0.324 ] } ], "average_rates": [ 1, 0.441, 0.373, 0.324 ], "users_analyzed": 560 } ``` ### Project - **Type:**`object` * **`allowed_origins`** `string` * **`id`** `string` * **`name`** `string` * **`project_token`** `string` * **`total_events`** `integer` * **`total_reads`** `integer` **Example:** ```json { "id": "", "name": "my-site", "project_token": "aat_...", "allowed_origins": "*", "total_events": 1, "total_reads": 1 } ``` ### LiveSnapshotResponse - **Type:**`object` * **`active_sessions`** `integer` — Unique session\_ids in the time window * **`active_visitors`** `integer` — Unique user\_ids in the time window * **`events_per_minute`** `integer` * **`project`** `string` * **`recent_events`** `array` — Last 10 events, newest first **Items:** - **`event`** `string` - **`properties`** `object | null` - **`timestamp`** `number` - **`user_id`** `string | null` * **`timestamp`** `number` * **`top_events`** `array` **Items:** - **`count`** `integer` - **`event`** `string` * **`top_pages`** `array` **Items:** - **`path`** `string` - **`visitors`** `integer` * **`window_seconds`** `integer` **Example:** ```json { "project": "my-site", "window_seconds": 60, "timestamp": 1708000060000, "active_visitors": 12, "active_sessions": 8, "events_per_minute": 47, "top_pages": [ { "path": "/", "visitors": 5 } ], "top_events": [ { "event": "page_view", "count": 32 } ], "recent_events": [ { "event": "", "properties": null, "user_id": null, "timestamp": 1 } ] } ``` ### AllSitesPeriod - **Type:**`object` * **`from`** `string`, format: `date` * **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` * **`previous_from`** `string`, format: `date` * **`previous_to`** `string`, format: `date` * **`to`** `string`, format: `date` **Example:** ```json { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" } ``` ### AllSitesMetricDelta - **Type:**`object` * **`change`** `integer` * **`change_pct`** `object` * **`current`** `integer` * **`previous`** `integer` **Example:** ```json { "current": 120, "previous": 90, "change": 30, "change_pct": 33 } ``` ### AllSitesSummary - **Type:**`object` * **`active_projects`** `integer` * **`total_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` * **`total_projects`** `integer` **Example:** ```json { "total_projects": 3, "active_projects": 2, "total_events": { "current": 120, "previous": 90, "change": 30, "change_pct": 33 } } ``` ### AllSitesTimeSeriesPoint - **Type:**`object` * **`date`** `string`, format: `date` * **`events`** `integer` **Example:** ```json { "date": "2026-03-09", "events": 40 } ``` ### AllSitesProjectSummary - **Type:**`object` * **`events`** `integer` * **`id`** `string` * **`last_active_date`** `object` * **`name`** `string` * **`share_pct`** `number` **Example:** ```json { "id": "proj_123", "name": "my-site", "events": 80, "share_pct": 66.7, "last_active_date": "2026-03-09" } ``` ### AllSitesOverviewResponse - **Type:**`object` * **`period`** `object` - **`from`** `string`, format: `date` - **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` - **`previous_from`** `string`, format: `date` - **`previous_to`** `string`, format: `date` - **`to`** `string`, format: `date` * **`projects`** `array` **Items:** - **`events`** `integer` - **`id`** `string` - **`last_active_date`** `object` - **`name`** `string` - **`share_pct`** `number` * **`remaining_projects`** `integer` * **`scope`** `string`, possible values: `"account"` * **`summary`** `object` - **`active_projects`** `integer` - **`total_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`total_projects`** `integer` * **`time_series`** `array` **Items:** - **`date`** `string`, format: `date` - **`events`** `integer` **Example:** ```json { "scope": "account", "period": { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" }, "summary": { "total_projects": 3, "active_projects": 2, "total_events": { "current": 120, "previous": 90, "change": 30, "change_pct": 33 } }, "time_series": [ { "date": "2026-03-09", "events": 40 } ], "projects": [ { "id": "proj_123", "name": "my-site", "events": 80, "share_pct": 66.7, "last_active_date": "2026-03-09" } ], "remaining_projects": 1 } ``` ### BotTrafficMetricDelta - **Type:**`object` * **`change`** `integer` * **`change_pct`** `object` * **`current`** `integer` * **`previous`** `integer` **Example:** ```json { "current": 5, "previous": 1, "change": 4, "change_pct": 400 } ``` ### BotTrafficTimeSeriesPoint - **Type:**`object` * **`date`** `string`, format: `date` * **`dropped_events`** `integer` * **`requests`** `integer` **Example:** ```json { "date": "2026-03-13", "requests": 5, "dropped_events": 7 } ``` ### BotTrafficCategorySummary - **Type:**`object` * **`category`** `string` * **`dropped_events`** `integer` * **`requests`** `integer` * **`share_pct`** `number` **Example:** ```json { "category": "ai_agent", "requests": 3, "dropped_events": 5, "share_pct": 60 } ``` ### BotTrafficActorSummary - **Type:**`object` * **`actor`** `string` * **`category`** `string` * **`dropped_events`** `integer` * **`last_seen_at`** `object` * **`requests`** `integer` **Example:** ```json { "actor": "ChatGPT-User", "category": "ai_agent", "requests": 3, "dropped_events": 5, "last_seen_at": 1773459600000 } ``` ### BotTrafficProjectSummary - **Type:**`object` * **`dropped_events`** `integer` * **`id`** `string` * **`last_seen_at`** `object` * **`name`** `string` * **`requests`** `integer` * **`share_pct`** `number` **Example:** ```json { "id": "proj_123", "name": "my-site", "requests": 3, "dropped_events": 4, "share_pct": 60, "last_seen_at": 1773459600000 } ``` ### BotTrafficProjectSummaryMetrics - **Type:**`object` * **`automated_requests`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` * **`dropped_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` * **`last_seen_at`** `object` **Example:** ```json { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "last_seen_at": 1773459600000 } ``` ### BotTrafficAccountSummary - **Type:**`object` * **`active_projects`** `integer` * **`automated_requests`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` * **`dropped_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` * **`last_seen_at`** `object` * **`total_projects`** `integer` **Example:** ```json { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "active_projects": 1, "total_projects": 4, "last_seen_at": 1773459600000 } ``` ### BotTrafficOverviewResponse - **Type:**`object` * **`actors`** `array` **Items:** - **`actor`** `string` - **`category`** `string` - **`dropped_events`** `integer` - **`last_seen_at`** `object` - **`requests`** `integer` * **`categories`** `array` **Items:** - **`category`** `string` - **`dropped_events`** `integer` - **`requests`** `integer` - **`share_pct`** `number` * **`period`** `object` - **`from`** `string`, format: `date` - **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` - **`previous_from`** `string`, format: `date` - **`previous_to`** `string`, format: `date` - **`to`** `string`, format: `date` * **`project`** `string` * **`scope`** `string`, possible values: `"project"` * **`summary`** `object` - **`automated_requests`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`dropped_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`last_seen_at`** `object` * **`time_series`** `array` **Items:** - **`date`** `string`, format: `date` - **`dropped_events`** `integer` - **`requests`** `integer` **Example:** ```json { "scope": "project", "project": "my-site", "period": { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" }, "summary": { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "last_seen_at": 1773459600000 }, "categories": [ { "category": "ai_agent", "requests": 3, "dropped_events": 5, "share_pct": 60 } ], "actors": [ { "actor": "ChatGPT-User", "category": "ai_agent", "requests": 3, "dropped_events": 5, "last_seen_at": 1773459600000 } ], "time_series": [ { "date": "2026-03-13", "requests": 5, "dropped_events": 7 } ] } ``` ### AllSitesBotTrafficOverviewResponse - **Type:**`object` * **`categories`** `array` **Items:** - **`category`** `string` - **`dropped_events`** `integer` - **`requests`** `integer` - **`share_pct`** `number` * **`period`** `object` - **`from`** `string`, format: `date` - **`label`** `string`, possible values: `"1d", "7d", "14d", "30d", "90d"` - **`previous_from`** `string`, format: `date` - **`previous_to`** `string`, format: `date` - **`to`** `string`, format: `date` * **`projects`** `array` **Items:** - **`dropped_events`** `integer` - **`id`** `string` - **`last_seen_at`** `object` - **`name`** `string` - **`requests`** `integer` - **`share_pct`** `number` * **`remaining_projects`** `integer` * **`scope`** `string`, possible values: `"account"` * **`summary`** `object` - **`active_projects`** `integer` - **`automated_requests`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`dropped_events`** `object` - **`change`** `integer` - **`change_pct`** `object` - **`current`** `integer` - **`previous`** `integer` - **`last_seen_at`** `object` - **`total_projects`** `integer` * **`time_series`** `array` **Items:** - **`date`** `string`, format: `date` - **`dropped_events`** `integer` - **`requests`** `integer` **Example:** ```json { "scope": "account", "period": { "label": "7d", "from": "2026-03-03", "to": "2026-03-09", "previous_from": "2026-02-24", "previous_to": "2026-03-02" }, "summary": { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "active_projects": 1, "total_projects": 4, "last_seen_at": 1773459600000 }, "categories": [ { "category": "ai_agent", "requests": 3, "dropped_events": 5, "share_pct": 60 } ], "projects": [ { "id": "proj_123", "name": "my-site", "requests": 3, "dropped_events": 4, "share_pct": 60, "last_seen_at": 1773459600000 } ], "remaining_projects": 3, "time_series": [ { "date": "2026-03-13", "requests": 5, "dropped_events": 7 } ] } ``` ### AccountResponse - **Type:**`object` * **`created_at`** `string` * **`email`** `string` * **`github_login`** `string | null` * **`id`** `string` * **`monthly_spend_cap_dollars`** `number | null` * **`projects_count`** `integer` * **`tier`** `string`, possible values: `"free", "pro"` * **`tier_limits`** `object` - **`max_events_per_month`** `integer` - **`max_projects`** `integer` - **`max_reads_per_month`** `integer` - **`rate_limit_rpm`** `integer` - **`retention_days`** `integer` **Example:** ```json { "id": "acc_free123", "email": "free@example.com", "github_login": "freeuser", "tier": "free", "created_at": "2026-02-01T12:00:00.000Z", "projects_count": 2, "tier_limits": { "max_events_per_month": 100000, "max_projects": 2, "max_reads_per_month": 500, "retention_days": 90, "rate_limit_rpm": 10 }, "monthly_spend_cap_dollars": null } ``` ### Experiment - **Type:**`object` * **`account_id`** `string` * **`completed_at`** `string | null` * **`created_at`** `string` * **`goal_event`** `string` * **`id`** `string` * **`name`** `string` * **`project_id`** `string` * **`status`** `string`, possible values: `"active", "paused", "completed"` * **`updated_at`** `string` * **`variants`** `array` **Items:** - **`key`** `string` - **`weight`** `integer` * **`winner`** `string | null` — Variant key, set when status is completed **Example:** ```json { "id": "exp_a1b2c3d4e5f6g7h8", "project_id": "", "account_id": "", "name": "signup_cta", "variants": [ { "key": "control", "weight": 50 } ], "goal_event": "signup", "status": "active", "winner": null, "created_at": "", "updated_at": "", "completed_at": null } ``` ### ExperimentResults - **Type:**`object` * **`lift`** `object` * **`probability_best`** `object` * **`recommendation`** `string` * **`sufficient_data`** `boolean` * **`variants`** `array` **Items:** - **`conversion_rate`** `number` - **`conversions`** `integer` - **`exposures`** `integer` - **`key`** `string` - **`unique_users`** `integer` **Example:** ```json { "variants": [ { "key": "control", "exposures": 520, "unique_users": 480, "conversions": 48, "conversion_rate": 0.1 } ], "probability_best": { "control": 0.06, "new_cta": 0.94 }, "lift": { "control": -0.32, "new_cta": 0.47 }, "sufficient_data": true, "recommendation": "'new_cta' is the winner with 94% probability (47% lift over baseline)" } ``` ## Additional Response Examples ### `GET /account` Status: `200` · Content-Type: `application/json` #### freeTier — Free tier account ```json { "id": "acc_free123", "email": "free@example.com", "github_login": "freeuser", "tier": "free", "created_at": "2026-02-01T12:00:00.000Z", "projects_count": 2, "tier_limits": { "max_events_per_month": 100000, "max_projects": 2, "max_reads_per_month": 500, "retention_days": 90, "rate_limit_rpm": 10 }, "monthly_spend_cap_dollars": null } ``` #### proTier — Pro tier account ```json { "id": "acc_pro123", "email": "pro@example.com", "github_login": "prouser", "tier": "pro", "created_at": "2026-02-01T12:00:00.000Z", "projects_count": 42, "tier_limits": { "max_events_per_month": -1, "max_projects": -1, "max_reads_per_month": -1, "retention_days": 365, "rate_limit_rpm": 1000 }, "monthly_spend_cap_dollars": 50 } ``` ### `GET /bot-traffic` Status: `200` · Content-Type: `application/json` #### empty — Empty state ```json { "scope": "project", "project": "my-site", "period": { "label": "7d", "from": "2026-03-07", "to": "2026-03-13", "previous_from": "2026-02-28", "previous_to": "2026-03-06" }, "summary": { "automated_requests": { "current": 0, "previous": 0, "change": 0, "change_pct": 0 }, "dropped_events": { "current": 0, "previous": 0, "change": 0, "change_pct": 0 }, "last_seen_at": null }, "categories": [], "actors": [], "time_series": [ { "date": "2026-03-13", "requests": 0, "dropped_events": 0 } ] } ``` #### active — Active bot traffic ```json { "scope": "project", "project": "my-site", "period": { "label": "7d", "from": "2026-03-07", "to": "2026-03-13", "previous_from": "2026-02-28", "previous_to": "2026-03-06" }, "summary": { "automated_requests": { "current": 5, "previous": 1, "change": 4, "change_pct": 400 }, "dropped_events": { "current": 7, "previous": 1, "change": 6, "change_pct": 600 }, "last_seen_at": 1773459600000 }, "categories": [ { "category": "ai_agent", "requests": 3, "dropped_events": 5, "share_pct": 60 }, { "category": "search_crawler", "requests": 2, "dropped_events": 2, "share_pct": 40 } ], "actors": [ { "actor": "ChatGPT-User", "category": "ai_agent", "requests": 3, "dropped_events": 5, "last_seen_at": 1773459600000 }, { "actor": "Googlebot", "category": "search_crawler", "requests": 2, "dropped_events": 2, "last_seen_at": 1773456000000 } ], "time_series": [ { "date": "2026-03-13", "requests": 5, "dropped_events": 7 } ] } ```