Numbers without product context are noise. This skill teaches your AI agent to read Analiz data like an experienced analyst — anchored on your homepage, cross-referenced with your codebase and recent commits, and aware of which features sit behind paid tiers.
Drop the skill into the repo that has the Analiz snippet installed. That way your agent has your codebase + your analytics in one context window — it can read your routes, grep for analiz.capture() calls, and check recent commits when explaining a spike.
# Run this from the root of the project that uses Analiz
mkdir -p .claude/skills/analiz-analytics-interpretation
curl -o .claude/skills/analiz-analytics-interpretation/SKILL.md \
https://analiz.dev/skills/analiz-analytics-interpretation.md# Run this from the root of the project that uses Analiz
mkdir -p .cursor/rules
curl -o .cursor/rules/analiz-analytics-interpretation.mdc \
https://analiz.dev/skills/analiz-analytics-interpretation.md# Run this from the root of the project that uses Analiz
mkdir -p .windsurfrules
curl -o .windsurfrules/analiz-analytics-interpretation.md \
https://analiz.dev/skills/analiz-analytics-interpretation.mdUse this only if you can't modify the project repo. The agent will still know how to read Analiz data, but won't cross-reference your code unless that project is also open.
mkdir -p ~/.claude/skills/analiz-analytics-interpretation
curl -o ~/.claude/skills/analiz-analytics-interpretation/SKILL.md \
https://analiz.dev/skills/analiz-analytics-interpretation.mdThe skill file lives at a stable URL — re-run any of the commands above to pull the latest version.
The skill works best when your agent can actually query your analytics. Add the Analiz MCP server to .mcp.json (Claude Code) or your editor's MCP config:
{
"mcpServers": {
"analiz": {
"command": "npx",
"args": ["analiz-mcp"],
"env": {
"ANALIZ_API_KEY": "your_api_key",
"ANALIZ_PROJECT_ID": "your_project_id",
"ANALIZ_URL": "https://analiz.dev"
}
}
}
}Get your API key and project ID from the dashboard.
Once installed, the skill activates automatically when you ask your agent about analytics. Try any of these:
Four core principles, plus a catalog of common mistakes to avoid.
The homepage is the only page every visitor hits. It's the denominator for nearly every conversion question. The skill ensures the agent always pulls top pages and references / first.
Owner traffic on /dashboard/proj_* inflates pageview counts but says nothing about product reach. The skill teaches the agent to exclude these from acquisition math and call it out explicitly.
Before drawing conclusions, the agent greps the project: low form_submit count → check if the form is a real <form>; revenue null → check if litetrack.revenue() is wired; pageview spike → git log to find recent commits.
If a question can't be answered, the agent proposes the exact analiz.capture / identify / revenue call and where to add it — not just 'we don't track that'.
Things any analyst — human or AI — gets wrong without these guardrails.
| Mistake | What the skill does instead |
|---|---|
| Treating $identify count as signups | It's logins + signups. Recommend explicit capture("signup"). |
| Reporting 88 visitors without excluding /dashboard/* self-traffic | Subtract or call out — that's owner browsing, not product reach. |
| Concluding 'no revenue' without checking instrumentation | Grep for analiz.revenue( first — often just not wired. |
| Recommending session stories without flagging Pro tier | Read lib/plan-access.ts. Flag the gate. |
| Drawing trends from low absolute numbers | Under ~50 conversions/wk = noise. Say so. |
| Reporting raw avg_duration in milliseconds | Convert to seconds/minutes for humans. |
We send occasional, useful emails — new skills, instrumentation patterns, and what we're seeing across thousands of projects. No spam, unsubscribe anytime.
---
name: analiz-analytics-interpretation
description: Use when the user asks to read, interpret, analyze, summarize, or act on Analiz analytics data via the mcp__analiz__* tools — covers "how are we doing", "what's going on", "is feature X being used", "where's the leak", "should we be worried" — AND when the user asks to add tracking, identify users, set up goals or funnels, or wire up revenue. Drop this file into the project that has the Analiz snippet installed so the agent has both the analytics data and the codebase in one context window.
---
# Analiz Analytics Interpretation
## Overview
Analiz analytics tools return raw numbers. **Numbers without product context are noise.** This skill turns Analiz data into product insight by anchoring every interpretation in:
1. **The homepage `/`** as the top-of-funnel reference (always pull it)
2. **The codebase** — routes, instrumentation, recent commits explain the numbers
3. **The full Analiz capability surface** — knowing when to recommend `analiz.capture()`, `analiz.identify()`, `analiz.revenue()`, goals, or funnels instead of guessing
4. **Tier awareness** — some features (funnels, sessions, revenue) are paid-tier; flag this when relevant
This skill is intended to live **inside the project that uses Analiz** (e.g. `.claude/skills/analiz-analytics-interpretation/SKILL.md`). It assumes the agent can grep the project, read source files, and check `git log` alongside the analytics MCP tools.
## When to Use
Triggers:
- User asks to read, summarize, or interpret analytics ("how are we doing", "check the analytics", "what's going on")
- Question about a specific metric, page, source, or event
- "Is feature X being used?", "where are users dropping off?", "is anyone converting?"
- User asks to **add tracking**, instrument an event, identify users, set up a goal or funnel, or wire revenue
- Any time `mcp__analiz__*` tools are present in the toolset
Do NOT use:
- For unrelated frontend/backend work that has nothing to do with analytics
## Core Principles
### 1. Always anchor on `/` (homepage)
The homepage is the only page every visitor hits. It is the denominator for nearly every conversion question. **Always include `get_top_pages` and look at `/` first**, even if the user asks something narrow.
### 2. Filter dashboard self-traffic before reporting "user activity"
`/dashboard/proj_*` paths are owners viewing their own analytics. They inflate pageview/session counts but say nothing about *product reach*. When measuring acquisition or conversion, **exclude dashboard paths from the denominator** and call this out explicitly.
```
Top pages typically look like:
/ → real top-of-funnel (USE THIS)
/dashboard/proj_xxx → owner self-traffic (EXCLUDE)
/dashboard/proj_xxx/X → owner self-traffic (EXCLUDE)
```
### 3. Cross-reference the codebase before interpreting
Before drawing conclusions, **grep the project**. Examples:
| Observation | Check the codebase for |
|---|---|
| Low `$form_submit` count | The actual signup/contact form route. Is the form using `<form>`? Auto-capture only fires on real form submits. |
| `$identify` count looks like signups | `analiz.identify(...)` calls — it fires on **every login AND signup**. Not a signup signal alone. |
| Spike in pageviews | `git log --since="2 weeks ago"` — was a feature shipped? landing page changed? |
| Specific page in top results | Read that route file — what is it actually for? |
| Custom event `xyz` shows up | `grep -r 'capture("xyz"' .` to find where it fires |
| `$revenue` is null but Stripe is wired | Check that `analiz.revenue({amount, plan})` is called in the Stripe webhook / success page |
### 4. Recommend instrumentation, don't just report gaps
If a question can't be answered from existing data, **propose the exact `analiz.*` call** (or goal/funnel) and where to add it. Don't just say "we don't track that."
## The Analiz API (cheat sheet)
The tracker exposes a single global. Both `window.analiz` and `window.litetrack` point to the same object (the latter is a deprecated alias kept for legacy snippets).
```ts
declare global {
interface Window {
analiz: {
capture: (event: string, properties?: Record<string, string | number | boolean>) => void
identify: (userId: string, traits?: Record<string, unknown>) => void
revenue: (data: { amount: number; currency?: string; plan?: string }) => void
}
}
}
```
In React projects, prefer the hook from `@analiz/react`:
```tsx
import { useTrack } from "@analiz/react"
const { capture, identify, revenue } = useTrack()
```
For static HTML buttons/links, the `data-track` attribute auto-captures clicks with no JS:
```html
<button data-track="cta_pricing">Get Started</button>
```
## Built-in Auto-Captured Events
Read `packages/tracker/src/t.ts` if you need to confirm exactly what fires. Auto-captured (no code needed):
| Event | Fires on |
|---|---|
| `$pageview` | Page load + SPA navigation (pushState/replaceState/popstate) |
| `$pageleave` | Page hidden / nav away (with `time_on_page_ms`, `scroll_depth_percent`) |
| `$session_end` | Tab hidden after activity |
| `$click` | Clicks on `<a>`, `<button>`, or `[data-track]` — captures text/href/classes/track_id |
| `$scroll_depth` | 25/50/75/100% milestones |
| `$form_submit` | `<form>` submit event — captures field count + has_email/has_password (no values) |
| `$web_vitals` | LCP/CLS/INP/TTFB/FCP on page hide |
| `$identify` | Manual `analiz.identify(id, traits)` — **fires on login AND signup**, not signup-only |
| `$revenue` | Manual `analiz.revenue({amount, currency, plan})` |
**Critical:** `$identify` is NOT a signup signal. It fires every login. To track signups specifically, recommend an explicit event (see "Adding custom events" below).
---
## Adding custom events
Use `analiz.capture(eventName, properties)` for any business-meaningful action that isn't auto-captured. **Place the call where the action *succeeds*, not where it's attempted.**
### Naming conventions
- snake_case: `"button_click"` not `"buttonClick"`
- Descriptive: `"pricing_page_viewed"` not `"view"`
- Namespace by feature: `"checkout_started"`, `"onboarding_complete"`, `"export_csv_used"`
### Property rules
- Properties must be `string`, `number`, or `boolean` (no nested objects, no arrays)
- **Never include PII** — no emails, no passwords, no full names, no raw form input values
- Use IDs and categorical values (`plan: "pro"`, `source: "hero"`, `step: 3`)
### Recipes
**Generic event**
```js
window.analiz?.capture("event_name", { any: "properties" })
```
**Signup completion** (the most-asked-for and most-missed event)
```js
// In the signup success handler — Next.js example
await fetch("/api/auth/signup", { ... })
window.analiz?.capture("signup", { plan: "trial", source: "homepage_cta" })
window.analiz?.identify(user.id, { plan: "trial" })
```
**Feature usage**
```js
window.analiz?.capture("feature_used", { feature: "export_csv" })
window.analiz?.capture("feature_used", { feature: "share_link", target: "team" })
```
**Onboarding step**
```js
window.analiz?.capture("onboarding_step", { step: 1, name: "create_account" })
window.analiz?.capture("onboarding_step", { step: 2, name: "connect_data" })
window.analiz?.capture("onboarding_complete")
```
**Errors worth tracking**
```js
window.analiz?.capture("error", { type: "api_error", status: 500, route: "/api/checkout" })
```
**React (`@analiz/react`)**
```tsx
import { useTrack } from "@analiz/react"
function PricingButton({ plan }: { plan: string }) {
const { capture } = useTrack()
return (
<button
data-track="cta_pricing"
onClick={() => capture("pricing_cta_click", { plan })}
>
Choose {plan}
</button>
)
}
```
**Pure-HTML click tracking** — no JS needed, captures as `$click` with `track_id`:
```html
<button data-track="cta_pricing">Get Started</button>
<a href="/signup" data-track="nav_signup">Sign up</a>
```
### When to recommend a custom event
If the user asks any of these and the answer requires data Analiz doesn't already have, **propose the exact capture call**:
- "Is anyone clicking X?" → recommend `data-track="x"` or `capture("x_clicked")`
- "How many signups?" → recommend `capture("signup")` (and stop using `$identify` as a proxy)
- "Are people activating?" → define what "activated" means with the user, then recommend `capture("activated", { reason: "ran_first_query" })`
- "Are users hitting feature Y?" → recommend `capture("feature_used", { feature: "y" })`
---
## Identifying users
`analiz.identify(userId, traits?)` associates the current session (and any future sessions on the same browser) with a user. Use it once after login or signup — not on every page.
### Where to call it
In the auth provider or layout, right after the user is known. In Next.js App Router:
```tsx
"use client"
import { useEffect } from "react"
import { useSession } from "next-auth/react"
export function IdentifyUser() {
const { data } = useSession()
useEffect(() => {
if (!data?.user?.id) return
window.analiz?.identify(data.user.id, {
plan: (data.user as { plan?: string }).plan,
// NEVER include email, full name, or any PII you wouldn't want in analytics
})
}, [data?.user?.id])
return null
}
```
For a manual login flow:
```js
// After your login API responds with a user
window.analiz?.identify(user.id, { plan: user.plan, role: user.role })
```
### Rules
- Call `identify()` **once per session**, after login or signup. The tracker remembers the user_id for the rest of the session.
- The first argument should be your **stable internal user ID** (not email).
- Traits are a small bag of categorical attributes: `plan`, `role`, `team_size`, `signup_source`. Keep them low-cardinality.
- **Never pass PII** — no email, no name, no phone. If you need to look up a user by email, do it from your own database.
- The tracker retroactively stamps `user_id` onto any events queued before `identify()` ran (e.g., the initial `$pageview`), so calling it inside `useEffect` is safe.
### Don't confuse `$identify` with "signup"
`$identify` fires every time `identify()` is called — that's every login, not every signup. To count signups, fire an **explicit** `capture("signup", ...)` in the signup completion handler (see recipe above).
---
## Setting up goals
Goals are simple conversion targets stored in PostgreSQL (one row per goal, per project). Each goal computes:
- **Conversion rate** — unique visitors who hit the goal ÷ unique visitors in the period
- **Total completions** — total times the goal fired
- **Unique completions** — distinct visitors who completed it
A goal is one of two flavors:
| Flavor | When to use | Example |
|---|---|---|
| **Event goal** (`eventName`) | A custom event fires when the conversion happens | `signup`, `purchase`, `feature_used` |
| **Path goal** (`pathPattern`) | A specific page is reached | `/thank-you`, `/onboarding/complete` |
You can only use one of `eventName` or `pathPattern` per goal — not both.
### Create a goal via the dashboard (recommended)
1. Open the project dashboard at `https://analiz.dev/dashboard/<projectId>`.
2. Open the **Goals** modal (Target icon in the dashboard chrome).
3. Click **Add goal** → name it (e.g. "Signup completed") → pick **Event** or **Path** → enter the event name (e.g. `signup`) or path (e.g. `/thank-you`) → save.
### Create a goal via the API
```bash
curl -X POST "https://analiz.dev/api/v1/goals?project_id=<PROJECT_ID>" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Signup completed",
"eventName": "signup"
}'
```
For a path goal:
```bash
curl -X POST "https://analiz.dev/api/v1/goals?project_id=<PROJECT_ID>" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Reached thank-you page",
"pathPattern": "/thank-you"
}'
```
### When to recommend a goal vs a funnel
- **Goal** = one moment that matters. Cheap, top-line conversion rate. Use when the user asks "is anyone buying?" or "what's our signup rate?"
- **Funnel** = multi-step path with drop-off at each step. Use when the user asks "where are people falling off?" or "how does our onboarding actually flow?"
Set up a goal **first** for any KPI the user cares about. Promote to a funnel only when drop-off analysis is needed.
### Recommend instrumentation alongside the goal
If the user wants a goal for an event that doesn't exist yet, recommend **both**:
1. The `capture("event_name", ...)` call to add to the codebase
2. The goal definition that points at it
A goal pointing at an event that's never fired will silently report 0%.
---
## Funnels (paid tier)
Funnels live in PostgreSQL (`funnels` table, `apps/web/lib/schema.ts`). Each funnel has ordered `steps` (JSON) — typically a sequence of event names or paths. Built in the dashboard at `/dashboard/[projectId]/funnels`.
**Recommend a funnel when** the user asks about drop-off, conversion paths, or "where are we losing people." Standard SaaS funnel:
```
1. $pageview path=/ (visited homepage)
2. $pageview path=/signup (reached signup)
3. capture event=signup (completed signup)
4. capture event=activated (used core feature)
5. revenue (paid)
```
Call `get_funnel()` (no args) to list existing funnels first, then drill in by ID.
---
## Revenue
`analiz.revenue({ amount, currency?, plan? })` fires a `$revenue` event that powers the revenue dashboard, by-source attribution, and revenue-aware funnels.
### Where to call it
- **Best:** in your Stripe webhook handler when `invoice.payment_succeeded` (or equivalent) fires server-side, mirrored to the client via a fetch call to a small endpoint that triggers the tracker. (Or better: have the server fire a custom event via the ingestion API.)
- **Acceptable:** on the success/thank-you page after a Stripe Checkout redirect:
```js
// pages/checkout/success.tsx
useEffect(() => {
window.analiz?.revenue({ amount: 29, currency: "USD", plan: "pro_monthly" })
}, [])
```
### Rules
- `amount` is in **major units** (29 = $29, not 2900 cents).
- Always pass a stable `plan` string so revenue can be sliced by plan.
- Fire it **once per payment** — don't fire on every pageview of the success page (use a guard or sessionStorage flag if needed).
---
## Reading the Analiz Tool Surface
### Tool → use case map
| Tool | When to call |
|---|---|
| `get_stats` | First call for any "how are we doing" — KPIs + WoW deltas |
| `get_realtime` | "Is anyone on the site right now?", live debugging |
| `get_top_pages` | **Almost always pull this** — anchor on `/` |
| `get_top_sources` | Acquisition / channel questions, attribution |
| `get_events` | What custom events are firing? Use before drawing event-based conclusions |
| `get_events(event_name=X)` | Drill into a single event's properties |
| `get_sessions(outcome="converted"\|"engaged"\|"bounced")` | Walk individual visitor journeys — *paid tier* |
| `get_revenue` | Revenue KPIs + by-source/page attribution — *paid tier* |
| `get_funnel()` | List funnels first, then drill in — *paid tier* |
### Default playbook for "what's going on" / "how are we doing"
Run in parallel:
1. `get_stats(period="week")` — anchor metrics
2. `get_top_pages(period="week")` — find `/` and identify dashboard self-traffic
3. `get_top_sources(period="week")` — channel mix
4. `get_events(period="week")` — what's instrumented
5. `get_realtime()` — live snapshot
6. `get_revenue(period="month")` — only if revenue is part of the question or instrumented
Then `git log --oneline -10` to see if recent commits explain any spike/drop.
---
## Tier Awareness
From `apps/web/lib/plan-access.ts`:
| Tier | Has access to |
|---|---|
| **Starter** | No analytics API/MCP access |
| **Pro** | `stats`, `top-pages`, `top-sources`, `realtime` |
| **Business** | All of Pro + `sessions/stories`, `funnels`, `revenue`, `events` (drill-in) |
| **Trial** | Everything (3 days from signup) |
When recommending features, **flag the tier**:
> Sessions storytelling and funnels are Business-tier features. If you're on Pro you'll see a 403 from the MCP tool. The current trial period is 3 days.
---
## Reporting Format
Structure interpretations like this:
```
## Headline (1 sentence)
[The single most important finding]
## Key metrics
- Anchor on / first
- WoW deltas with direction
- Always note dashboard self-traffic exclusion when relevant
## What it means
[Connect numbers to the codebase / recent commits / product context]
## What to do
[Concrete next action — often: instrument event X, set up goal Y, build funnel Z]
[If recommending a paid-tier feature, flag the tier]
```
Keep numbers in absolute + relative form. Always show the denominator.
---
## Common Mistakes
| Mistake | Fix |
|---|---|
| Treating `$identify` count as signups | It's logins + signups. Recommend explicit `capture("signup")`. |
| Reporting "88 visitors" without excluding `/dashboard/*` self-traffic | Subtract or call out — that's owner browsing, not product reach. |
| Concluding "no revenue" without checking instrumentation | Grep for `analiz.revenue(` first — often just not wired. |
| Recommending funnels without flagging Business tier | Read `lib/plan-access.ts`. Flag the gate. |
| Drawing trends from low absolute numbers | Under ~50 conversions/wk = noise. Say so. |
| Not pulling homepage when answering narrow question | Always pull `/` — context matters even for narrow questions. |
| Skipping `git log` when explaining a spike | Recent commits often explain the data. Always check. |
| Reporting raw `avg_duration` in milliseconds | Convert to seconds/minutes for humans. |
| Calling `identify()` with email or PII | Use the stable internal user ID. Pass plan/role as traits, never email. |
| Setting up a goal for an event that doesn't exist | Recommend the `capture()` call **and** the goal in the same response. |
| Firing `revenue()` in cents | `amount` is major units (29, not 2900). |
| Firing `revenue()` on every pageview of /thank-you | Guard with sessionStorage so it only fires once per payment. |
---
## Red Flags — Stop and Re-Check
- About to report conversion without excluding `/dashboard/*` from denominator
- About to say "we don't track X" without grepping for `capture("X"` first
- About to recommend a funnel/sessions/revenue feature without checking the user's tier
- About to interpret a spike without `git log`
- Skipped `get_top_pages` because the question seemed narrow
- About to recommend `identify()` with email or any PII as a trait
- About to define a goal for an event that doesn't exist yet in the codebase