---
name: gentic-analytics
description: "Give your AI agent read-only access to your site analytics — Gentic Ingest, PostHog, and Northbeam — through one MCP endpoint. Sessions, scroll depth, funnel drop-off, ad attribution, and saved insights."
license: MIT
metadata:
  author: gentic
  version: "1.0.0"
---

# Gentic Analytics

Give your AI agent read access to your site analytics — Gentic Ingest, PostHog, and Northbeam — through one MCP endpoint. Analyze sessions, scroll depth, rage clicks, funnel drop-off, ad attribution, and saved insights. Drop into constrained SQL or raw HogQL when you need something custom.

## When to apply

- User is debugging a funnel, a landing page, or a campaign and wants to know what's actually happening on the site.
- User wants to compare a recent period against a previous one — traffic, engagement, scroll, clicks.
- User asks about a specific entry URL or pathname.
- User wants to pull a saved PostHog insight or run a custom HogQL query against events / sessions / persons.

## Tools

| Tool | Description | Cost |
|------|-------------|------|
| `gentic_ingest_add_to_cart_value` | Returns SUM(price × quantity) per product_id over the given window (cart_add_value), plus units_added, cart_adders, currency, and pdp_url (modal /products/<handle> URL viewed by this product's cart-adders — useful for naming the product in agent output without a separate Shopify lookup). Ordered by value (default limit 50). IMPORTANT: this is the intended-cart-value revenue PROXY — it is add-to-cart INTENT, NOT confirmed revenue. Purchases are not captured for tracker-only sites, so this is the closest revenue-shaped signal available; never present it to a user as sales or realized revenue. Use it for 'which products drive the most cart value / intent' insights. Pass url/url_prefix to scope to the cohort of subjects who viewed a matching page (the ecommerce events carry no URL of their own). pdp_url caveats: best-effort, subject-bridged from pageviews matching /products/* (Shopify-shaped). It is null for cart-only items (shipping protection, upsells) and non-Shopify carts. Co-added/bundled items can borrow each other's pdp_url because a subject's pageview set applies to every product they added — read with a grain of salt on bundle-heavy catalogs. Response includes a top-level `pdp_url_enrichment` field: 'ok' = complete; 'truncated' = substrate 10k-row cap hit; 'skipped' = disabled via enrich_pdp_url; 'failed' = bridge errored. | 5¢ / call |
| `gentic_ingest_avg_engagement_time` | Returns per-URL engagement metrics — average wallclock dwell time (avg_ms), average active-engagement time (avg_active_ms, NULL for pre-#68 legacy events), and average max scroll depth (0–100) — over the given window. Optionally filtered by URL (exact or prefix). Use this for 'are users actually reading this page' insights — wallclock ≥ active by definition; long wallclock + low active = backgrounded tab. | 5¢ / call |
| `gentic_ingest_bounce_rate` | Returns the bounce rate for the site over the given window — total sessions (containing at least one pageview), bounces (sessions with exactly ONE pageview), and the bounce rate percentage. THE most-requested operator metric: 'how many of our visitors leave immediately?' Pair with gentic_ingest_top_entry_pages to see WHICH landing pages drive the bounces. Denominator semantics matter: bounce = exactly-one-pageview session, NOT 'session where the user disengaged' — engagement-only sessions without a pageview aren't in the denominator at all. Aligns with GA4's definition. Aggregate session model (30-min idle threshold over subject_id, no DOM state captured or reconstructed) — answers 'what fraction of sessions bounced', not 'what did this visitor do.' | 5¢ / call |
| `gentic_ingest_click_counts` | Returns aggregate click counts grouped by element tag + class_chain over the given window. Optionally filtered by URL (exact or prefix). Use this for 'which CTA is getting clicked vs ignored' insights — this surface counts ALL clicks (interactive and non-interactive); for rage-click incidents specifically use gentic_ingest_top_rage_clicks. | 5¢ / call |
| `gentic_ingest_click_heatmap_grid` | Returns a 100×100 viewport-normalized click heatmap grid for the given window — rows of (url, vx_pct, vy_pct, clicks) where coords are integer 0–100. Aggregate-only by design (raw pixel positions never leave the client; 1%-bucketed at source per Principle X) — do NOT promise replay-like 'show me this user's clicks' behavior. Optionally filtered by URL (exact or prefix). Default limit is 10000 — enough for a full single-URL grid without truncation. Edge cells (vx_pct=0/100 or vy_pct=0/100) may carry synthetic concentrations from elastic-scroll / off-viewport clamps; treat edge hotspots as instrumentation noise unless they match a known fixed-position element. | 5¢ / call |
| `gentic_ingest_daily_visitors` | Returns unique-visitor count per UTC day for the given window. Optionally filtered by URL (exact or prefix). Use this for 'how many distinct visitors did the brand get this week vs last week' style insights. Returns one row per day. | 5¢ / call |
| `gentic_ingest_describe_events` | Returns the discovered schema for this site's events: per-event-type total_events / first_seen / last_seen aggregates, plus per-property the JSON type, non-null count, and up to 3 distinct sample values. Call this BEFORE composing a complex filter or writing custom SQL via gentic_ingest_execute_query — without schema knowledge, the LLM will guess property names and fail. Optional since/until scope the schema sampling to a time window (e.g., 'what schemas does this site emit in the last 24h' vs 'historically'); defaults to all-time. Free — call early and often when building ad-hoc queries. Aggregate-only per Principle X — no subject_id or per-user data in the response; sample values are up to 3 distinct property values, not per-user identifiers. | Free |
| `gentic_ingest_execute_query` | Run a constrained SELECT against the events table within the time window. Use when no predefined gentic_ingest_* tool fits the question (e.g., 'top URLs whose engagement_ms grew >50% week-over-week filtered to mobile only'). Returns up to 10,000 rows. site_id is auto-scoped to your authenticated read_key — site_id literals in your SQL filter on top of the scoped CTE, never selecting other sites. Time-window predicates on occurred_at are auto-injected from since/until — your SQL should NOT add them. Costs 25¢ per call (vs 5¢ for predefined gentic_ingest_* tools): prefer purpose-built tools when one fits; fall back here for ad-hoc questions. Call gentic_ingest_describe_events FIRST to enumerate event_types and per-event-type properties — without that, the LLM guesses property names and fails. Errors are passed through verbatim from the substrate ('keyword X is not allowed', 'unknown function Y', 'sql must reference FROM events') so you can self-correct; SQL-validation failures cost $0, execution-time failures bill normally. | 25¢ / call |
| `gentic_ingest_funnel_completion` | Returns unique-subject counts at each step of the on-site ecommerce funnel (pageview → product_viewed → add_to_cart → checkout_started) over the given window. Rows arrive in funnel order, with `unique_subjects` per `event_type`. Use this for 'how far through the funnel do visitors get' insights — derived conversion rates are step-to-step ratios in the row order. IMPORTANT: the funnel terminates at `checkout_started` — `purchase` / `checkout_completed` are NOT measured on this substrate (the first-party tracker structurally cannot capture them: Shopify blocks the script on the checkout/order pages, and customers don't install a server-side pixel). Do not present this as a purchase/revenue funnel. Also note `checkout_started` is a COUNT-only signal — `cart_total` is never populated — so don't derive any cart value or revenue from it. | 5¢ / call |
| `gentic_ingest_list_available_queries` | Returns the catalog of all gentic_ingest_* analytics tools available on this MCP endpoint — each entry has tool_name, a one-line description, category, and the underlying gentic-ingest API endpoint (null for the meta-tool itself). Use this BEFORE composing a multi-tool answer to discover what primitives exist (e.g., before answering a vitals question, call this with category='vitals' to see what's available). Free — no upstream call. | Free |
| `gentic_ingest_list_sites` | Lists the gentic-ingest sites (domains) connected for the current organization, with site_id, created_at, and revoked status. Use this BEFORE calling any other gentic_ingest_* tool on a multi-site org you don't know — pass the resulting `domain` back as the `domain` param on subsequent tool calls. Returns active (non-revoked) sites sorted oldest-first. Free — no upstream call. | Free |
| `gentic_ingest_pageviews_by_device_class` | Returns pageview count and unique-visitor count grouped by device_class (mobile, desktop, or 'unknown' for pre-tracker-v1 legacy data) over the given window. Optionally filtered to a specific URL or URL prefix. Use this for 'what's the mobile-vs-desktop split for this brand's site this week' insights. Returns ≤3 rows. | 5¢ / call |
| `gentic_ingest_period_report` | Single-call period analytics report. Fans out 17 underlying ingest queries over a current period and (optionally) a comparison baseline, then merges into one structured payload with per-metric delta_pct. Use this for 'run the daily/weekly/monthly report' or DoD/WoW/MoM comparison questions — replaces ~30 individual gentic_ingest_* tool calls with one, sized to fit in agent context (~20-30KB per site vs ~1-3MB of by-hand fan-out). RENDER CONTRACT — every field in this response is meant to be surfaced in your output: no MCP-side curation, no suppression thresholds, no 'this signal isn't worth showing' decisions. The per-section top-N cap (default 10, max 25) is the only size discipline and is a token-budget concern, not a signal filter. Walk every section in order, mention every row, do not skip. Cadence presets default the window: 'day' = yesterday vs day-before (DoD), 'week' = last 7d vs the 7d before that (WoW, default), 'month' = last 30d vs the 30d before that (MoM); UTC-day boundaries. Explicit since/until/compare_since/compare_until override; cadence becomes 'custom'. include_compare: false → snapshot-only (every delta_pct: null). Output sections (render in this order to match the canonical daily-report format): (1) top-line scalars — visitor_days NOT unique-over-window (see caveat), bounce_rate_pct, sessions{total/avg_pages/avg_duration}, interactive_clicks total from click_counts, device_split, web_vitals{lcp_p75_ms/cls_p75 averaged across URLs}; (2) per-URL deep-dives — web_vitals_by_url with threshold-crossing flags (Good/NI/Poor), heatmap_by_url with focal cell + concentration ratio, engagement_quality with avg_active_ms + scroll-depth shifts; (3) top-N lists — top_pages, top_entry_pages with bounce_rate_pct/bounce_count in meta, top_sources, top_outbound_destinations, top_form_submits with submits_per_unique retry ratio in meta, top_rage_clicks with element selector in meta (URL-keyed; the element is supplementary context); (4) commerce — funnel_completion per-stage with subjects + computed step_conversion_pct, top_products with pdp_url, top_cart_value with pdp_url + currency. Top-N rows can carry `gone: true` (fell out) and `new: true` (new entrants), so a list can reach up to 2×top_n total rows — surface both. Per-metric fail-soft via metric_errors[]: one flaky substrate query nulls that metric, the rest still returns. INCLUDED — 17 of the 19 gentic_ingest_* primitives: daily-visitors, bounce-rate, sessions-summary, pageviews-by-device-class, web-vitals-summary, top-pages, top-entry-pages, top-sources, top-outbound-destinations, top-form-submits, top-rage-clicks, funnel-completion, product-funnel, add-to-cart-value, click-counts (scalar total only), click-heatmap-grid, avg-engagement-time. NOT INCLUDED (use standalone tools): top-exit-pages (redundant with entry pages), sessions-by-day (time-series belongs in a trend tool); a per-element movers list off click_counts was intentionally removed in PR #588 — without a URL anchor and without stable id/data-* attrs on production components, rows collapsed to Tailwind utility-class fingerprints (`button.flex w-full`) that conflated dozens of unrelated elements. For per-element click tracking call gentic_ingest_click_counts directly on a site that exposes id/data-* attrs. CAVEATS: (1) visitor_days = sum of unique_visitors per UTC day — NOT unique-over-window — a returning customer counts once per day; (2) web_vitals.lcp_p75_ms scalar is averaged equally across URLs — not a true weighted p75; the actionable per-URL view is web_vitals_by_url with threshold-crossing flags; (3) pdp_url is best-effort, Shopify-shaped — same caveats as gentic_ingest_product_funnel; (4) heatmap focal_cell is viewport-normalized — '(50, 50)' means center of the visible viewport when the click happened, not page-center; (5) on top_rage_clicks the row key is the URL where rage clicks occurred; meta.element gives a legible selector hint (`tag#id` if id present, else `tag.firstTwoClasses`) and meta.class_chain carries the full chain — substrate rows that share the same meta.element label collapse into one entry, and meta.class_chain carries the chain of ONE representative row, not the full collapsed set. The delta on a collapsed key conflates volume change with composition change across periods — inherent to aggregation. Tailwind sites where prettier-plugin-tailwindcss sorts classes layout-first can still collapse structurally-unrelated elements onto the same first-two utility classes (e.g. every flex button starts with `inline-flex items-center`); for stable per-element tracking on those sites, expose `id` or `data-*` attributes on production components so the id-priority branch resolves a real identifier. The URL key still anchors the row identity, so this hint is supplementary context rather than the merge key (unlike the now-removed top_element_clicks surface). | 50¢ / call |
| `gentic_ingest_product_funnel` | Returns one row per product_id over the given window: product_viewers, cart_adders, units_added, adders_who_started_checkout, price, view_to_cart_pct, cart_to_checkout_pct, and pdp_url (modal /products/<handle> URL viewed by this product's cart-adders — useful for naming the product in agent output without a separate Shopify lookup). Ordered by adders then viewers (default limit 50), so a no-filter call doubles as 'top products by add-to-cart.' Pass url/url_prefix to scope to the cohort of subjects who viewed a matching page, then see how each product they touched converted (per-product-page funnel). Use this for 'which products convert best from view to cart' insights. CAVEATS, surfaced so they can't mislead: (1) the funnel terminates at checkout_started — purchase/checkout_completed are NOT captured on this substrate, so this is NOT a revenue funnel; (2) cart_to_checkout_pct and adders_who_started_checkout are SUBJECT-bridged (subjects who added the product AND later started any checkout) — session-level, not strict per-product attribution, because checkout_started carries no product_id; (3) pdp_url is best-effort: subject-bridged from pageviews matching /products/* (Shopify-shaped). It is null for cart-only items (shipping protection, upsells) and non-Shopify carts — those rows surface product_id alone, same as before; (4) co-added/bundled items can borrow each other's pdp_url because a subject's pageview set applies to every product they added — read pdp_url with a grain of salt on bundle-heavy catalogs. Response includes a top-level `pdp_url_enrichment` field: 'ok' = mapping is complete; 'truncated' = substrate hit its 10k-row cap, partial mapping (more likely on multi-day windows on busy sites); 'skipped' = enrichment was disabled via enrich_pdp_url; 'failed' = the bridge query errored (parent tool still returns funnel rows, pdp_url is null on every row). | 5¢ / call |
| `gentic_ingest_sessions_by_day` | Returns daily session counts + bounces + average duration over the window, one row per UTC day. Each session is bucketed by the day of its FIRST event (so a session spanning midnight counts on the start day only). Use for trend tracking — 'are sessions trending up?', 'is bounce rate getting worse?' — and as the per-day delta feed for the analytics-report skill's daily-comparison shape. Aggregate session model — no per-user trajectory, just per-day rollups. | 5¢ / call |
| `gentic_ingest_sessions_summary` | Returns dashboard-headline session stats over the given window — total_sessions, avg_pages_per_session, avg_duration_seconds, median_duration_seconds. Single-row response. Pair with gentic_ingest_bounce_rate for the canonical 'we got N sessions, X pages avg, Y seconds avg, Z% bounced' summary line. Aggregate session model — 30-min idle threshold over subject_id, no per-user reconstruction (the model gives 'what fraction of sessions did X', not 'what did visitor 47 do'). | 5¢ / call |
| `gentic_ingest_top_entry_pages` | Returns the URLs where the most sessions BEGAN — top landing pages, ordered by session count, with per-page bounce count and bounce rate. Use for 'where do visitors arrive on the site, and which landing pages cause the most bounces?' First-touch attribution surface: once referrer/UTM capture lands upstream (gentic-ingest#35), this composes with referrer to answer 'which campaign drove this landing experience.' Aggregate session model — counts which pages started sessions over the window, NOT a per-user landing-page history. | 5¢ / call |
| `gentic_ingest_top_exit_pages` | Returns the URLs where the most sessions ENDED — top exit pages, ordered by exit-session count. Use for 'where do visitors leave the site?' — identifying dead-end pages or confusing navigation. NOTE: a single-pageview session has the SAME URL as both entry AND exit, so this query and gentic_ingest_top_entry_pages will share rows for bouncers — use gentic_ingest_bounce_rate to disambiguate the populations. Aggregate session model — counts which pages ended sessions over the window, NOT a per-user exit history. | 5¢ / call |
| `gentic_ingest_top_form_submits` | Returns the most-submitted forms over the given window, grouped by (url, form_id), with submit count and unique-visitor count. Optionally filtered by URL (exact or prefix). Use this for 'which forms are getting traction vs which are dead' insights. Unnamed forms collapse to form_id=''. Privacy: upstream payload contains form_id + form_name + field_count ONLY — NO field values, names, or labels. Login forms and newsletter signups are payload-indistinguishable by design; do NOT attempt to infer form purpose from form_id alone. | 5¢ / call |
| `gentic_ingest_top_outbound_destinations` | Returns the most-clicked outbound destination hosts over the given window, with click count and unique-visitor count per destination. Optionally filtered by URL (exact or prefix). Use this for 'where is the brand's audience going when they leave' insights — useful for finding accidental referral traffic patterns or competing-destination drain. Upstream correctly excludes pseudo-protocols (mailto:, tel:, javascript:) and in-page anchors — returned destinations are true off-host navigations only. | 5¢ / call |
| `gentic_ingest_top_pages` | Returns the most-viewed URLs over the given window, with pageview count per URL. Optionally filtered by URL (exact or prefix). Use this for 'what's resonating' or 'where are users landing' style insights. | 5¢ / call |
| `gentic_ingest_top_rage_clicks` | Returns elements receiving the most rage-click incidents (3+ rapid clicks within 1500ms on the same element) over the given window, grouped by (url, tag, element_id, class_chain), with incident count, total-click count, and unique-visitor count. Includes non-interactive elements — most useful when surfacing 'users are repeatedly clicking what they think is a button but isn't.' Optionally filtered by URL (exact or prefix). For the highest-signal frustration signal, compose with gentic_ingest_click_counts on the same element: high rage:normal-click ratio + non-interactive element = clearest 'broken UX' finding. | 5¢ / call |
| `gentic_ingest_top_sources` | Returns the top referring pages (document.referrer) driving traffic to the site over the given window, with pageview count and unique-visitor count per source URL. Direct loads (no referring page) bucket as '(direct)'. Use this for 'where do visitors come from?' — pair with gentic_ingest_top_entry_pages to see which referrers feed which landing pages. Aggregate-only: COUNT(*) and COUNT(DISTINCT subject_id) per source URL — no subject_id in the response, cannot be joined back to individual sessions (answers 'how many came from each source', not 'where did visitor 47 come from'). The referrer URL itself is the browser-set HTTP Referer header (not PII per common definitions). LEGACY CAVEAT: events captured before 2026-05-25 (when the tracker's referrer capture rolled out) bucket as '(direct)' even if they were actually referred — the upstream COALESCE chain treats missing-property legacy events and empty-string post-launch direct hits the same way. Historical reports spanning the launch boundary will show inflated '(direct)' counts for the pre-launch portion; tail rolls off as new traffic flows. This tool does NOT return UTM data (utm_source / utm_medium / utm_campaign) — UTM-aware breakdown is a future tool once upstream tracker captures those fields. | 5¢ / call |
| `gentic_ingest_web_vitals_summary` | Returns Google Core Web Vitals per URL — LCP and CLS averages plus p75 — over the given window. Pageloads count is the denominator (rows: { url, pageloads, avg_lcp_ms, p75_lcp_ms, avg_cls, p75_cls }). p75 is the Google 'passing' criterion: a page passes CWV if ≥75% of pageloads are under the threshold. Google's 'Good' thresholds: LCP ≤2500ms, CLS ≤0.1. Optionally filtered by URL (exact or prefix). Caveats: (1) v1 of the tracker captures LCP + CLS ONLY — no INP / FCP / TTFB / FID; if asked about those, respond honestly that they're not yet captured rather than presenting null/zero. (2) Legacy events from before gentic-ingest #69 don't carry web_vitals; upstream filters them out, so the aggregate is over post-#69 pageviews only — reference the window start date when interpreting results that span the launch boundary. (3) avg_lcp_ms = 0 means PerformanceObserver never fired (very old browsers / non-page contexts), currently indistinguishable from 'page loaded instantly' — treat suspiciously. | 5¢ / call |
| `northbeam_create_export` | Kick off a Northbeam Data Export job. Returns an export_id immediately; use northbeam_get_export to poll for the CSV download URL once ready (typically 30s-3min). Call northbeam_list_metrics / list_attribution_models / list_breakdowns first to discover valid IDs. For period_type, use one of Northbeam's presets (e.g. 'YESTERDAY', 'LAST_30_DAYS', 'MONTH_TO_DATE') or 'FIXED' with explicit period_starting_at / period_ending_at — see the period_type field description for the full list. If you pass breakdowns, every entry must include a non-empty `values` array. Costs $0.50. | 50¢ / call |
| `northbeam_get_export` | Fetch the result of a Northbeam Data Export submitted via northbeam_create_export. Returns status, the CSV download URL when ready, and a parsed preview of the top rows. If still processing, the tool polls (2s interval) up to wait_seconds before returning the IN_PROGRESS state so the agent doesn't have to re-poll. Free. | Free |
| `northbeam_list_attribution_models` | List the attribution models available in your Northbeam account (e.g. northbeam_custom, last_touch, first_touch, linear). Use this before calling northbeam_create_export to pick a valid model. Free. | Free |
| `northbeam_list_breakdowns` | List the breakdown dimensions available in your Northbeam account (e.g. platform, category, targeting, influencer_by_platform). Use this before calling northbeam_create_export to pick valid breakdown groupings. Free. | Free |
| `northbeam_list_metrics` | List the metrics available in your Northbeam account (e.g. attributed_revenue, cac, roas, spend, transactions). Use this before calling northbeam_create_export to pick valid metric IDs. Free. | Free |
| `northbeam_upsert_spend` | Push ad spend records to Northbeam for non-integrated channels (influencer, podcast, newsletter, affiliate, etc.) — the API-equivalent of Northbeam's spreadsheet spend import. Send 1–1000 daily records per call, each keyed on (date, platform_name, campaign_id, adset_id, ad_id). Re-POSTing the same key overwrites the prior value (upsert). For a single influencer payment spread across N days, generate the N daily records client-side and pass them in one call. Free. | Free |
| `posthog_analyze_button_clicks` | Analyze button click breakdown for sessions that entered through a specific page. Returns click counts, unique users, and share-of-clicks per button text. Useful for identifying which CTAs on a landing page are driving engagement. | 8¢ / call |
| `posthog_analyze_sessions` | Analyze session metrics for a specific entry page, comparing recent vs previous period. Returns volume, engagement, quality, performance, and traffic-source breakdowns. Useful for spotting regressions or lifts after page/campaign changes. | 10¢ / call |
| `posthog_execute_hogql_query` | Execute a raw HogQL query against PostHog. Use for custom analytics queries on events, sessions, persons — e.g. "SELECT properties.$current_url, count() FROM events WHERE event = '$pageview' GROUP BY properties.$current_url LIMIT 50". Returns query results as JSON. | 10¢ / call |
| `posthog_get_insights` | List saved PostHog insights (dashboards, funnels, trends) for the connected project. Supports pagination. Free. | Free |
| `posthog_scroll_depth_analysis` | Analyze scroll depth for sessions entering through a specific pathname. Returns avg/max scroll percent, time on page, and reader distribution (deep/medium/light). Useful for diagnosing which sections of a landing page users actually reach. | 8¢ / call |
| `youtube_get_channel_stats` | Whole-channel rollup for a configurable window (default 28 days, max 365). Returns subscribers (current + delta over window), total views in window, total watch time in minutes, daily-views time series, and a top-N videos list (default 5) by views in the window. ~6 quota units. | Free |
| `youtube_get_comments` | Top-level comments on a single video. Returns author, text (original + display HTML), like count, published/updated timestamps, and reply count. Supports pagination via page_token. Use for 'summarize sentiment of recent comments' or 'find creators-of-interest who commented' workflows. ~1 quota unit. REQUIRES the `youtube.force-ssl` scope (labeled 'Manage your YouTube account' on the consent screen) — Google enforces this for commentThreads.list despite some docs claiming youtube.readonly works. If the connected channel grant is missing this scope, the tool returns missing_scope and the customer needs to reconnect with the broader scope set. | Free |
| `youtube_get_playlist_videos` | Enumerate videos in a YouTube playlist (the channel's own playlists, including 'uploads', or any public playlist). Hydrates each video with title, published_at, view/like counts, and duration. ~3 quota units (1 playlistItems.list + 1 videos.list + 1 buffer). | Free |
| `youtube_get_retention_curve` | Full per-second(ish) audience retention curve for a single video — the deep drill-down behind the at_15s / at_30s / at_50_percent triple in youtube_get_video_analytics. Returns an array of { elapsed_ratio, audience_watch_ratio } sorted ascending, plus elapsed_seconds when video duration is known. Curve is lifetime-of-video (YouTube API constraint — no window param). ~4 quota units. | Free |
| `youtube_get_traffic_sources` | Full traffic-source breakdown for a single video over a configurable window — the deep drill-down behind the top-N preview in youtube_get_video_analytics. Returns one row per source bucket (Browse, Search, Suggested, External, Channel pages, etc.) with views, watch time minutes, and share-of-total. Optional dimension_split='day' returns a per-day-per-source matrix instead. ~3 quota units. | Free |
| `youtube_get_video_analytics` | Composite first-stop analytics for a single YouTube video. Returns a summary view (views, watch time, CTR, average view duration), a retention-summary triple (% retained at 15s / 30s / 50% of duration), and the top-3 traffic source buckets — everything Mother needs to answer 'how is video X doing?' in one call. Full per-second retention array lives in youtube_get_retention_curve; full traffic-source breakdown in youtube_get_traffic_sources (both in PR 3). Costs ~6 YouTube API units; counted against the per-org daily soft cap. | Free |
| `youtube_get_video_transcript` | Fetch the transcript (caption track) of any public YouTube video. Pass the video as `video_url` (a full YouTube link) or `video_id` (a bare 11-char id). Returns both timestamped segments and a concatenated plain_text blob. Tiering: when channel_id is supplied AND owns the video, tries the official Data API first (free, uses YouTube quota); for any other (third-party) video it skips the Data API entirely and goes straight to Supadata (15c) then YouTube's free timedtext endpoint — so third-party transcripts burn ZERO YouTube quota and cost ≤15c. When every tier returns empty, fall back to understand_video_contents on /creative (50c) for Gemini-based audio transcription. Cost varies 0-15c per call; charged_cents is in the response. | Free |
| `youtube_list_channels` | List the YouTube channels this organization has connected. Returns each channel's id, title, granted OAuth scopes, and when it was connected. No Google API call is made — this reads directly from the org's stored integration record. Use this to discover which channel_ids are available before calling tools that need one (youtube_get_channel_stats, youtube_get_video_analytics, etc.). If the org has no YouTube integration registered yet, returns an empty list with a setup hint. | Free |
| `youtube_list_videos` | List videos on a connected YouTube channel, sorted by date (uploads playlist, 1 quota unit) or by views (Data API search, 100 quota units). Returns each video's id, title, published_at, view/like/comment counts, ISO-8601 duration, and thumbnail. Use the returned video_id with youtube_get_video_analytics for a deeper drill-down. Discover available channel_ids first via youtube_list_channels. | Free |
| `youtube_search_videos` | YouTube global search via Data API search.list — finds videos across all of YouTube, not just the org's connected channels. **Costs 100 quota units per call** (the dominant burn vector for the per-org daily soft cap of 1000 units). Use sparingly: youtube_list_videos with sort_by='date' costs 3 units and covers most discovery needs. This tool is for cross-channel queries ('top fitness videos this week') or hard-to-find content. Returns video_id, title, channel, published_at, plus optionally hydrated stats + duration. | Free |

## Workflow

### 1. Start with the entry pathname

If the user mentions a page, ad, or campaign, resolve it to a PostHog entry pathname (e.g. `/pages/qure-microinfusion-offer`). Almost every tool keys off this. If the user is vague, ask.

### 2. Use posthog_analyze_sessions first

For any 'how is this page doing' question, start with `posthog_analyze_sessions`. It gives you recent-vs-previous period volume, engagement, quality, performance, and traffic-source breakdowns in one call. This is almost always the right first move.

### 3. Drill down with scroll depth and button clicks

If sessions look thin or engagement drops, call `posthog_scroll_depth_analysis` to see where readers are dropping off, and `posthog_analyze_button_clicks` to see which CTAs are pulling. Run both in parallel — they're independent.

### 4. Pull saved insights

Use `posthog_get_insights` (free) to surface saved funnels and dashboards the team has already built. Prefer existing insights over ad-hoc HogQL when the answer already lives in one.

### 5. Drop into HogQL for custom questions

When the pre-built tools don't cover it — cross-event joins, custom aggregations, specific user segments — use `posthog_execute_hogql_query`. HogQL is PostHog's SQL dialect; syntax docs at https://posthog.com/docs/hogql. Keep queries bounded (LIMIT clauses) and read-only (this integration is scoped read-only anyway).

## Notes

- All tools are organization-scoped and talk to the PostHog project connected in the Gentic dashboard.
- The stored PostHog key is read-only — write/mutate queries will fail at the PostHog side, not Gentic's side.
- Scroll depth, button clicks, and session analysis all default to a 7-14 day window. Pass `days_back` to widen it up to 180 days.

## Tool details

- `gentic_ingest_add_to_cart_value` — Returns SUM(price × quantity) per product_id over the given window (cart_add_value), plus units_added, cart_adders, currency, and pdp_url (modal /products/<handle> URL viewed by this product's cart-adders — useful for naming the product in agent output without a separate Shopify lookup). Ordered by value (default limit 50). IMPORTANT: this is the intended-cart-value revenue PROXY — it is add-to-cart INTENT, NOT confirmed revenue. Purchases are not captured for tracker-only sites, so this is the closest revenue-shaped signal available; never present it to a user as sales or realized revenue. Use it for 'which products drive the most cart value / intent' insights. Pass url/url_prefix to scope to the cohort of subjects who viewed a matching page (the ecommerce events carry no URL of their own). pdp_url caveats: best-effort, subject-bridged from pageviews matching /products/* (Shopify-shaped). It is null for cart-only items (shipping protection, upsells) and non-Shopify carts. Co-added/bundled items can borrow each other's pdp_url because a subject's pageview set applies to every product they added — read with a grain of salt on bundle-heavy catalogs. Response includes a top-level `pdp_url_enrichment` field: 'ok' = complete; 'truncated' = substrate 10k-row cap hit; 'skipped' = disabled via enrich_pdp_url; 'failed' = bridge errored.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max products returned. Default 50, ranked by cart-add value.
  - `url` (string) — Optional. Scope to subjects who viewed this exact URL, then sum cart value by product_id. Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Scope to subjects who viewed a URL starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `enrich_pdp_url` (boolean) — Whether to populate pdp_url on each row. Default true. Set false for bulk-export workloads — every row gets pdp_url: null and the response carries pdp_url_enrichment: 'skipped'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_avg_engagement_time` — Returns per-URL engagement metrics — average wallclock dwell time (avg_ms), average active-engagement time (avg_active_ms, NULL for pre-#68 legacy events), and average max scroll depth (0–100) — over the given window. Optionally filtered by URL (exact or prefix). Use this for 'are users actually reading this page' insights — wallclock ≥ active by definition; long wallclock + low active = backgrounded tab.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_bounce_rate` — Returns the bounce rate for the site over the given window — total sessions (containing at least one pageview), bounces (sessions with exactly ONE pageview), and the bounce rate percentage. THE most-requested operator metric: 'how many of our visitors leave immediately?' Pair with gentic_ingest_top_entry_pages to see WHICH landing pages drive the bounces. Denominator semantics matter: bounce = exactly-one-pageview session, NOT 'session where the user disengaged' — engagement-only sessions without a pageview aren't in the denominator at all. Aligns with GA4's definition. Aggregate session model (30-min idle threshold over subject_id, no DOM state captured or reconstructed) — answers 'what fraction of sessions bounced', not 'what did this visitor do.'
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_click_counts` — Returns aggregate click counts grouped by element tag + class_chain over the given window. Optionally filtered by URL (exact or prefix). Use this for 'which CTA is getting clicked vs ignored' insights — this surface counts ALL clicks (interactive and non-interactive); for rage-click incidents specifically use gentic_ingest_top_rage_clicks.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_click_heatmap_grid` — Returns a 100×100 viewport-normalized click heatmap grid for the given window — rows of (url, vx_pct, vy_pct, clicks) where coords are integer 0–100. Aggregate-only by design (raw pixel positions never leave the client; 1%-bucketed at source per Principle X) — do NOT promise replay-like 'show me this user's clicks' behavior. Optionally filtered by URL (exact or prefix). Default limit is 10000 — enough for a full single-URL grid without truncation. Edge cells (vx_pct=0/100 or vy_pct=0/100) may carry synthetic concentrations from elastic-scroll / off-viewport clamps; treat edge hotspots as instrumentation noise unless they match a known fixed-position element.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 10000 — full grid for one URL. Raise for multi-URL queries, lower for sparse pages.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_daily_visitors` — Returns unique-visitor count per UTC day for the given window. Optionally filtered by URL (exact or prefix). Use this for 'how many distinct visitors did the brand get this week vs last week' style insights. Returns one row per day.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix (e.g., 'https://example.com/blog/'). LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_describe_events` — Returns the discovered schema for this site's events: per-event-type total_events / first_seen / last_seen aggregates, plus per-property the JSON type, non-null count, and up to 3 distinct sample values. Call this BEFORE composing a complex filter or writing custom SQL via gentic_ingest_execute_query — without schema knowledge, the LLM will guess property names and fail. Optional since/until scope the schema sampling to a time window (e.g., 'what schemas does this site emit in the last 24h' vs 'historically'); defaults to all-time. Free — call early and often when building ad-hoc queries. Aggregate-only per Principle X — no subject_id or per-user data in the response; sample values are up to 3 distinct property values, not per-user identifiers.
  - `since` (string) — Optional. Scope the schema sampling to events after this timestamp. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Defaults to all-time.
  - `until` (string) — Optional. Scope upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Defaults to now.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_execute_query` — Run a constrained SELECT against the events table within the time window. Use when no predefined gentic_ingest_* tool fits the question (e.g., 'top URLs whose engagement_ms grew >50% week-over-week filtered to mobile only'). Returns up to 10,000 rows. site_id is auto-scoped to your authenticated read_key — site_id literals in your SQL filter on top of the scoped CTE, never selecting other sites. Time-window predicates on occurred_at are auto-injected from since/until — your SQL should NOT add them. Costs 25¢ per call (vs 5¢ for predefined gentic_ingest_* tools): prefer purpose-built tools when one fits; fall back here for ad-hoc questions. Call gentic_ingest_describe_events FIRST to enumerate event_types and per-event-type properties — without that, the LLM guesses property names and fails. Errors are passed through verbatim from the substrate ('keyword X is not allowed', 'unknown function Y', 'sql must reference FROM events') so you can self-correct; SQL-validation failures cost $0, execution-time failures bill normally.
  - `sql` (string, required) — SQL SELECT statement (10-8000 chars). Example: SELECT json_extract_string(properties, '$.url') AS url, COUNT(*) AS views FROM events WHERE event_type = 'pageview' GROUP BY url ORDER BY views DESC LIMIT 10
  - `since` (string, required) — Inclusive lower bound for the auto-injected occurred_at window. ISO date (YYYY-MM-DD) or full ISO 8601 datetime.
  - `until` (string, required) — Exclusive upper bound for the auto-injected occurred_at window. ISO date (YYYY-MM-DD) or full ISO 8601 datetime.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_funnel_completion` — Returns unique-subject counts at each step of the on-site ecommerce funnel (pageview → product_viewed → add_to_cart → checkout_started) over the given window. Rows arrive in funnel order, with `unique_subjects` per `event_type`. Use this for 'how far through the funnel do visitors get' insights — derived conversion rates are step-to-step ratios in the row order. IMPORTANT: the funnel terminates at `checkout_started` — `purchase` / `checkout_completed` are NOT measured on this substrate (the first-party tracker structurally cannot capture them: Shopify blocks the script on the checkout/order pages, and customers don't install a server-side pixel). Do not present this as a purchase/revenue funnel. Also note `checkout_started` is a COUNT-only signal — `cart_total` is never populated — so don't derive any cart value or revenue from it.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_list_available_queries` — Returns the catalog of all gentic_ingest_* analytics tools available on this MCP endpoint — each entry has tool_name, a one-line description, category, and the underlying gentic-ingest API endpoint (null for the meta-tool itself). Use this BEFORE composing a multi-tool answer to discover what primitives exist (e.g., before answering a vitals question, call this with category='vitals' to see what's available). Free — no upstream call.
  - `category` (string, enum: `engagement` | `navigation` | `clicks` | `forms` | `vitals` | `commerce` | `sessions` | `composite` | `meta`) — Optional. Filter to one category. Omit to return the full catalog (24 tools today: 14 substrate-query primitives + 5 session-aware + 1 referrer-source + 1 composite (period_report) + 2 ad-hoc escape hatches — describe_events + execute_query — + 2 discovery — list_available_queries + list_sites — give or take the version of the family at time of read).
- `gentic_ingest_list_sites` — Lists the gentic-ingest sites (domains) connected for the current organization, with site_id, created_at, and revoked status. Use this BEFORE calling any other gentic_ingest_* tool on a multi-site org you don't know — pass the resulting `domain` back as the `domain` param on subsequent tool calls. Returns active (non-revoked) sites sorted oldest-first. Free — no upstream call.
- `gentic_ingest_pageviews_by_device_class` — Returns pageview count and unique-visitor count grouped by device_class (mobile, desktop, or 'unknown' for pre-tracker-v1 legacy data) over the given window. Optionally filtered to a specific URL or URL prefix. Use this for 'what's the mobile-vs-desktop split for this brand's site this week' insights. Returns ≤3 rows.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix (e.g., 'https://example.com/blog/'). LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_period_report` — Single-call period analytics report. Fans out 17 underlying ingest queries over a current period and (optionally) a comparison baseline, then merges into one structured payload with per-metric delta_pct. Use this for 'run the daily/weekly/monthly report' or DoD/WoW/MoM comparison questions — replaces ~30 individual gentic_ingest_* tool calls with one, sized to fit in agent context (~20-30KB per site vs ~1-3MB of by-hand fan-out). RENDER CONTRACT — every field in this response is meant to be surfaced in your output: no MCP-side curation, no suppression thresholds, no 'this signal isn't worth showing' decisions. The per-section top-N cap (default 10, max 25) is the only size discipline and is a token-budget concern, not a signal filter. Walk every section in order, mention every row, do not skip. Cadence presets default the window: 'day' = yesterday vs day-before (DoD), 'week' = last 7d vs the 7d before that (WoW, default), 'month' = last 30d vs the 30d before that (MoM); UTC-day boundaries. Explicit since/until/compare_since/compare_until override; cadence becomes 'custom'. include_compare: false → snapshot-only (every delta_pct: null). Output sections (render in this order to match the canonical daily-report format): (1) top-line scalars — visitor_days NOT unique-over-window (see caveat), bounce_rate_pct, sessions{total/avg_pages/avg_duration}, interactive_clicks total from click_counts, device_split, web_vitals{lcp_p75_ms/cls_p75 averaged across URLs}; (2) per-URL deep-dives — web_vitals_by_url with threshold-crossing flags (Good/NI/Poor), heatmap_by_url with focal cell + concentration ratio, engagement_quality with avg_active_ms + scroll-depth shifts; (3) top-N lists — top_pages, top_entry_pages with bounce_rate_pct/bounce_count in meta, top_sources, top_outbound_destinations, top_form_submits with submits_per_unique retry ratio in meta, top_rage_clicks with element selector in meta (URL-keyed; the element is supplementary context); (4) commerce — funnel_completion per-stage with subjects + computed step_conversion_pct, top_products with pdp_url, top_cart_value with pdp_url + currency. Top-N rows can carry `gone: true` (fell out) and `new: true` (new entrants), so a list can reach up to 2×top_n total rows — surface both. Per-metric fail-soft via metric_errors[]: one flaky substrate query nulls that metric, the rest still returns. INCLUDED — 17 of the 19 gentic_ingest_* primitives: daily-visitors, bounce-rate, sessions-summary, pageviews-by-device-class, web-vitals-summary, top-pages, top-entry-pages, top-sources, top-outbound-destinations, top-form-submits, top-rage-clicks, funnel-completion, product-funnel, add-to-cart-value, click-counts (scalar total only), click-heatmap-grid, avg-engagement-time. NOT INCLUDED (use standalone tools): top-exit-pages (redundant with entry pages), sessions-by-day (time-series belongs in a trend tool); a per-element movers list off click_counts was intentionally removed in PR #588 — without a URL anchor and without stable id/data-* attrs on production components, rows collapsed to Tailwind utility-class fingerprints (`button.flex w-full`) that conflated dozens of unrelated elements. For per-element click tracking call gentic_ingest_click_counts directly on a site that exposes id/data-* attrs. CAVEATS: (1) visitor_days = sum of unique_visitors per UTC day — NOT unique-over-window — a returning customer counts once per day; (2) web_vitals.lcp_p75_ms scalar is averaged equally across URLs — not a true weighted p75; the actionable per-URL view is web_vitals_by_url with threshold-crossing flags; (3) pdp_url is best-effort, Shopify-shaped — same caveats as gentic_ingest_product_funnel; (4) heatmap focal_cell is viewport-normalized — '(50, 50)' means center of the visible viewport when the click happened, not page-center; (5) on top_rage_clicks the row key is the URL where rage clicks occurred; meta.element gives a legible selector hint (`tag#id` if id present, else `tag.firstTwoClasses`) and meta.class_chain carries the full chain — substrate rows that share the same meta.element label collapse into one entry, and meta.class_chain carries the chain of ONE representative row, not the full collapsed set. The delta on a collapsed key conflates volume change with composition change across periods — inherent to aggregation. Tailwind sites where prettier-plugin-tailwindcss sorts classes layout-first can still collapse structurally-unrelated elements onto the same first-two utility classes (e.g. every flex button starts with `inline-flex items-center`); for stable per-element tracking on those sites, expose `id` or `data-*` attributes on production components so the id-priority branch resolves a real identifier. The URL key still anchors the row identity, so this hint is supplementary context rather than the merge key (unlike the now-removed top_element_clicks surface).
  - `cadence` (string, enum: `day` | `week` | `month`) — Cadence preset. 'day' = yesterday vs day-before (DoD). 'week' = last 7d vs prior 7d (WoW, the default). 'month' = last 30d vs prior 30d (MoM). Ignored when explicit since/until is provided.
  - `since` (string) — Explicit current-period start (ISO date or datetime). Wins over cadence. Pair with until.
  - `until` (string) — Explicit current-period end, exclusive (ISO date or datetime). Wins over cadence. Pair with since.
  - `compare_since` (string) — Explicit compare-period start. Only honored when explicit since/until is provided AND include_compare is true. Pair with compare_until.
  - `compare_until` (string) — Explicit compare-period end, exclusive. Pair with compare_since.
  - `include_compare` (boolean) — Whether to compute a comparison baseline. Default true (cadence picks the immediately prior window). Set false for snapshot-only — all delta_pct values become null and the compare period is omitted from the response.
  - `top_n` (integer) — Top-N cap PER PERIOD for each top-N list (top_pages, top_products, etc.). Default 10, max 25. A list can hold up to 2×top_n rows total: the top_n current entries plus up to top_n gone-entries appended after them (preserves big drop-off signal). Sized to keep the report under a few hundred KB across many sites.
  - `enrich_pdp_url` (boolean) — Whether to attach pdp_url to top_products / top_cart_value rows. Default true. Set false for bulk runs where product names aren't needed — saves one substrate query (~300ms) and the pdp_url_enrichment field becomes 'skipped'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_product_funnel` — Returns one row per product_id over the given window: product_viewers, cart_adders, units_added, adders_who_started_checkout, price, view_to_cart_pct, cart_to_checkout_pct, and pdp_url (modal /products/<handle> URL viewed by this product's cart-adders — useful for naming the product in agent output without a separate Shopify lookup). Ordered by adders then viewers (default limit 50), so a no-filter call doubles as 'top products by add-to-cart.' Pass url/url_prefix to scope to the cohort of subjects who viewed a matching page, then see how each product they touched converted (per-product-page funnel). Use this for 'which products convert best from view to cart' insights. CAVEATS, surfaced so they can't mislead: (1) the funnel terminates at checkout_started — purchase/checkout_completed are NOT captured on this substrate, so this is NOT a revenue funnel; (2) cart_to_checkout_pct and adders_who_started_checkout are SUBJECT-bridged (subjects who added the product AND later started any checkout) — session-level, not strict per-product attribution, because checkout_started carries no product_id; (3) pdp_url is best-effort: subject-bridged from pageviews matching /products/* (Shopify-shaped). It is null for cart-only items (shipping protection, upsells) and non-Shopify carts — those rows surface product_id alone, same as before; (4) co-added/bundled items can borrow each other's pdp_url because a subject's pageview set applies to every product they added — read pdp_url with a grain of salt on bundle-heavy catalogs. Response includes a top-level `pdp_url_enrichment` field: 'ok' = mapping is complete; 'truncated' = substrate hit its 10k-row cap, partial mapping (more likely on multi-day windows on busy sites); 'skipped' = enrichment was disabled via enrich_pdp_url; 'failed' = the bridge query errored (parent tool still returns funnel rows, pdp_url is null on every row).
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max products returned. Default 50, ranked by cart-adders then viewers.
  - `url` (string) — Optional. Scope to subjects who viewed this exact URL, then break their product activity down by product_id. Wins over url_prefix if both provided. (The ecommerce events carry no URL of their own; this gates the pageviewer cohort.)
  - `url_prefix` (string) — Optional. Scope to subjects who viewed a URL starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `enrich_pdp_url` (boolean) — Whether to populate pdp_url on each row. Default true (matches Mother's interactive use-case). Set false for bulk-export workloads where the substrate's extra wide-scan cost (≈300ms + a second _sql query in the window) isn't worth the human-readability boost — every row gets pdp_url: null and the response carries pdp_url_enrichment: 'skipped'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_sessions_by_day` — Returns daily session counts + bounces + average duration over the window, one row per UTC day. Each session is bucketed by the day of its FIRST event (so a session spanning midnight counts on the start day only). Use for trend tracking — 'are sessions trending up?', 'is bounce rate getting worse?' — and as the per-day delta feed for the analytics-report skill's daily-comparison shape. Aggregate session model — no per-user trajectory, just per-day rollups.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_sessions_summary` — Returns dashboard-headline session stats over the given window — total_sessions, avg_pages_per_session, avg_duration_seconds, median_duration_seconds. Single-row response. Pair with gentic_ingest_bounce_rate for the canonical 'we got N sessions, X pages avg, Y seconds avg, Z% bounced' summary line. Aggregate session model — 30-min idle threshold over subject_id, no per-user reconstruction (the model gives 'what fraction of sessions did X', not 'what did visitor 47 do').
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_entry_pages` — Returns the URLs where the most sessions BEGAN — top landing pages, ordered by session count, with per-page bounce count and bounce rate. Use for 'where do visitors arrive on the site, and which landing pages cause the most bounces?' First-touch attribution surface: once referrer/UTM capture lands upstream (gentic-ingest#35), this composes with referrer to answer 'which campaign drove this landing experience.' Aggregate session model — counts which pages started sessions over the window, NOT a per-user landing-page history.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 25.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_exit_pages` — Returns the URLs where the most sessions ENDED — top exit pages, ordered by exit-session count. Use for 'where do visitors leave the site?' — identifying dead-end pages or confusing navigation. NOTE: a single-pageview session has the SAME URL as both entry AND exit, so this query and gentic_ingest_top_entry_pages will share rows for bouncers — use gentic_ingest_bounce_rate to disambiguate the populations. Aggregate session model — counts which pages ended sessions over the window, NOT a per-user exit history.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 25.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_form_submits` — Returns the most-submitted forms over the given window, grouped by (url, form_id), with submit count and unique-visitor count. Optionally filtered by URL (exact or prefix). Use this for 'which forms are getting traction vs which are dead' insights. Unnamed forms collapse to form_id=''. Privacy: upstream payload contains form_id + form_name + field_count ONLY — NO field values, names, or labels. Login forms and newsletter signups are payload-indistinguishable by design; do NOT attempt to infer form purpose from form_id alone.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_outbound_destinations` — Returns the most-clicked outbound destination hosts over the given window, with click count and unique-visitor count per destination. Optionally filtered by URL (exact or prefix). Use this for 'where is the brand's audience going when they leave' insights — useful for finding accidental referral traffic patterns or competing-destination drain. Upstream correctly excludes pseudo-protocols (mailto:, tel:, javascript:) and in-page anchors — returned destinations are true off-host navigations only.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_pages` — Returns the most-viewed URLs over the given window, with pageview count per URL. Optionally filtered by URL (exact or prefix). Use this for 'what's resonating' or 'where are users landing' style insights.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_rage_clicks` — Returns elements receiving the most rage-click incidents (3+ rapid clicks within 1500ms on the same element) over the given window, grouped by (url, tag, element_id, class_chain), with incident count, total-click count, and unique-visitor count. Includes non-interactive elements — most useful when surfacing 'users are repeatedly clicking what they think is a button but isn't.' Optionally filtered by URL (exact or prefix). For the highest-signal frustration signal, compose with gentic_ingest_click_counts on the same element: high rage:normal-click ratio + non-interactive element = clearest 'broken UX' finding.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_top_sources` — Returns the top referring pages (document.referrer) driving traffic to the site over the given window, with pageview count and unique-visitor count per source URL. Direct loads (no referring page) bucket as '(direct)'. Use this for 'where do visitors come from?' — pair with gentic_ingest_top_entry_pages to see which referrers feed which landing pages. Aggregate-only: COUNT(*) and COUNT(DISTINCT subject_id) per source URL — no subject_id in the response, cannot be joined back to individual sessions (answers 'how many came from each source', not 'where did visitor 47 come from'). The referrer URL itself is the browser-set HTTP Referer header (not PII per common definitions). LEGACY CAVEAT: events captured before 2026-05-25 (when the tracker's referrer capture rolled out) bucket as '(direct)' even if they were actually referred — the upstream COALESCE chain treats missing-property legacy events and empty-string post-launch direct hits the same way. Historical reports spanning the launch boundary will show inflated '(direct)' counts for the pre-launch portion; tail rolls off as new traffic flows. This tool does NOT return UTM data (utm_source / utm_medium / utm_campaign) — UTM-aware breakdown is a future tool once upstream tracker captures those fields.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 20.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `gentic_ingest_web_vitals_summary` — Returns Google Core Web Vitals per URL — LCP and CLS averages plus p75 — over the given window. Pageloads count is the denominator (rows: { url, pageloads, avg_lcp_ms, p75_lcp_ms, avg_cls, p75_cls }). p75 is the Google 'passing' criterion: a page passes CWV if ≥75% of pageloads are under the threshold. Google's 'Good' thresholds: LCP ≤2500ms, CLS ≤0.1. Optionally filtered by URL (exact or prefix). Caveats: (1) v1 of the tracker captures LCP + CLS ONLY — no INP / FCP / TTFB / FID; if asked about those, respond honestly that they're not yet captured rather than presenting null/zero. (2) Legacy events from before gentic-ingest #69 don't carry web_vitals; upstream filters them out, so the aggregate is over post-#69 pageviews only — reference the window start date when interpreting results that span the launch boundary. (3) avg_lcp_ms = 0 means PerformanceObserver never fired (very old browsers / non-page contexts), currently indistinguishable from 'page loaded instantly' — treat suspiciously.
  - `since` (string, required) — Inclusive lower bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-18'.
  - `until` (string, required) — Exclusive upper bound. ISO date (YYYY-MM-DD) or full ISO 8601 datetime. Example: '2026-05-25'.
  - `limit` (integer) — Max rows returned. Default 50.
  - `url` (string) — Optional. Filter to a specific URL (exact match). Wins over url_prefix if both provided.
  - `url_prefix` (string) — Optional. Filter to URLs starting with this prefix. LIKE wildcards are escaped — only literal prefixes match.
  - `domain` (string) — Optional. The domain of the site within this org to query (e.g. 'gosolo.ghost.io'). If the org has only one connected site, omit. If the org has multiple, you MUST pass this — otherwise queries silently return data for the oldest-connected site, which is rarely what's intended. Call gentic_ingest_list_sites to enumerate available domains.
- `northbeam_create_export` — Kick off a Northbeam Data Export job. Returns an export_id immediately; use northbeam_get_export to poll for the CSV download URL once ready (typically 30s-3min). Call northbeam_list_metrics / list_attribution_models / list_breakdowns first to discover valid IDs. For period_type, use one of Northbeam's presets (e.g. 'YESTERDAY', 'LAST_30_DAYS', 'MONTH_TO_DATE') or 'FIXED' with explicit period_starting_at / period_ending_at — see the period_type field description for the full list. If you pass breakdowns, every entry must include a non-empty `values` array. Costs $0.50.
  - `metrics` (array of string, required) — Metric IDs (from northbeam_list_metrics), e.g. ['attributed_revenue', 'spend', 'roas'].
  - `attribution_models` (array of string, required) — Attribution model IDs (from northbeam_list_attribution_models), e.g. ['northbeam_custom']. Pass multiple to compare models in one report.
  - `accounting_modes` (array of string) — Accounting modes: 'cash' (revenue when transaction occurs) or 'accrual' (revenue when touchpoint occurs). Defaults to ['accrual'].
  - `attribution_windows` (array of string) — Attribution windows in days, e.g. ['7'] or ['1','7','30']. Defaults to ['7'].
  - `level` (string, enum: `ad` | `adset` | `campaign` | `account`) — Granularity of the export. Defaults to Northbeam's API default.
  - `time_granularity` (string, enum: `DAILY` | `WEEKLY` | `MONTHLY`) — Row time granularity. Defaults to DAILY.
  - `period_type` (string) — Reporting period. Defaults to 'YESTERDAY'. Use 'FIXED' for a custom date range (then set period_starting_at + period_ending_at). Common preset values: TODAY, YESTERDAY, YESTERDAY_AND_TODAY, TODAY_AND_YESTERDAY, LAST_3_DAYS, LAST_7_DAYS, LAST_14_DAYS, LAST_28_DAYS, LAST_30_DAYS, LAST_60_DAYS, LAST_90_DAYS, LAST_180_DAYS, MONTH_TO_DATE, LAST_MONTH, THIS_MONTH, THIS_MONTH_AND_LAST_MONTH, THIS_WEEK, LAST_WEEK, THIS_WEEK_AND_LAST_WEEK, LAST_3_WEEKS, LAST_4_WEEKS, LAST_7_WEEKS, LAST_12_WEEKS, LAST_14_WEEKS, LAST_26_WEEKS, LAST_28_WEEKS, LAST_52_WEEKS. Northbeam may add more — invalid values come back as a clear 422 from the API.
  - `period_starting_at` (string) — ISO date (YYYY-MM-DD). Required when period_type='FIXED' (custom date range).
  - `period_ending_at` (string) — ISO date (YYYY-MM-DD). Required when period_type='FIXED' (custom date range).
  - `breakdowns` (array of object) — Optional breakdown groupings. Each item is {key, values}. Discover valid keys via northbeam_list_breakdowns. NOTE: every breakdown must include at least one value — Northbeam rejects breakdowns with missing/empty values arrays.
  - `remove_zero_spend` (boolean) — Filter out zero-spend rows. Defaults to true.
  - `aggregate_data` (boolean) — Aggregate across the period instead of returning per-period rows. Defaults to false.
  - `include_ids` (boolean) — Include platform-side IDs in output rows. Defaults to true.
  - `include_kind_and_platform` (boolean) — Include kind/platform columns. Defaults to false.
  - `export_aggregation` (string, enum: `BREAKDOWN` | `TOTAL`) — BREAKDOWN: per-row data. TOTAL: a single aggregate row. Defaults to BREAKDOWN.
- `northbeam_get_export` — Fetch the result of a Northbeam Data Export submitted via northbeam_create_export. Returns status, the CSV download URL when ready, and a parsed preview of the top rows. If still processing, the tool polls (2s interval) up to wait_seconds before returning the IN_PROGRESS state so the agent doesn't have to re-poll. Free.
  - `export_id` (string, required) — The export_id returned by northbeam_create_export.
  - `wait_seconds` (integer) — Max seconds to wait for SUCCESS before returning IN_PROGRESS. Default 60. Set to 0 for a single check.
  - `preview_rows` (integer) — How many rows of the CSV to fetch (via Range header) and parse into JSON. Default 20. Set to 0 to skip the preview fetch entirely (useful for very large exports).
- `northbeam_list_attribution_models` — List the attribution models available in your Northbeam account (e.g. northbeam_custom, last_touch, first_touch, linear). Use this before calling northbeam_create_export to pick a valid model. Free.
- `northbeam_list_breakdowns` — List the breakdown dimensions available in your Northbeam account (e.g. platform, category, targeting, influencer_by_platform). Use this before calling northbeam_create_export to pick valid breakdown groupings. Free.
- `northbeam_list_metrics` — List the metrics available in your Northbeam account (e.g. attributed_revenue, cac, roas, spend, transactions). Use this before calling northbeam_create_export to pick valid metric IDs. Free.
  - `platform` (string) — Optional platform filter (e.g. 'meta', 'google', 'tiktok'). If omitted, returns metrics for all connected platforms.
- `northbeam_upsert_spend` — Push ad spend records to Northbeam for non-integrated channels (influencer, podcast, newsletter, affiliate, etc.) — the API-equivalent of Northbeam's spreadsheet spend import. Send 1–1000 daily records per call, each keyed on (date, platform_name, campaign_id, adset_id, ad_id). Re-POSTing the same key overwrites the prior value (upsert). For a single influencer payment spread across N days, generate the N daily records client-side and pass them in one call. Free.
  - `records` (array of object, required) — Array of spend records, 1–1000 per call. Northbeam's API caps batch size at 1000 (https://docs.northbeam.io/docs/spend-api-best-practices-and-limits).
- `posthog_analyze_button_clicks` — Analyze button click breakdown for sessions that entered through a specific page. Returns click counts, unique users, and share-of-clicks per button text. Useful for identifying which CTAs on a landing page are driving engagement.
  - `entry_pathname` (string, required) — The entry pathname to analyze, e.g. '/pages/qure-microinfusion-offer'
  - `days_back` (number) — Days to look back. Defaults to 7.
- `posthog_analyze_sessions` — Analyze session metrics for a specific entry page, comparing recent vs previous period. Returns volume, engagement, quality, performance, and traffic-source breakdowns. Useful for spotting regressions or lifts after page/campaign changes.
  - `entry_pathname` (string, required) — The entry pathname to analyze, e.g. '/pages/qure-microinfusion-offer'
  - `days_back` (number) — Total days to analyze (2-180). Split in half for comparison (e.g. 14 = last 7 vs previous 7). Defaults to 14.
- `posthog_execute_hogql_query` — Execute a raw HogQL query against PostHog. Use for custom analytics queries on events, sessions, persons — e.g. "SELECT properties.$current_url, count() FROM events WHERE event = '$pageview' GROUP BY properties.$current_url LIMIT 50". Returns query results as JSON.
  - `hogql_query` (string, required) — The HogQL query to execute. HogQL is PostHog's SQL dialect — see https://posthog.com/docs/hogql
- `posthog_get_insights` — List saved PostHog insights (dashboards, funnels, trends) for the connected project. Supports pagination. Free.
  - `limit` (number) — Max insights to return (1-100). Defaults to 100.
  - `offset` (number) — Number of insights to skip for pagination. Defaults to 0.
- `posthog_scroll_depth_analysis` — Analyze scroll depth for sessions entering through a specific pathname. Returns avg/max scroll percent, time on page, and reader distribution (deep/medium/light). Useful for diagnosing which sections of a landing page users actually reach.
  - `entry_pathname` (string, required) — The entry pathname to analyze, e.g. '/pages/qure-microinfusion-offer'
  - `days_back` (number) — Days to look back. Defaults to 7.
- `youtube_get_channel_stats` — Whole-channel rollup for a configurable window (default 28 days, max 365). Returns subscribers (current + delta over window), total views in window, total watch time in minutes, daily-views time series, and a top-N videos list (default 5) by views in the window. ~6 quota units.
  - `channel_id` (string, required) — The channel_id — discover via youtube_list_channels.
  - `window_days` (integer, default: `28`, required) — Lookback window in days (1-365, default 28).
  - `top_n_videos` (integer, default: `5`, required) — Number of top videos to include (1-20, default 5).
- `youtube_get_comments` — Top-level comments on a single video. Returns author, text (original + display HTML), like count, published/updated timestamps, and reply count. Supports pagination via page_token. Use for 'summarize sentiment of recent comments' or 'find creators-of-interest who commented' workflows. ~1 quota unit. REQUIRES the `youtube.force-ssl` scope (labeled 'Manage your YouTube account' on the consent screen) — Google enforces this for commentThreads.list despite some docs claiming youtube.readonly works. If the connected channel grant is missing this scope, the tool returns missing_scope and the customer needs to reconnect with the broader scope set.
  - `channel_id` (string, required) — The channel_id that owns the video.
  - `video_id` (string, required) — The video_id to read comments for.
  - `order` (string, enum: `time` | `relevance`, default: `"relevance"`, required) — 'relevance' (default — YouTube's rank) or 'time' (newest first).
  - `limit` (integer, default: `20`, required) — Number of top-level comments to return (1-100, default 20).
  - `page_token` (string) — Pagination token from a previous response's `next_page_token`.
- `youtube_get_playlist_videos` — Enumerate videos in a YouTube playlist (the channel's own playlists, including 'uploads', or any public playlist). Hydrates each video with title, published_at, view/like counts, and duration. ~3 quota units (1 playlistItems.list + 1 videos.list + 1 buffer).
  - `channel_id` (string, required) — The channel_id for credential resolution. Doesn't need to own the playlist (public playlists are readable across channels).
  - `playlist_id` (string, required) — The playlist_id. For a channel's uploads playlist, use the value from youtube_list_channels-of-uploads or call /channels?part=contentDetails first.
  - `limit` (integer, default: `20`, required) — Number of videos to return (1-50, default 20).
  - `page_token` (string) — Pagination token from a previous response.
- `youtube_get_retention_curve` — Full per-second(ish) audience retention curve for a single video — the deep drill-down behind the at_15s / at_30s / at_50_percent triple in youtube_get_video_analytics. Returns an array of { elapsed_ratio, audience_watch_ratio } sorted ascending, plus elapsed_seconds when video duration is known. Curve is lifetime-of-video (YouTube API constraint — no window param). ~4 quota units.
  - `channel_id` (string, required) — The channel_id that owns the video.
  - `video_id` (string, required) — The video_id to fetch the curve for.
- `youtube_get_traffic_sources` — Full traffic-source breakdown for a single video over a configurable window — the deep drill-down behind the top-N preview in youtube_get_video_analytics. Returns one row per source bucket (Browse, Search, Suggested, External, Channel pages, etc.) with views, watch time minutes, and share-of-total. Optional dimension_split='day' returns a per-day-per-source matrix instead. ~3 quota units.
  - `channel_id` (string, required) — The channel_id that owns the video.
  - `video_id` (string, required) — The video_id to break down.
  - `window_days` (integer, default: `28`, required) — Lookback window in days (1-365, default 28).
  - `dimension_split` (string, enum: `none` | `day`, default: `"none"`, required) — 'none' (default): one row per source. 'day': source × day matrix (heavier payload — use sparingly).
- `youtube_get_video_analytics` — Composite first-stop analytics for a single YouTube video. Returns a summary view (views, watch time, CTR, average view duration), a retention-summary triple (% retained at 15s / 30s / 50% of duration), and the top-3 traffic source buckets — everything Mother needs to answer 'how is video X doing?' in one call. Full per-second retention array lives in youtube_get_retention_curve; full traffic-source breakdown in youtube_get_traffic_sources (both in PR 3). Costs ~6 YouTube API units; counted against the per-org daily soft cap.
  - `channel_id` (string, required) — The channel_id that owns the video. Get it from youtube_list_channels. Must be a channel this org has connected.
  - `video_id` (string, required) — The YouTube video_id (11 chars, e.g. 'dQw4w9WgXcQ'). Get one from youtube_list_videos. Must belong to channel_id.
  - `window_days` (integer, default: `28`, required) — Lookback window for the analytics rollup, in days from today (1-365, default 28). The retention curve is lifetime-of-video, not windowed — that's a YouTube API constraint.
- `youtube_get_video_transcript` — Fetch the transcript (caption track) of any public YouTube video. Pass the video as `video_url` (a full YouTube link) or `video_id` (a bare 11-char id). Returns both timestamped segments and a concatenated plain_text blob. Tiering: when channel_id is supplied AND owns the video, tries the official Data API first (free, uses YouTube quota); for any other (third-party) video it skips the Data API entirely and goes straight to Supadata (15c) then YouTube's free timedtext endpoint — so third-party transcripts burn ZERO YouTube quota and cost ≤15c. When every tier returns empty, fall back to understand_video_contents on /creative (50c) for Gemini-based audio transcription. Cost varies 0-15c per call; charged_cents is in the response.
  - `video_url` (string) — A full YouTube URL (youtube.com/watch?v=…, youtu.be/…, /shorts/…, /embed/…, /live/…). The video id is parsed server-side. Use this for any third-party video. Provide either `video_url` or `video_id`.
  - `video_id` (string) — A bare 11-char YouTube video id (e.g. dQw4w9WgXcQ). Provide either `video_id` or `video_url` — pass a full link as `video_url`, not here.
  - `channel_id` (string) — OPTIONAL. The connected channel_id that OWNS the video (from youtube_list_channels). Supply it ONLY for the org's own videos — it unlocks the free official Data API caption path. Omit it for any third-party video: the tool skips the Data API (zero YouTube quota) and uses Supadata/timedtext instead.
  - `language` (string, default: `"en"`, required) — BCP-47 language code (e.g. 'en', 'es', 'pt-BR'). Default 'en'. If no track matches this language exactly, falls back to the first available track.
  - `prefer_manual` (boolean, default: `true`, required) — When true (default), prefer manually-uploaded caption tracks over auto-generated (ASR). Set false to specifically request ASR (e.g. when manual captions are out of date).
- `youtube_list_channels` — List the YouTube channels this organization has connected. Returns each channel's id, title, granted OAuth scopes, and when it was connected. No Google API call is made — this reads directly from the org's stored integration record. Use this to discover which channel_ids are available before calling tools that need one (youtube_get_channel_stats, youtube_get_video_analytics, etc.). If the org has no YouTube integration registered yet, returns an empty list with a setup hint.
- `youtube_list_videos` — List videos on a connected YouTube channel, sorted by date (uploads playlist, 1 quota unit) or by views (Data API search, 100 quota units). Returns each video's id, title, published_at, view/like/comment counts, ISO-8601 duration, and thumbnail. Use the returned video_id with youtube_get_video_analytics for a deeper drill-down. Discover available channel_ids first via youtube_list_channels.
  - `channel_id` (string, required) — The YouTube channel_id (e.g. 'UCabc123...') — get it from youtube_list_channels. Must be a channel this org has connected and not soft-revoked.
  - `sort_by` (string, enum: `date` | `views`, default: `"date"`, required) — 'date' uses the uploads-playlist path (1 quota unit, default). 'views' uses Data API search (100 quota units — guarded by per-org per-day cap).
  - `limit` (integer, default: `10`, required) — Number of videos to return (1-50, default 10).
- `youtube_search_videos` — YouTube global search via Data API search.list — finds videos across all of YouTube, not just the org's connected channels. **Costs 100 quota units per call** (the dominant burn vector for the per-org daily soft cap of 1000 units). Use sparingly: youtube_list_videos with sort_by='date' costs 3 units and covers most discovery needs. This tool is for cross-channel queries ('top fitness videos this week') or hard-to-find content. Returns video_id, title, channel, published_at, plus optionally hydrated stats + duration.
  - `channel_id` (string, required) — Any connected channel_id — used only for credential resolution. The search itself is global (subject to filters like region/language).
  - `query` (string, required) — Natural-language search query. YouTube's relevance ranker handles synonyms / partial matches; keep it concise.
  - `limit` (integer, default: `10`, required) — Number of results to return (1-50, default 10).
  - `region_code` (string) — ISO 3166-1 alpha-2 region code, e.g. 'US', 'GB'. Restricts to videos viewable in that region.
  - `relevance_language` (string) — BCP-47 language tag, e.g. 'en', 'en-US'. Biases relevance toward this language.
  - `published_after` (string) — RFC 3339 timestamp (e.g. 2026-06-01T00:00:00Z). Restricts to videos published on/after this instant.
  - `hydrate_stats` (boolean, default: `true`, required) — When true (default) also fetch view/like/comment counts + duration for each result (one additional videos.list = 1 unit, total 101).

---

_This SKILL.md is generated from the live Gentic MCP manifest. Tool names, descriptions, and pricing are always current. Connect Gentic Analytics at https://gentic.co/analytics._
