# Annotations Source: https://docs.staminads.com/annotations Mark significant events on your charts for better context Annotations let you mark significant dates on your charts, helping you correlate traffic patterns with real-world events like campaigns, product launches, or website changes. Chart with annotations ## Use Cases * **Campaign launches** — Mark when a new ad campaign started to see its impact on traffic * **Website changes** — Track deployments, redesigns, or content updates * **External events** — Note industry events, holidays, or competitor actions * **Technical issues** — Document outages or tracking issues for future reference * **Seasonal patterns** — Mark recurring events to compare year-over-year ## Creating an Annotation Navigate to **Settings > Annotations** in your workspace to manage annotations. Each annotation includes: | Field | Description | | --------------- | ------------------------------------------------------------ | | **Date** | The date of the event (YYYY-MM-DD format) | | **Time** | The specific time of the event (HH:mm format) | | **Timezone** | The timezone for the event (e.g., America/New\_York) | | **Title** | A short label displayed on the chart | | **Description** | Optional details about the event | | **Color** | Custom color for the annotation marker (defaults to #7763f1) | Annotation creation form ## Viewing Annotations Annotations appear as vertical markers on time-series charts. Hover over a marker to see the annotation details. Use consistent colors to categorize your annotations. For example, use blue for campaigns, green for releases, and red for incidents. ## Best Practices 1. **Be specific** — Include enough detail in the description to understand the event months later 2. **Use accurate times** — Set the correct timezone to align annotations with your traffic data 3. **Keep titles short** — Use concise titles that fit on the chart without cluttering 4. **Clean up old annotations** — Remove annotations that are no longer relevant to keep charts readable # Execute an analytics query Source: https://docs.staminads.com/api-reference/analytics/execute-an-analytics-query openapi.json post /api/analytics.query # Get available analytics tables Source: https://docs.staminads.com/api-reference/analytics/get-available-analytics-tables openapi.json get /api/analytics.tables # Get available dimensions Source: https://docs.staminads.com/api-reference/analytics/get-available-dimensions openapi.json get /api/analytics.dimensions # Get available metrics Source: https://docs.staminads.com/api-reference/analytics/get-available-metrics openapi.json get /api/analytics.metrics # Get min/max extremes of a metric across grouped data Source: https://docs.staminads.com/api-reference/analytics/get-minmax-extremes-of-a-metric-across-grouped-data openapi.json post /api/analytics.extremes # Export user events Source: https://docs.staminads.com/api-reference/export/export-user-events openapi.json get /api/export.userEvents Export raw events for users with a user_id set. Supports cursor-based pagination for incremental sync. # Cancel running backfill task Source: https://docs.staminads.com/api-reference/filters/cancel-running-backfill-task openapi.json post /api/filters.backfillCancel # Create filter Source: https://docs.staminads.com/api-reference/filters/create-filter openapi.json post /api/filters.create # Delete filter Source: https://docs.staminads.com/api-reference/filters/delete-filter openapi.json post /api/filters.delete # Get backfill status summary for workspace Source: https://docs.staminads.com/api-reference/filters/get-backfill-status-summary-for-workspace openapi.json get /api/filters.backfillSummary # Get backfill task status Source: https://docs.staminads.com/api-reference/filters/get-backfill-task-status openapi.json get /api/filters.backfillStatus # Get filter by ID Source: https://docs.staminads.com/api-reference/filters/get-filter-by-id openapi.json get /api/filters.get # List backfill tasks for workspace Source: https://docs.staminads.com/api-reference/filters/list-backfill-tasks-for-workspace openapi.json get /api/filters.backfillList # List filters for workspace Source: https://docs.staminads.com/api-reference/filters/list-filters-for-workspace openapi.json get /api/filters.list # List unique tags across all filters Source: https://docs.staminads.com/api-reference/filters/list-unique-tags-across-all-filters openapi.json get /api/filters.listTags # Reorder filters Source: https://docs.staminads.com/api-reference/filters/reorder-filters openapi.json post /api/filters.reorder # Start background backfill for all filters Source: https://docs.staminads.com/api-reference/filters/start-background-backfill-for-all-filters openapi.json post /api/filters.backfillStart # Update filter Source: https://docs.staminads.com/api-reference/filters/update-filter openapi.json post /api/filters.update # Create a new workspace Source: https://docs.staminads.com/api-reference/workspaces/create-a-new-workspace openapi.json post /api/workspaces.create # Delete a workspace Source: https://docs.staminads.com/api-reference/workspaces/delete-a-workspace openapi.json post /api/workspaces.delete # Get workspace by ID Source: https://docs.staminads.com/api-reference/workspaces/get-workspace-by-id openapi.json get /api/workspaces.get # List workspaces for current user Source: https://docs.staminads.com/api-reference/workspaces/list-workspaces-for-current-user openapi.json get /api/workspaces.list # Update an existing workspace Source: https://docs.staminads.com/api-reference/workspaces/update-an-existing-workspace openapi.json post /api/workspaces.update # Dashboard Source: https://docs.staminads.com/dashboard Monitor your traffic quality at a glance The Dashboard provides an overview of your traffic quality with key metrics, trends, and breakdowns by dimension. Dashboard overview with KPIs and chart ## Date Range & Comparison Use the date picker to select a time period for analysis. Available presets include Today, Yesterday, Last 7 days, Last 30 days, and custom ranges. Enable **comparison mode** to overlay the previous period on charts and show percentage changes on KPIs. This helps identify trends and measure performance over time. ## Key Metrics The top row displays your main KPIs: | Metric | Description | | -------------------- | ------------------------------------------------------------- | | **Sessions** | Total number of sessions in the selected period | | **Median TimeScore** | Median session duration — your core engagement metric | | **Bounce Rate** | Percentage of single-page sessions below the bounce threshold | | **Pages / Session** | Average number of pages viewed per session | Click any metric to switch the time-series chart to that metric. ## Time-Series Chart The main chart visualizes your selected metric over time. Features include: * **Granularity selector** — Switch between hourly, daily, weekly, or monthly views * **Period comparison** — Overlay previous period data when comparison mode is enabled * **Annotations** — Vertical markers for significant events you've added ## Widgets Dashboard widgets showing pages, sources, campaigns, and more The dashboard includes several widgets to break down your traffic: | Widget | Tabs | Description | | ------------------- | -------------------------------------------- | ---------------------------------------- | | **Top Pages** | Landing pages, Exits | Most visited entry and exit pages | | **Top Sources** | Referrers, Channels, Channel groups | Where your traffic comes from | | **Top Campaigns** | Campaigns, Sources, Mediums, Contents, Terms | UTM parameter breakdown | | **Countries** | Map, List | Geographic distribution of visitors | | **Traffic Heatmap** | Sessions, TimeScore | Traffic patterns by day of week and hour | | **Devices** | Devices, Browsers, OS | Technical breakdown of visitors | ### Click-to-Filter Click any row in a widget to instantly filter the entire dashboard by that value. For example: * Click a channel to see only traffic from that channel * Click a country to focus on that geographic region * Click a landing page to analyze traffic to that specific page Click the same row again to remove the filter. Active filters appear below the date picker. ## Real-Time View Click the **Live** button next to the dashboard title to open the real-time view. This shows visitors currently active on your site in the last 30 minutes. Real-time live sessions view with world map The real-time view includes: * **World map** — See where active visitors are located * **Live metrics** — Current session count and engagement * **Active pages** — Pages being viewed right now * **Live sources** — Channels, referrers, and campaigns driving current traffic * **Devices** — Real-time device and browser breakdown Use the real-time view to monitor campaign launches, check if tracking is working after deploying the SDK, or watch traffic during peak events. # Explore Report Source: https://docs.staminads.com/explore-report Deep dive into your data to find insights The Explore report lets you drill down into your data by any dimension to spot patterns and find insights. Explore report interface ## Use Cases * **Find high-performing traffic sources** — Identify new sources with top TimeScores * **Optimize landing pages** — Detect which pages have the best engagement for a given campaign * **Audit expensive campaigns** — Measure the traffic quality of high-spend campaigns * **Device analysis** — Find out the impact of device types on engagement * **Channel comparison** — Compare TimeScores across your marketing channels ## How It Works 1. **Select dimensions** — Choose what to group by (source, landing page, device, etc.) 2. **Apply filters** — Narrow down to specific traffic segments 3. **Sort by metrics** — Order by sessions, TimeScore, bounce rate, etc. 4. **Drill down** — Click any row to add it as a filter and explore deeper Explore report with channel breakdown Start broad (e.g., by channel) then drill down into specific dimensions to understand what drives engagement. The best insights often come from unexpected combinations. # Goals Source: https://docs.staminads.com/goals Track conversions and measure the value of your traffic Goals allow you to track conversions and measure the business value of your traffic. Each goal can have an optional monetary value, helping you understand which traffic sources, campaigns, and pages drive the most value. Goals page showing goal cards with KPIs and charts ## Goal Cards Each goal is displayed as a card with three key metrics: | Metric | Description | | ---------- | --------------------------------------------------------------------- | | **Count** | Total number of times the goal was triggered | | **Value** | Sum of all goal values (if values are tracked) | | **Median** | Median goal value — useful for understanding typical conversion value | Click any metric to switch the mini chart to display that metric's evolution over time. When comparison mode is enabled, each metric shows the percentage change compared to the previous period. ## Date Range & Comparison Use the date picker to select a time period for analysis. Enable **comparison mode** to see how your goals are performing compared to the previous period. The mini charts on each card will show both the current period (purple line) and the previous period (gray line) when comparison is enabled. ## Goal Dashboard Click the eye icon on any goal card to open the full goal dashboard drawer. Goal dashboard drawer showing detailed metrics and breakdowns The goal dashboard provides detailed analysis for a specific goal: ### KPI Summary The top section displays the three main metrics (Count, Value, Median) with comparison percentages. Click any metric to view its time-series chart. ### Time-Series Chart Visualize the selected metric over time with: * **Granularity** based on date range (hourly, daily, weekly, monthly) * **Period comparison** overlay when comparison mode is enabled * **Annotations** showing significant events you've marked ### Breakdown Widgets | Widget | Tabs | Description | | ----------------- | ----------------------------------- | -------------------------------------------- | | **Top Sources** | Referrers, Channels, Channel groups | Which traffic sources drive conversions | | **Top Campaigns** | Campaigns, Sources, Mediums | UTM parameter breakdown for goal conversions | | **Countries** | Map, List | Geographic distribution of conversions | | **Devices** | Devices, Browsers, OS | Technical breakdown of converting visitors | | **Heatmap** | Goals | Conversion patterns by day of week and hour | ## Tracking Goals Goals are tracked using the Web SDK. See the [Web SDK documentation](/web-sdk#tracking-goals) for implementation details. ```javascript theme={null} // Track a goal without value staminads.goal('signup') // Track a goal with a value staminads.goal('purchase', 99.99) ``` Use meaningful goal names that describe the conversion action, such as `signup`, `purchase`, `newsletter_subscribe`, or `contact_form_submit`. # Installation Source: https://docs.staminads.com/installation Deploy Staminads on your own infrastructure Staminads is self-hosted. Choose the deployment method that fits your infrastructure. ## Quick Deploy TODO: add railway *** ## Docker Compose Best for: **Self-hosting with everything included** (ClickHouse + Staminads in one stack). ### 1. Clone the repository ```bash theme={null} git clone https://github.com/staminads/staminads.git cd staminads ``` ### 2. Create `.env` file ```bash theme={null} # Required - generate with: openssl rand -hex 32 ENCRYPTION_KEY=your-32-character-encryption-key-here # Your Staminads URL (used for email links) APP_URL=https://staminads.yourdomain.com # CORS origins (your Staminads URL) CORS_ALLOWED_ORIGINS=https://staminads.yourdomain.com # Optional: ClickHouse password CLICKHOUSE_PASSWORD= ``` ### 3. Start the stack ```bash theme={null} docker compose up -d ``` Staminads is now running at `http://localhost:3000`. *** ## Dockerfile (External ClickHouse) Best for: **Using an existing ClickHouse instance** or managed ClickHouse services (ClickHouse Cloud, Aiven, etc.). ### 1. Pull the image ```bash theme={null} docker pull pierreb/staminads:latest ``` ### 2. Run with environment variables ```bash theme={null} docker run -d \ --name staminads \ -p 3000:3000 \ -e ENCRYPTION_KEY="your-32-character-encryption-key" \ -e APP_URL="https://staminads.yourdomain.com" \ -e CORS_ALLOWED_ORIGINS="https://staminads.yourdomain.com" \ -e CLICKHOUSE_HOST="https://your-clickhouse-host:8443" \ -e CLICKHOUSE_SYSTEM_DATABASE="staminads_system" \ -e CLICKHOUSE_USER="default" \ -e CLICKHOUSE_PASSWORD="your-clickhouse-password" \ pierreb/staminads:latest ``` When using ClickHouse Cloud or other managed services, use `https://` and port `8443` for secure connections. *** ## Environment Variables ### Required | Variable | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | `ENCRYPTION_KEY` | Encryption key for sensitive data. **Must be at least 32 characters.** Generate with: `openssl rand -hex 32` | | `APP_URL` | Public URL of your Staminads instance (used for email links like password reset, invitations) | | `CLICKHOUSE_HOST` | ClickHouse server URL (e.g., `http://localhost:8123` or `https://cloud.clickhouse.com:8443`) | ### ClickHouse Connection | Variable | Default | Description | | ---------------------------- | ------------------ | ----------------------------------------- | | `CLICKHOUSE_SYSTEM_DATABASE` | `staminads_system` | Database name for Staminads system tables | | `CLICKHOUSE_USER` | `default` | ClickHouse username | | `CLICKHOUSE_PASSWORD` | — | ClickHouse password | ### Server | Variable | Default | Description | | -------------------------------- | ------- | --------------------------------------------------------------------------------------------------- | | `PORT` | `3000` | HTTP server port | | `JWT_EXPIRES_IN` | `7d` | JWT token expiration (e.g., `7d`, `24h`) | | `CORS_ALLOWED_ORIGINS` | `*` | Comma-separated allowed origins for CORS. Track endpoints (`/api/track*`) always allow all origins. | | `CUSTOM_DIMENSIONS_CACHE_TTL_MS` | `15000` | Cache TTL for custom dimensions (milliseconds) | ### SMTP (Optional) Configure SMTP to enable email features (password reset, team invitations). If not set globally, workspace-level SMTP must be configured. | Variable | Default | Description | | ----------------- | ----------- | --------------------------- | | `SMTP_HOST` | — | SMTP server hostname | | `SMTP_PORT` | `587` | SMTP server port | | `SMTP_TLS` | `true` | Use TLS for SMTP connection | | `SMTP_USER` | — | SMTP username | | `SMTP_PASSWORD` | — | SMTP password | | `SMTP_FROM_NAME` | `Staminads` | Sender name for emails | | `SMTP_FROM_EMAIL` | — | Sender email address | *** ## First Login After installation, open your Staminads URL in a browser. You'll be prompted to create your first admin account. First login screen ## Next Steps Set up filters to categorize your traffic sources. Learn about the data you can analyze. # Introduction Source: https://docs.staminads.com/introduction Cookieless, privacy-first traffic quality analytics. Measure engagement with TimeScore. Stop chasing conversions you can't track. Start measuring **engagement** you can trust. Staminads is an open-source, self-hosted analytics platform that measures traffic quality through engagement — not conversions. No cookies, no consent banners, 100% of your traffic captured. Staminads dashboard AGPL-3.0 licensed. Your data, your rules. No cookies, no consent banners. GDPR compliant out of the box. Deploy on your own infrastructure. Free forever. **What we don't measure:** Staminads doesn't provide "unique users" or "visitor counts." Accurate user counting requires cross-device identity reconciliation — knowing that the same person visited from their phone, laptop, and work computer. This is technically impossible without invasive tracking or logged-in user data. Other tools showing these numbers are displaying estimates at best. We don't do fake metrics — if we can't measure it accurately, we don't show it. ## Why Staminads? ### The Problem: Conversion Tracking is Broken Web marketers have relied on conversion tracking (ROI, ROAS) for years. But due to technical and legal constraints, **this approach no longer works**: * Adblockers blocking tracking scripts * Limited cookie lifespans (7 days on Safari) * Cross-device journeys impossible to track * App webviews breaking attribution * GDPR consent requirements * Cookie banner fatigue (users opt out) * Privacy regulations tightening globally * First-party data limitations Your conversion data is incomplete. You're optimizing campaigns based on a fraction of reality. ### The Solution: Engagement-Based Analytics Conversions don't happen in one visit. **Engagement does.** A typical customer journey looks like this: | Visit | Intent | What You Can Measure | | ----- | -------- | ----------------------------- | | 1 | Research | ✅ Engagement | | 2 | Compare | ✅ Engagement | | 3 | Decide | ✅ Engagement | | 4 | Convert | ❌ Often lost to tracking gaps | The role of marketing isn't to convert — it's to **move the buying process forward**. Staminads measures what matters at every step: how engaged your visitors are. ## Core Concepts ### TimeScore The **TimeScore** is the median time spent on your website for each traffic source, expressed in seconds. Why median instead of average? Because medians are resistant to outliers — a single visitor leaving a tab open for hours won't skew your data. TimeScore measures real engagement. A visitor who spends 4 minutes reading your pricing page is more valuable than one who bounces after 10 seconds — regardless of whether they convert today. ## Key Features Separate Google Ads from Google Organic. Group landing pages by product category. Create channel definitions that match your business. Ask questions in natural language. "Which traffic source has the best engagement this week?" Get insights without writing queries. Drill down by any dimension — source, campaign, country, device, or custom dimensions. Filter and combine in real time. Isolated workspaces for multiple sites or clients. Granular user roles: Admin, Editor, Viewer. Powered by ClickHouse. Sub-50ms query latency. Handle billions of events without breaking a sweat. Fully responsive dashboard. Check your analytics on any device. ## Next Steps Deploy Staminads on Railway, PikaPods, or your own infrastructure with Docker. Deep dive into your data to find insights and optimize your traffic sources. Create filter rules to categorize traffic by channel, page type, or custom dimensions. Star the repo, report issues, or contribute to the project. # Metrics & Dimensions Source: https://docs.staminads.com/metrics-dimensions Complete reference for all metrics and dimensions available in Staminads Staminads collects data into two tables, each with its own set of metrics and dimensions. Session-level aggregated data. One row per visitor session. Use for traffic analysis, channel comparison, and engagement metrics. Per-page view data with duration tracking. One row per page viewed. Use for content performance and exit analysis. *** ## Metrics ### Session Metrics | Metric | Description | Example | | ---------------------- | -------------------------------------------------- | -------- | | `sessions` | Total number of sessions | `1,234` | | `median_duration` | Median session duration in seconds (**TimeScore**) | `38.0` | | `avg_duration` | Average session duration in seconds | `45.3` | | `bounce_rate` | Percentage of sessions under bounce threshold | `34.56%` | | `pageviews` | Total pageviews across all sessions | `3,456` | | `pages_per_session` | Average pages viewed per session | `2.81` | | `median_scroll` | Median max scroll depth percentage | `72.0%` | | `max_scroll` | Average max scroll depth percentage | `67.2%` | | `median_page_duration` | Session-weighted median time on page (seconds) | `23.0` | The primary engagement metric in Staminads. Represents the median time users spend actively engaged in a session. **Why median?** Medians are resistant to outliers. A single visitor leaving a tab open for hours won't skew your data. **Example:** If sessions have durations `[10, 20, 30, 120, 300]`, the median is `30s` (not `96s` average). Sessions shorter than the bounce threshold (default: 10 seconds) are considered "bounces." The threshold is configurable in workspace settings. Lower bounce rate is generally better. **Session-weighted**: Each session contributes equally regardless of page count. Useful for comparing engagement quality across traffic sources. Different from `page_duration` (pages table) which is page-weighted. ### Page Metrics | Metric | Description | Example | | -------------------- | ---------------------------------- | -------- | | `page_count` | Total page views | `5,678` | | `unique_pages` | Number of unique page paths | `42` | | `page_duration` | Median time on page in seconds | `28.0` | | `page_scroll` | Median scroll depth percentage | `65.0%` | | `landing_page_count` | Number of landing page views | `1,234` | | `exit_page_count` | Number of exit page views | `1,234` | | `exit_rate` | Percentage of views that are exits | `45.67%` | **Exit rate context matters:** * High exit rate on checkout page = problem * High exit rate on "thank you" page = expected *** ## Dimensions ### Traffic Source | Dimension | Description | Example Values | | ----------------- | --------------------------------------- | --------------------------------- | | `referrer` | Full referrer URL | `https://google.com/search?q=...` | | `referrer_domain` | Referrer domain only | `google.com`, `facebook.com` | | `referrer_path` | Referrer URL path | `/search`, `/posts/123` | | `is_direct` | Whether traffic is direct (no referrer) | `true`, `false` | ### UTM Parameters | Dimension | Description | Example Values | | -------------- | ------------------------- | ------------------------------------------------ | | `utm_source` | Traffic source | `google`, `newsletter`, `facebook` | | `utm_medium` | Marketing medium | `cpc`, `email`, `social`, `organic` | | `utm_campaign` | Campaign name | `summer_sale`, `product_launch` | | `utm_term` | Paid search keywords | `running shoes`, `analytics tool` | | `utm_content` | Ad content identifier | `banner_v1`, `text_link` | | `utm_id` | Ad network click ID value | `Cj0KCAiAxea5...`, `fb.1.123...` | | `utm_id_from` | Origin of the click ID | `gclid`, `gbraid`, `wbraid`, `fbclid`, `msclkid` | `utm_id_from` is useful for identifying paid traffic from specific ad networks. See [Traffic Categorization](/traffic-categorization#ad-network-click-identifiers) for the full list of supported click IDs. ### Channel Channels are set via [Traffic Categorization](/traffic-categorization) filters. | Dimension | Description | Example Values | | --------------- | ---------------- | ----------------------------------------- | | `channel` | Traffic channel | `Organic Search`, `Paid Search`, `Direct` | | `channel_group` | Channel grouping | `Search`, `Social`, `Referral`, `Direct` | **Common channel values:** * `Direct` — No referrer, typed URL * `Organic Search` — Google, Bing, etc. without ads * `Paid Search` — Google Ads, Bing Ads * `Social` — Facebook, Twitter, LinkedIn, etc. * `Referral` — Links from other websites * `Email` — Email campaigns * `Display` — Display advertising ### Landing & Exit Pages | Dimension | Description | Example Values | | ---------------- | ------------------------- | -------------------------------- | | `landing_page` | Full landing page URL | `https://example.com/products` | | `landing_domain` | Landing page domain | `example.com` | | `landing_path` | Landing page path | `/`, `/products`, `/blog/post-1` | | `exit_path` | Last page path in session | `/checkout`, `/thank-you` | ### Page (Pages Table) | Dimension | Description | Example Values | | ----------------- | ------------------------------- | -------------------------- | | `page_path` | Page URL path | `/`, `/products`, `/about` | | `is_landing_page` | Whether this was the entry page | `true`, `false` | | `is_exit_page` | Whether this was the exit page | `true`, `false` | | `page_entry_type` | How user arrived at page | `landing`, `navigation` | ### Device | Dimension | Description | Example Values | | ----------------- | ----------------------- | --------------------------------------------- | | `device` | Device category | `desktop`, `mobile`, `tablet` | | `browser` | Browser name | `Chrome`, `Safari`, `Firefox`, `Edge` | | `browser_type` | Browser engine type | `chromium`, `webkit`, `gecko` | | `os` | Operating system | `Windows`, `macOS`, `iOS`, `Android`, `Linux` | | `screen_width` | Screen width in pixels | `1920`, `1440`, `375` | | `screen_height` | Screen height in pixels | `1080`, `900`, `812` | | `viewport_width` | Browser viewport width | `1920`, `1200`, `375` | | `viewport_height` | Browser viewport height | `969`, `800`, `667` | | `connection_type` | Network connection type | `4g`, `3g`, `wifi`, `slow-2g` | ### Geographic | Dimension | Description | Example Values | | ----------- | --------------------------------- | ---------------------------------------- | | `country` | Country code (ISO 3166-1 alpha-2) | `US`, `GB`, `FR`, `DE`, `JP` | | `region` | Region/state | `California`, `England`, `Île-de-France` | | `city` | City name | `San Francisco`, `London`, `Paris` | | `latitude` | Latitude coordinate | `37.7749`, `51.5074` | | `longitude` | Longitude coordinate | `-122.4194`, `-0.1278` | | `language` | Browser language | `en-US`, `fr-FR`, `de-DE`, `ja-JP` | | `timezone` | User timezone | `America/Los_Angeles`, `Europe/London` | ### Time | Dimension | Description | Example Values | | ------------- | -------------------------------- | ---------------------- | | `year` | Year of session | `2024`, `2025`, `2026` | | `month` | Month (1-12) | `1`, `6`, `12` | | `day` | Day of month (1-31) | `1`, `15`, `31` | | `day_of_week` | Day of week (1=Monday, 7=Sunday) | `1`, `5`, `7` | | `week_number` | ISO week number (1-53) | `1`, `26`, `52` | | `hour` | Hour of day (0-23) | `0`, `12`, `23` | | `is_weekend` | Whether session was on weekend | `true`, `false` | ### Session | Dimension | Description | Example Values | | ---------------- | --------------------------- | ----------------------- | | `duration` | Session duration in seconds | `0`, `30`, `120`, `600` | | `pageview_count` | Pages viewed in session | `1`, `3`, `10` | ### Custom Dimensions 10 custom dimension slots are available for your own data. Set via filters or the SDK. | Dimension | Description | | --------- | ------------------- | | `stm_1` | Custom dimension 1 | | `stm_2` | Custom dimension 2 | | `stm_3` | Custom dimension 3 | | `stm_4` | Custom dimension 4 | | `stm_5` | Custom dimension 5 | | `stm_6` | Custom dimension 6 | | `stm_7` | Custom dimension 7 | | `stm_8` | Custom dimension 8 | | `stm_9` | Custom dimension 9 | | `stm_10` | Custom dimension 10 | **Setting via SDK:** ```javascript theme={null} staminads.setDimension(1, 'premium'); // stm_1 = 'premium' staminads.setDimension(2, 'logged_in'); // stm_2 = 'logged_in' ``` **Setting via Filters:** See [Traffic Categorization](/traffic-categorization) for rule-based dimension assignment. *** ## Quick Reference ### Which metric for which question? | Question | Metrics | Table | | ------------------------- | ------------------------------------------- | -------- | | How many visitors? | `sessions` | sessions | | How engaged are users? | `median_duration`, `median_scroll` | sessions | | Are users bouncing? | `bounce_rate` | sessions | | Which pages perform best? | `page_duration`, `page_scroll` | pages | | Where do users exit? | `exit_rate`, `exit_page_count` | pages | | Traffic source quality? | `median_page_duration`, `pages_per_session` | sessions | | Content depth? | `pages_per_session`, `max_scroll` | sessions | *** ## Metrics We Don't Support Staminads is built on a fundamental principle: **we won't show you data we can't measure accurately**. Some metrics that other analytics tools display are technically impossible to track reliably in a cookieless, privacy-first world. Rather than showing you misleading numbers, we don't include them at all. **Why we don't support it:** Counting unique users requires identifying the same person across multiple sessions. Without cookies or persistent identifiers, this is technically impossible. A person visiting from their phone, then their laptop, then their work computer appears as three separate visitors. There's no privacy-respecting way to link these sessions. **What to use instead:** `sessions` — the number of sessions is accurate and meaningful. Focus on session quality (TimeScore) rather than trying to count people you can't identify. **Why we don't support it:** Identifying returning visitors requires recognizing someone who visited before. Without cookies, we can't know if a session is from a new or returning visitor. **What to use instead:** Focus on engagement metrics. A highly engaged session (high TimeScore) often indicates familiarity with your content, regardless of whether we can technically label it as "returning." **Why we don't support it:** Return on Ad Spend requires tracking conversions back to the ad click that caused them. This is broken for multiple reasons: * **Cross-device journeys** — Someone clicks an ad on mobile, converts on desktop * **Multi-touch attribution** — Multiple channels contribute to a conversion * **Cookie restrictions** — Safari limits cookies to 7 days, adblockers delete them * **Consent requirements** — Users who decline tracking can't be attributed Your ROAS numbers in other tools are incomplete — they only count conversions they can track, which is a shrinking percentage of actual conversions. **What to use instead:** TimeScore and engagement metrics. Measure whether your ads bring engaged visitors. Engagement is measurable on every session, not just the ones that happen to convert within a trackable window. This isn't a limitation of Staminads — it's a limitation of the modern web. Any tool claiming to accurately count unique users or attribute conversions without cookies is either using fingerprinting (privacy-invasive) or showing you incomplete data. We choose transparency over false precision. # Traffic Categorization Source: https://docs.staminads.com/traffic-categorization Categorize your traffic using flexible filter rules Filters let you categorize your traffic by defining rules that match sessions and assign them to channels, groups, or custom dimensions. This is the foundation of meaningful traffic analysis in Staminads. Filters list page showing all configured traffic categorization rules A default set of common traffic sources is created for you (Google SEO, Google Ads, Facebook Ads, TikTok Ads, etc.). However, **you should create filters for your specific traffic sources** to avoid them being grouped as "not categorized". ## How Filters Work Each filter consists of: 1. **Conditions** — Rules that match incoming sessions (e.g., "utm\_source contains google") 2. **Operations** — What to do when conditions match (e.g., "set channel to Google Ads") 3. **Priority** — When multiple filters match, the highest priority wins ``` Session arrives → Conditions evaluated → Operations applied → Session categorized ``` Filter form showing conditions and operations configuration ## What You Can Match (Conditions) Filters can match on a wide range of session attributes: | Category | Fields | | ----------- | ---------------------------------------------------------------------------------------------- | | **UTM** | `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`, `utm_id`, `utm_id_from` | | **Traffic** | `referrer`, `referrer_domain`, `referrer_path`, `is_direct` | | **Pages** | `landing_page`, `landing_domain`, `landing_path`, `path` | | **Device** | `device`, `browser`, `browser_type`, `os`, `user_agent`, `connection_type` | | **Geo** | `language`, `timezone` | ### Operators | Operator | Description | | --------------- | ---------------------------------- | | `equals` | Exact match | | `not equals` | Does not match exactly | | `contains` | Contains substring | | `not contains` | Does not contain substring | | `is empty` | Field has no value | | `is not empty` | Field has a value | | `matches regex` | Matches regular expression pattern | ## What You Can Set (Operations) When conditions match, filters can write to these dimensions: | Category | Dimensions | | ----------- | --------------------------------------------------------------------- | | **Channel** | `channel`, `channel_group` | | **Custom** | `stm_1` through `stm_10` (Custom Dimension 1-10) | | **UTM** | `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content` | | **Traffic** | `referrer_domain`, `is_direct` | ### Actions | Action | Description | | --------------- | ---------------------------------------- | | **Set value** | Always set to the specified value | | **Clear value** | Set to null/empty | | **Set default** | Set only if the field is currently empty | ## Custom Dimension Labels By default, custom dimensions are labeled `stm_1` through `stm_10`. You can assign meaningful names to these dimensions in **Workspace Settings > Custom Dimensions** to make reports easier to understand. Custom dimension labels configuration in workspace settings **Example labels:** | Slot | Label | | ------- | ---------------- | | `stm_1` | Product Category | | `stm_2` | Funnel Stage | | `stm_3` | Content Type | Once labeled, reports will display "Product Category" instead of "stm\_1", making your data more intuitive to analyze. ## Use Cases ### Channel Mapping The most common use case — categorize traffic by marketing channel. **Example: Google Ads filter** * Condition: `utm_id_from` equals `gclid` * Operation: Set `channel` to `Google Ads`, set `channel_group` to `Paid Search` **Example: Google SEO filter** * Condition: `referrer_domain` contains `google` * Condition: `utm_id_from` is empty * Operation: Set `channel` to `Google SEO`, set `channel_group` to `Organic` ### Page Categorization Use custom dimensions to categorize pages by product, funnel stage, or content type. **Example: Product category filter** * Condition: `landing_path` contains `/shoes/` * Operation: Set `stm_1` (Product Category) to `Shoes` **Example: Funnel stage filter** * Condition: `landing_path` equals `/pricing` * Operation: Set `stm_2` (Funnel Stage) to `Consideration` ### Brand vs Non-Brand Organic Separate branded searches (people searching for your company name) from generic organic traffic. **Example: Brand organic filter** * Condition: `landing_path` equals `/` * Condition: `referrer_domain` contains `google` * Condition: `utm_medium` equals `referral` * Condition: `utm_id_from` is empty * Operation: Set `channel` to `Brand Organic`, set `channel_group` to `Direct` Brand organic traffic typically lands on your homepage from Google without UTM parameters. These visitors already know your brand — they should be measured separately from discovery-phase organic traffic. ## Priority & Overlap When a session matches multiple filters, the filter with the **highest priority** (0-1000) wins. **Example priority structure:** | Priority | Filter | Rationale | | -------- | -------------- | ---------------------------------------------- | | 900 | Google Ads | Specific paid traffic, should override organic | | 800 | Facebook Ads | Specific paid traffic | | 500 | Google SEO | General organic | | 500 | Direct Traffic | General direct | | 100 | Catch-all | Default for unmatched traffic | ## Backfill Historical Data When you create or modify filters, they only apply to new sessions by default. Use the **Backfill** feature to apply your current filter rules to historical data. This is useful when: * You add a new filter and want it to apply retroactively * You fix a misconfigured filter * You add new custom dimensions Backfilling rewrites dimension values for historical sessions. The operation is safe (original source data is preserved), but large date ranges may take time to process. ## UTM Parameters Reference UTM parameters are the standard way to tag campaign URLs for tracking. | Parameter | Description | Example | | -------------- | -------------------- | ------------------------------------ | | `utm_source` | Traffic source | `google`, `facebook`, `newsletter` | | `utm_medium` | Marketing medium | `cpc`, `email`, `social`, `referral` | | `utm_campaign` | Campaign name | `black-friday-2025`, `spring-sale` | | `utm_term` | Paid search keywords | `running shoes`, `best crm` | | `utm_content` | Ad variation | `banner-v1`, `text-link` | ### Ad Network Click Identifiers Staminads automatically extracts click IDs from ad network URLs and stores them in two fields: | Field | Description | | ------------- | ---------------------------------------- | | `utm_id` | The click ID value | | `utm_id_from` | Origin of the click ID (see table below) | #### Google Ads Click IDs Google uses multiple click identifiers depending on the user's device and privacy settings: | Parameter | Name | When Used | | --------- | ----------------- | --------------------------------------------------------------------------------- | | `gclid` | Google Click ID | Desktop, Android, and iOS users who grant tracking consent | | `gbraid` | Google Browser ID | iOS 14.5+ web clicks when user declines tracking (privacy-preserving, aggregated) | | `wbraid` | Google Web ID | iOS web-to-web and app-to-web journeys when gclid cannot be passed | Unlike `gclid` which identifies individual clicks, `gbraid` and `wbraid` are privacy-preserving parameters that use aggregated signals to comply with Apple's App Tracking Transparency (ATT) framework. #### Other Ad Networks | Parameter | Ad Network | | ----------- | -------------------------- | | `fbclid` | Meta (Facebook, Instagram) | | `msclkid` | Microsoft Ads (Bing) | | `ttclid` | TikTok Ads | | `li_fat_id` | LinkedIn Ads | | `twclid` | Twitter/X Ads | | `dclid` | Google Display & Video 360 | **Example:** A Google Ads click generates a URL like: ``` https://example.com?utm_source=google&utm_medium=cpc&gclid=Cj0KCAiAxea5... ``` Staminads extracts: * `utm_id` = `Cj0KCAiAxea5...` * `utm_id_from` = `gclid` To reliably identify Google Ads traffic regardless of which click ID is used, create a filter with: * `utm_id_from` matches regex `^(gclid|gbraid|wbraid)$` # Users & API Keys Source: https://docs.staminads.com/users-api-keys Manage team access and API credentials for your workspace Control who has access to your workspace and manage API keys for programmatic access to your data. ## Team Members Invite team members to collaborate on your workspace. Each member is assigned a role that determines their permissions. Team members list ### Roles | Role | Description | | ---------- | ----------------------------------------------------------------------------------------- | | **Owner** | Full control including billing and workspace deletion. Cannot be assigned via invitation. | | **Admin** | Manage members, settings, filters, and view all analytics | | **Editor** | Create and edit filters, annotations, and view analytics | | **Viewer** | Read-only access to reports and analytics | ### Inviting Members 1. Navigate to **Settings > Team** 2. Click **Invite Member** 3. Enter the email address and select a role 4. The invitee receives an email with a link to join Invite member form Invitations expire after 7 days. You can resend or revoke pending invitations from the Team settings page. ## API Keys API keys allow programmatic access to Staminads for sending events, querying analytics, and managing workspace configuration. API keys management ### Creating an API Key 1. Navigate to **Settings > API Keys** 2. Click **Create API Key** 3. Enter a name and optional description 4. Select the appropriate role (determines scopes) 5. Optionally set an expiration date 6. Copy the key immediately — it won't be shown again API keys are only displayed once at creation. Store them securely. If you lose a key, you'll need to create a new one. ### API Key Roles | Role | Description | Use Case | | ---------- | --------------------------------- | -------------------------------- | | **Admin** | Full API access | Backend integrations, automation | | **Editor** | Track events and manage analytics | SDK integration, data pipelines | | **Viewer** | Read-only analytics access | Dashboards, reporting tools | ### Scopes Each role grants a specific set of scopes: | Scope | Description | Admin | Editor | Viewer | | -------------------- | ----------------------------------- | :---: | :----: | :----: | | `events.track` | Send session and event data via SDK | ✓ | ✓ | | | `analytics.view` | Query analytics data | ✓ | ✓ | ✓ | | `analytics.export` | Export analytics data | ✓ | ✓ | | | `workspace.read` | Read workspace configuration | ✓ | | ✓ | | `filters.manage` | Create and manage filters | ✓ | ✓ | | | `annotations.manage` | Create and manage annotations | ✓ | ✓ | | ### Key Status | Status | Description | | ----------- | ---------------------------------- | | **Active** | Key is valid and can be used | | **Expired** | Key has passed its expiration date | | **Revoked** | Key was manually disabled | ### Revoking Keys To revoke an API key: 1. Navigate to **Settings > API Keys** 2. Find the key and click **Revoke** 3. Confirm the action Revoked keys are immediately invalidated and cannot be restored. Create separate API keys for different environments (development, staging, production) and integrations. This makes it easy to rotate keys without affecting other systems. ## Security Best Practices * **Use the minimum required role** — Don't use Admin keys when Viewer access is sufficient * **Set expiration dates** — For temporary integrations, set keys to expire automatically * **Rotate keys regularly** — Periodically create new keys and revoke old ones * **Never commit keys to version control** — Use environment variables or secret managers * **Monitor usage** — Check `last_used_at` to identify unused keys that should be revoked # Web SDK Source: https://docs.staminads.com/web-sdk Track sessions, events, and engagement on your website The Staminads Web SDK captures session data, tracks engagement, and sends events to your workspace. It's lightweight (\~18KB gzipped), privacy-focused, and works across all modern browsers. ## Installation Add the configuration and script to your HTML: ```html theme={null} ``` Place the config script in the `` and the SDK script before the closing `` tag for optimal performance. ## Configuration Set `window.StaminadsConfig` **before** loading the SDK: ```javascript theme={null} window.StaminadsConfig = { // Required workspace_id: 'ws_your_workspace_id', endpoint: 'https://your-staminads-server.com', // Optional debug: false, // Enable console logging sessionTimeout: 30 * 60 * 1000, // 30 minutes (default) // Features trackSPA: true, // Auto-track SPA navigation trackScroll: true, // Track scroll depth }; ``` ### Configuration Options | Option | Type | Default | Description | | ------------------------ | --------- | ---------- | ------------------------------------------ | | `workspace_id` | string | *required* | Your workspace identifier | | `endpoint` | string | *required* | API endpoint URL | | `debug` | boolean | `false` | Enable debug logging | | `sessionTimeout` | number | `1800000` | Session timeout in ms (30 min) | | `trackSPA` | boolean | `true` | Auto-track single page app navigation | | `trackScroll` | boolean | `true` | Track scroll depth milestones | | `crossDomains` | string\[] | `[]` | Domains to share sessions with | | `crossDomainExpiry` | number | `120` | Cross-domain link expiry in seconds | | `crossDomainStripParams` | boolean | `true` | Remove `_stm` param from URL after reading | ### Cross-Domain Tracking By default, sessions are isolated per domain. When a visitor navigates from `blog.example.com` to `shop.example.com`, the SDK would create a **new session** on the shop domain — losing the connection to the original visit. Cross-domain tracking solves this by sharing session identity across domains, so you get: * **Accurate session counts** — One visitor journey = one session, not multiple * **Complete user paths** — See the full journey from blog article to checkout * **Correct attribution** — UTM parameters and referrer data stay with the session ```javascript theme={null} window.StaminadsConfig = { workspace_id: 'ws_your_workspace_id', endpoint: 'https://your-staminads-server.com', // List all domains that should share sessions crossDomains: ['shop.example.com', 'blog.example.com'], crossDomainExpiry: 120, // Link valid for 2 minutes (default) crossDomainStripParams: true // Remove _stm param after reading (default) }; ``` All domains must use the **same workspace\_id** and have the SDK installed with the same `crossDomains` configuration. #### How It Works 1. When a user clicks a link to a configured domain, the SDK appends a `_stm` parameter with encrypted session data 2. On the target domain, the SDK reads the parameter and continues the same session 3. The parameter is stripped from the URL automatically (if `crossDomainStripParams: true`) #### Manual URL Decoration For programmatic navigation, use `decorateUrl()`: ```javascript theme={null} // Decorate a URL with session data const decoratedUrl = Staminads.decorateUrl('https://shop.example.com/checkout'); // Returns: https://shop.example.com/checkout?_stm=eyJzIjoi... // Use with programmatic navigation window.location.href = decoratedUrl; ``` Cross-domain linking only works for domains in the `crossDomains` array. Links to other domains are not decorated. ## API Reference ### Session Information ```javascript theme={null} // Get current session ID const sessionId = await Staminads.getSessionId(); // Get persistent visitor ID const visitorId = await Staminads.getVisitorId(); // Get authenticated user ID (if set) const userId = await Staminads.getUserId(); // Get active engagement time (milliseconds) const focusDuration = await Staminads.getFocusDuration(); // Get total time since session start (milliseconds) const totalDuration = await Staminads.getTotalDuration(); // Get current config const config = Staminads.getConfig(); ``` ### Page Views Page views are tracked automatically, but you can trigger them manually for SPAs: ```javascript theme={null} // Track current page await Staminads.trackPageView(); // Track specific URL await Staminads.trackPageView('/custom/path'); ``` ### Goals Track conversions and goals with optional value: ```javascript theme={null} // Simple goal await Staminads.trackGoal({ action: 'purchase' }); // Goal with value await Staminads.trackGoal({ action: 'purchase', id: 'order-456', value: 99.99, currency: 'USD', properties: { plan: 'premium' } }); ``` | Field | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------ | | `action` | string | Yes | Goal name/action | | `id` | string | No | Unique goal identifier | | `value` | number | No | Monetary value | | `currency` | string | No | Currency code (USD, EUR, etc.) | | `properties` | object | No | Custom key-value pairs | ### Custom Dimensions Set custom dimensions (1-10) for advanced segmentation. There are two ways to set dimensions: #### Via URL Parameters (Automatic) Custom dimensions can be set via URL parameters `stm_1` through `stm_10`. They are automatically captured when the SDK initializes: ``` https://example.com/page?stm_1=campaign_a&stm_2=variant_b&stm_3=source_x ``` This is useful for: * Campaign tracking links * A/B test variant assignment * Affiliate tracking * Any scenario where you want to pass dimension values via URL **Priority rule**: Existing dimension values take priority over URL parameters. If a dimension is already set (e.g., from a previous page in the session), the URL parameter will NOT overwrite it. #### Via JavaScript (Programmatic) ```javascript theme={null} // Set single dimension await Staminads.setDimension(1, 'premium'); await Staminads.setDimension(2, 'returning'); // Set multiple dimensions await Staminads.setDimensions({ 1: 'premium', 2: 'returning', 3: 'mobile-app' }); // Get dimension value const value = await Staminads.getDimension(1); // Clear all dimensions await Staminads.clearDimensions(); ``` Custom dimensions are sent as `stm_1` through `stm_10` in events. Label them in **Workspace Settings > Custom Dimensions** for readable reports. ### User Identification Associate sessions with authenticated users for cross-device tracking and user-level analytics: ```javascript theme={null} // Set user ID after login await Staminads.setUserId('user_12345'); // Get current user ID const userId = await Staminads.getUserId(); // Clear user ID on logout await Staminads.setUserId(null); ``` | Behavior | Description | | ----------- | ------------------------------------------------- | | Persistence | User ID persists across page reloads and sessions | | Max length | 256 characters | | Reset | Calling `reset()` clears the user ID | **Consent required**: Linking analytics data to user identifiers requires explicit user consent under GDPR, ePrivacy Directive, and similar privacy regulations. Only call `setUserId()` after obtaining valid consent for analytics tracking. Ensure your privacy policy discloses this data collection. Set the user ID immediately after login (and consent) to ensure all subsequent events are associated with the user. ### Control Methods ```javascript theme={null} // Pause tracking (e.g., for cookie consent) await Staminads.pause(); // Resume tracking await Staminads.resume(); // Reset session (start fresh, clears user ID) await Staminads.reset(); ``` ### Debugging ```javascript theme={null} // Get debug information const debug = Staminads.debug(); console.log(debug); // { // session: { id: '...', visitor_id: '...', ... }, // config: { workspace_id: '...', ... }, // focusState: 'FOCUSED', // isTracking: true, // queueLength: 0 // } ``` ## Automatic Tracking The SDK automatically tracks the following without any code: | Event | Trigger | Data Captured | | ---------------- | ------------------------------ | ----------------------------------------- | | **screen\_view** | Page load, SPA navigation | Path, referrer, UTM params, previous page | | **ping** | Heartbeat (10-30s intervals) | Duration, scroll depth | | **scroll** | 25%, 50%, 75%, 100% milestones | Max scroll percentage | ### UTM Parameters UTM parameters are automatically captured from URLs: | Parameter | Description | | -------------- | --------------------------------- | | `utm_source` | Traffic source (google, facebook) | | `utm_medium` | Marketing medium (cpc, email) | | `utm_campaign` | Campaign name | | `utm_term` | Paid search keywords | | `utm_content` | Ad variation | ### Ad Click IDs The SDK automatically detects and captures ad network click IDs: | Parameter | Network | | ------------------ | -------------------------- | | `gclid` | Google Ads | | `gbraid`, `wbraid` | Google Ads (iOS) | | `fbclid` | Meta (Facebook, Instagram) | | `msclkid` | Microsoft Ads | | `ttclid` | TikTok Ads | | `li_fat_id` | LinkedIn Ads | | `twclid` | Twitter/X Ads | | `dclid` | Google Display & Video 360 | ## Focus State Tracking The SDK tracks engagement using a focus state machine: | State | Description | | ----------- | ------------------------------ | | **FOCUSED** | Tab is visible and active | | **BLURRED** | Tab is visible but not focused | | **HIDDEN** | Tab is hidden or minimized | Only **FOCUSED** time counts toward engagement duration, giving you accurate TimeScore calculations. ## Offline Support Events are queued when offline and sent when connectivity returns: * Queue holds up to 50 events * Events expire after 24 hours * Uses Beacon API with Fetch fallback * Handles Safari Private Mode gracefully ## Privacy & Compliance * **No cookies** — Uses localStorage with memory fallback * **No PII** — Does not collect personal identifiable information * **Bot filtering** — Automatically excludes crawlers and bots