Reading flow analytics
Every published flow has an Analytics tab that answers two questions: how many people move through the flow (the conversion funnel), and which variant of each A/B experiment is winning. The tab reads two endpoints —GET /flows/:id/analytics for the funnel and GET /flows/:id/ab-results for experiments — against the flow’s recorded execution history.
Where to find it
The Analytics tab lives inside the Flows page, not on a standalone route:- Open Flows in the dashboard and click the Analytics tab.
- Pick a flow from the picker. The selected flow lives in the URL (
/flows?tab=analytics&flow=<flow-id>), so you can bookmark or share a link that lands on a specific flow’s numbers.
/flows/analytics visit redirects to /flows?tab=analytics — the directory exists only to host the tab’s component, not as a page in its own right.
Reading conversion analytics
GET /flows/:id/analytics returns the flow-level rollup plus the per-node funnel:
- entered — executions that started within the entry window (the trailing window by default, or between
sinceanduntil). - completed — of those, how many ran to a successful finish.
- converted — how many contacts then hit the conversion goal (an event or a trait update) within the attribution window.
- completion_rate —
completed / entered, ornullwhen nobody entered. - conversion_rate —
converted / entered, ornullwhen entered is zero. - funnel — one step per node, ordered by traversal (see below).
An override wins over the stored goal — use it to compare funnels against different definitions of “converted.”
Per-node drop-off
The funnel lists one step per node, ordered breadth-first from the trigger — the same order contacts traverse the graph. Nodes unreachable from a trigger still appear, appended in definition order. Each step carries:reached— executions that touched this node.dropped_off— how many of the previous step’s traffic did not reach this step (0 on the entry node).step_conversion_rate— share of the previous step’s traffic that reached this one.overall_conversion_rate— share of total entries that reached this node.
A/B experiment results
GET /flows/:id/ab-results rolls up every abTest node in the flow, accepting the same override params as /analytics:
experiments— one entry per experiment node: itsvariantspluswinner_variant_id.- One row per variant —
variant_id,variant_label,assigned(executions routed into it),completed,converted, and both rates. winner_variant_id— the leading variant by goal-conversion rate (or by completion rate when no goal is configured). It isnullon a tie or when no variant has assignments yet — the UI never declares a false winner.
Define a conversion goal
The goal lives on the flow definition underconversionGoal — either an event name or a contact trait (optionally pinned to a value), with an optional attribution windowDays (1–365). Save it on the definition like any other node config:
{ "type": "trait", "trait": "plan", "value": "paid" } — only contacts whose plan was set to paid within the window count as converted. Without a goal, converted stays zero and experiments rank variants by completion rate instead.
curl examples
Fetch the funnel for a flow, overriding the goal to an ad-hoc event:Common gotchas
- Empty
experimentsarray — the flow has noabTestnodes, or none produced a run in the window. Add an experiment node and republish to populate it. winner_variant_idisnull— a dead tie, or no variant has any assignments yet. Wait for more traffic; the tab does not crown a winner on zero data.- All rates are
null— nothing entered in the window, so division has no denominator. Widensince/untilor check the trigger still fires. - The goal never fires — without a stored goal or a query override,
convertedstays zero and everyconversion_rateisnull. SaveconversionGoalon the definition, or passgoal_event/goal_traitper request. - Override surprises — a query-supplied goal wins over the stored one, so two tabs looking at “conversion” can disagree. The response echoes the resolved
goalfield; always check it. - Rates are fractions, not percents — multiply by 100 (and guard the
nullcase of an empty denominator) before rendering.
Both endpoints are tenant-scoped reads: they aggregate only your own flow’s recorded executions. Rates and counts can never leak across tenants.