OverviewInsight config reference

Insight config reference

The shape of the config object on POST /builder/insights — an explore spec under config.query (trend, funnel, or attribution for ad spend, CPA and ROAS) or an analysis panel under config.analysis (constraint finder, correlation, scanner summary) — with a working example per insight_type, the type rules, and how to read the result.

Overview

An insight is a saved explore query or an analysis panel plus a visualization. Its config is a JSON object with exactly one of query or analysis:

{
  "name": "Daily visitors",
  "insight_type": "line",
  "config": {
    "query": {
      "kind": "trend",
      "date_range": { "preset": "30d" },
      "interval": "day",
      "series": [{ "event": "$pageview", "math": "dau", "label": "Visitors" }],
      "compare": "previous_period"
    }
  }
}
  • config.query is the same object you would POST /client/v1/data/query. The full grammar — series maths, filters, breakdowns, funnels, limits — lives on the Explore queries page and is not repeated here.
  • insight_type is only the visualization. It never changes what is computed.
  • config.analysis replaces query for a constraint finder, correlation or scanner summary panel. See Analysis panels.
  • Any other key in config is stored verbatim as display settings and never read by the runner, except metric on an attribution number (see Ad spend, CPA and ROAS).

config is validated on write. POST /builder/insights returns 400 { "error": "invalid config", "details": [...] } for a missing or invalid query or analysis, both at once, or a kind that doesn't fit the insight_type. Nothing invalid reaches the table, so a saved insight cannot fail to compile later.

Which spec per type

insight_typequery.kindTypical specRenders
numbertrendinterval, 1–5 series, optional comparea scorecard per series (total + delta vs previous period) over one shared trend chart
linetrendinterval, 1–5 series, optional breakdownone line per series × breakdown value
bartrendno interval, breakdown or several seriesone bar per breakdown value or series
pietrendno interval, breakdownshare per breakdown value
tabletrendwith or without intervalseries × buckets, or one row per series
funnelfunnel2–10 steps, optional window, grain, breakdownstep bars with conversion rates
table, bar, numberattributiongoal or deals, level, modelspend, clicks, conversions, CPA and ROAS per campaign, ad set or ad

The hard rules: funnel ⇔ kind: "funnel", and attribution only draws as table, bar or number. Every other type carries a kind: "trend" spec. paths cannot be saved. A trend without interval returns one value per series; with interval it returns data[] per bucket and a total. bar and pie read the totals either way, so leaving interval off is cheaper for those. number renders either way too, but without an interval it has no buckets to draw and shows the scorecards alone.

Examples

number — conversions this week vs last

{
  "insight_type": "number",
  "config": {
    "query": {
      "kind": "trend",
      "date_range": { "preset": "7d" },
      "series": [{ "event": "purchase", "math": "total", "label": "Purchases" }],
      "compare": "previous_period"
    }
  }
}

line — visitors by device

{
  "insight_type": "line",
  "config": {
    "query": {
      "kind": "trend",
      "date_range": { "preset": "30d" },
      "interval": "day",
      "series": [{ "event": "$pageview", "math": "dau" }],
      "breakdown": { "property": "$device_type", "limit": 3 }
    }
  }
}

pie — traffic sources

{
  "insight_type": "pie",
  "config": {
    "query": {
      "kind": "trend",
      "date_range": { "preset": "30d" },
      "series": [{ "event": "$pageview", "math": "unique_sessions" }],
      "breakdown": { "property": "$referring_domain", "limit": 8 }
    }
  }
}

table — top pages

{
  "insight_type": "table",
  "config": {
    "query": {
      "kind": "trend",
      "date_range": { "preset": "7d" },
      "series": [
        { "event": "$pageview", "math": "total", "label": "Views" },
        { "event": "$pageview", "math": "dau", "label": "Visitors" }
      ],
      "breakdown": { "property": "$pathname", "limit": 20 }
    }
  }
}

funnel — checkout

{
  "insight_type": "funnel",
  "config": {
    "query": {
      "kind": "funnel",
      "date_range": { "preset": "30d" },
      "steps": [
        { "event": "$pageview", "filters": [{ "property": "$pathname", "op": "eq", "value": "/pricing" }], "label": "Pricing" },
        { "event": "checkout_started" },
        { "event": "purchase" }
      ],
      "window": { "value": 3, "unit": "day" }
    }
  }
}

Set "grain": "person" on a funnel whose steps cross from the browser into server-side or CRM events — those arrive under a contact id, not the visitor's distinct_id, so the default device grain cannot link them. See Explore queries for the cost.

Ad spend, CPA and ROAS

An attribution spec credits each conversion to the Meta or Google campaign, ad set or ad that brought the visitor, and joins the platform's daily spend. The result has totals (all conversions), filtered and one row per campaign/ad set/ad (and per non-ad channel) with spend, impressions, clicks, conversions, revenue, cpa and roas. cpa is null when no spend was recorded, never 0. See Explore queries for the grammar.

{
  "name": "Cost per lead by ad",
  "insight_type": "table",
  "config": {
    "query": {
      "kind": "attribution",
      "date_range": { "preset": "30d" },
      "goal": { "event": "lead_submitted" },
      "level": "ad"
    }
  }
}

A number shows one total, chosen by config.metric: conversions (default), revenue, spend, roas or cpa. A bar draws the same metric per row.

{ "insight_type": "number", "config": { "metric": "cpa", "query": { "kind": "attribution", "date_range": { "preset": "30d" }, "goal": { "event": "lead_submitted" } } } }

Analysis panels

config.analysis is { "kind", "params" }. The panel runs an existing analysis route as the viewer: their key is sent to the route, so the route's own scope, plan and client checks apply, and the result is cached for 5 minutes per viewer key. Analysis panels therefore do not run on public or embedded dashboards (POST /public/query answers 403).

kindRoute it runsinsight_typeparams
constraintsGET /client/v1/constraintstable (rows and their constraint), number (recoverable conversions at the primary constraint), bar (recoverable conversions per step)conversion_event, lead_event, group_by (campaign, adset, ad), days or from/to, timezone, attribution, min_impressions, campaign, account_id, attribution_window_days, resolve_identities, revenue_path, benchmark_ad/tracking/landing/sales/site (0-1)
correlationGET /client/v1/correlationline (each metric per bucket), table (pairwise Pearson and Spearman)metrics (array of 2-5 ids, required), grain, lookback_days or from/to, rolling_window, max_lag, new_lookback_days, revenue_path
scanner_summaryGET /client/v1/replay-vision/scanners/{id}/summarynumber (headline), pie/bar/table (answer counts), line (answers per day)scanner_id (required), days or date_from/date_to

Parameters are typed (numbers as numbers) and allowlisted: the constraint finder's table and include are not available to panels. POST /insights/{id}/query and run_insight return { "engine": "analysis", "kind", "result" } where result is the route's response body (named result so it is not mistaken for the response envelope's data).

{
  "name": "Where the paid funnel leaks, per ad",
  "insight_type": "table",
  "config": { "analysis": { "kind": "constraints", "params": { "conversion_event": "lead_submitted", "group_by": "ad", "days": 30, "timezone": "America/Los_Angeles" } } }
}
{
  "name": "Sessions with a broken experience",
  "insight_type": "number",
  "config": { "analysis": { "kind": "scanner_summary", "params": { "scanner_id": "921f8759-aafa-4595-ad62-b70d00354b68" } } }
}

Discovering events and properties

Don't hardcode names. The Data API answers, with the same key:

  • GET /client/v1/data/events/summary — event names with counts for a window.
  • GET /client/v1/data/properties?event=<name> — the property catalog (typed keys and how often they occur).
  • GET /client/v1/data/query/values?property=<key>&event=<name>&search= — top values of one property, for filter and breakdown pickers.

Validating and previewing

Validate on every edit

POST /insights/validate runs the write-path rules with no query: { "ok": true } or 400 { "ok": false, "errors": [...] }.

Preview a draft

POST /insights/preview runs { insight_type, config, dateRange?, granularity? } in memory and returns the same payload as the saved runner. Nothing is persisted; identical drafts are served from a 120-second cache.

Save

POST /insights stores it. Change the config later with the same call plus id; change name, description, tags or favorite with PATCH /insights/{id}.

Per-run overrides

POST /insights/{id}/query, POST /public/query and /insights/preview accept two optional overrides that are applied onto the stored spec for that run only:

  • dateRange — { "preset": "7d" } (24h, 7d, 14d, 30d, 90d, 365d) or { "preset": "custom", "from": "2026-08-01", "to": "2026-08-31" }. Anything else keeps the stored range.
  • granularity — hour, day, week, month; replaces query.interval. Trends only.

On an analysis panel, a preset becomes the route's relative window (days, or lookback_days for correlation, which ignores 24h) and a custom range its dates (from/to, or date_from/date_to for a scanner summary).

Dashboards use these to apply one date range and interval to every tile.

Reading the results

The run payload is the explore response with the insight attached — see the Queries group for the full schema. In short:

FieldTrendFunnel
kind"trend""funnel"
buckets / previous_bucketsISO labels, present with interval—
series[]{ label, math, event, breakdown_value?, data[] | value, previous[] | previous_value, total }—
breakdown_values[]values in rank order, $$other last—
steps[]—{ step, label, event, users, conversion_from_first, conversion_from_previous }
breakdown[]—{ value, steps[] } per breakdown value
metaresolved window, interval, breakdown settings, sql_ms, rows_readsame, plus grain
config, cached, sqlstored config, cache hit flag, compiled query (sql is omitted on /public/query)same

For a number, read each entry's total (with interval) or value for the scorecard, the previous* counterpart for the delta, and buckets against data for the chart beneath them. For a line, plot buckets against each entry's data. For bar/pie, one slice per series[] entry using total/value.

Dashboards: filters and theme

POST /builder/dashboards takes filters and theme objects, both {} by default.

filtersobject

Default date range and granularity applied to every tile on load. Shape: { "dateRange": { "preset": "7d" }, "granularity": "day" } (the per-run overrides above). Embed viewers can override via ?date_range=/?granularity= query params.

themeobject

Reserved; currently unused — leave it {}. Embed theming is driven by query params on the embed URL (theme, theme_primary, theme_secondary, theme_heading_font, theme_body_font, transparent, hideHeader, branding), not by this stored object.