Claude Code for data analysis works when the data sits behind commands it can run. Bind a folder to your brand with the ElasticFunnels CLI and it reads sales by funnel, every step of a funnel, per-buyer upsell takes, affiliates and single visits, then joins them into tables and checks one number against another.
It is fast and it is often right. It can also be confidently wrong, so make it prove every claim from orders before you act on it. Below is a real analysis, replayed prompt by prompt, including the three times it was wrong.
Most funnel reports get read the same way. Someone opens the dashboard, sees a conversion rate that looks bad, and changes a page. Half the time the page was fine and the number was measuring something else.
I spent nine years as a CTO building funnel systems, and the analysis part never got faster: export, join, pivot, argue about which number is right. This post is about handing that part to Claude Code. The walkthrough is one real session on a live supplement brand. The brand here is our demo brand, Northwind Supplements, and every name, id and number is changed, but the questions, the commands, the ratios and the mistakes are the ones that happened.
What Claude Code can do with your funnel data
Claude Code is Anthropic's AI agent for the terminal: it reads files, runs commands and writes code. On its own it knows nothing about your funnel. With the ef CLI in the folder, it can run the same analytics the dashboard reads, scoped to one brand by that folder's key.

ef commands, builds a table and tells you what it means. Then you ask the next question, or challenge the answer.What it can read and work out, with the command behind each:
| Question | What it runs |
|---|---|
| Which funnel, page, product, affiliate, country, device or UTM makes the money | ef stats by <field> |
| Headline numbers for any scope: revenue, AOV split into main and upsell, refunds, profit | ef stats --funnel / --aff / --page |
| How buyers move through a funnel, step by step | ef funnels product-flow + ef stats by page --funnel + ef products list |
| What each package's buyers bought next (per buyer, not per session) | ef orders buyers --funnel |
| What one visitor actually did, page by page | ef sessions show |
| Whether a split test has a result | ef stats split (the app's significance check, never its own) |
With those it can build a funnel's step table, measure take rate per buyer, profile an affiliate, reconcile two numbers that disagree, spot traffic that isn't people, and write a brief a developer can act on. When you decide something, it can act on it: end a split test on the variant you chose (ef splits winner), or change a page-event rule. Reading is read-only by design. ef stats has no write path at all.
The rules it follows
ef init installs four analysis skills into the project: ef-stats, ef-funnel-analysis, ef-upsell-diagnosis and ef-funnel-performance (plus ef-page-events for changes). They are the guardrails, and every one of them exists because an analysis went wrong without it:
- Every number comes with its range and timezone. The CLI counts days in your computer's timezone unless the project says otherwise, and your computer is not your market.
- Unavailable is not zero. A metric the brand doesn't track is reported as "not tracked", never as 0.
- The comparison is the previous period of the same length, not last year. A rise in a cost metric is bad news.
--limittruncates, and says so. "Most traffic is X" needs the row count first. The blank row is direct or untagged traffic, not a mystery source.- Checkout pages are not funnel steps. The checkout is chosen when the buy link is clicked, from the active merchant for that domain (which you can override), so a checkout missing from the funnel graph is normal.
- Buyers are not sessions. Pages after checkout undercount sessions badly, so upsell and routing claims are checked against orders.
- It never calls a split test early, and never runs its own significance test. It reads the app's result and says when the call is yours.
What it can't do, or shouldn't
- See other brands. The folder's key opens one brand. There is no account-wide roll-up.
- Know why you ran a test. The server stores numbers, not intent. Write the hypothesis in
split-tests.mdand it reads it. - Be right every time. It reasons well and sometimes reasons past the data. You'll see it happen three times below. Challenge anything that would change money.
Setting up Claude Code for data analysis
You need an ElasticFunnels brand (the CLI and every report it reads are on every plan), Node.js 18 or newer, and Claude Code signed in to your Claude account. Then, once:
$ npm i -g @elasticfunnels/cli $ mkdir northwind && cd northwind $ ef init # browser sign-in, pulls the brand, installs the skills $ ef config set analyticsTz America/New_York # the timezone your brand reports in $ claude
ef init writes a CLAUDE.md with the commands and rules, installs the skills into .claude/skills, and adds a session hook that keeps the folder current. In a folder you bound earlier, ef claude refreshes the guidance and skills. If ef orders --help errors, your CLI is older than these commands: run ef update --check.
Prefer to follow along one step at a time, with a clip per step? The Academy tutorial analyse your funnel data with Claude Code covers the same setup and the first three questions.
A real funnel analysis with Claude Code, replayed
Northwind sells one product, Vitality, a daily supplement, in 2, 3 and 6-bottle packages, mostly through affiliates. Four of them matter here: Maya, who runs Meta ads and brings the most revenue; Dev, who sends cold mobile Google Ads traffic to a VSL (video sales letter); the house email list; and Carlos and Lucía, who bid on search. Each step below is what the owner typed, what Claude Code ran, a trimmed copy of the output and what it concluded.
1 · The overview"Show me sales by funnel"
$ ef stats by funnel_id --metrics sales,revenue,main_sales,upsell_sales,sessions,conversion_rate,aov --range 30d by funnel_id last 30 days (2026-08-30 → 2026-09-28, Europe/London) FUNNEL_ID SALES REVENUE MAIN_SALES UPSELL_SALES SESSIONS CONVERSION_RATE AOV ----------------- ----- ---------- ---------- ------------ -------- --------------- ------- Main VSL funnel 133 $21,854.35 97 16 3,950 2.46% $225.30 Maya partner 123 $27,880.00 90 11 226 40.27% $271.40 (unlabeled) 59 $14,580.20 38 18 43 125.58% $231.43 Mobile VSL funnel 23 $4,430.10 14 3 487 3.08% $247.90 Quiz test 0 $0.00 0 0 21 0.00% - Advertorial v2 0 $0.00 0 0 14 0.00% - Webinar draft 0 $0.00 0 0 9 0.00% - Spanish VSL 0 $0.00 0 0 6 0.00% - Holiday offer 0 $0.00 0 0 2 0.00% -
338 sales and about $68.7k in the last 30 days, counted in Europe/London time.
- Maya's funnel brings in the most, but a 40% conversion rate isn't real. Most of her buyers probably reach checkout without a tracked session on these pages. Compare it on sales and revenue, not on rate.
- The unlabeled row is $14.6k of orders with no funnel. More sales than sessions is why it reads 125%. That's revenue no funnel gets credit for.
- Main VSL funnel gets almost all the traffic and converts at 2.46%. The mobile VSL funnel converts a little better on an eighth of the traffic.
- The five funnels with no sales had 52 sessions between them. They're unlaunched, not failing.
This project has no analytics timezone, so days are counted in this machine's zone. If the brand reports in US time, ef config set analyticsTz America/New_York and the day boundaries move.
The owner set the timezone. Every output from here on is New York time. Notice what the agent didn't do: it didn't rank Maya's funnel as the best converter. A number that high is a tracking question first, and it filed it as one.
2 · The step table"The upsell CVR is too low"
$ ef funnels product-flow 412 > flow.json $ ef stats by page --funnel 412 --metrics sessions,sales,main_sales,upsell_sales,revenue --range 30d --limit 500 --json > pages.json $ ef products list --json > products.json $ jq … flow.json | column -t # the step-table join from the ef-funnel-analysis skill CLASS PAGE NAME SESS SALES S/S NODE AFTER OFFERS main 2311 VSL 2104 45 2.1% q3m8vd - VIT_2B=$138 VIT_3B=$159 VIT_6B=$264 main 2318 Article 980 27 2.8% w5t1ka - VIT_2B=$138 VIT_3B=$159 VIT_6B=$264 main 2320 Packages 420 13 3.1% q3m8vd - VIT_2B=$138 VIT_3B=$159 VIT_6B=$264 upsell 2402 Upsell 1 116 6 5.2% u1r4nx q3m8vd VIT_6B_UP=$204 upsell 2403 Downsell 58 7 12.1% d8k2wb q3m8vd VIT_3B_DS=$102 upsell 2405 Upsell 1 (alt) 3 0 0% u1a7pc w5t1ka VIT_6B_UP2=$150 upsell 2410 Upsell 2 54 2 3.7% o2v6hs u1r4nx OMG_1B=$69 OMG_3B=$99 upsell 2415 Upsell 3 49 1 2% g3n5yd o2v6hs GLW_4B=$204 $ ef stats --funnel 412 --metrics upsell_take_pct,main_revenue,upsell_revenue --range 30d last 30 days (2026-08-30 → 2026-09-28, America/New_York) METRIC VALUE CHANGE VS --------------- ---------- ------- ------------------ upsell_take_pct 16.49% -6.40% vs Jul 31 - Aug 29 main_revenue $19,310.00 +4.11% vs Jul 31 - Aug 29 upsell_revenue $2,340.00 -9.85% vs Jul 31 - Aug 29
About 1 in 6 buyers (16.5%) buys anything after the first order. In the order a buyer sees it:
| Step | Offer | Sessions | Sales | Per session |
|---|---|---|---|---|
| Front end | Vitality 2 / 3 / 6 bottles, $138 / $159 / $264 | 3,504 | 85 | 2.4% |
| Upsell 1 | 6 more bottles, $204 | 116 | 6 | 5.2% |
| Downsell | 3 bottles, $102 | 58 | 7 | 12.1% |
| Upsell 2 | Omega Plus, $69–$99 | 54 | 2 | 3.7% |
| Upsell 3 | Glow Collagen 4 bottles, $204 | 49 | 1 | 2.0% |
- Price is the objection, not the product. The downsell is the same product at half the price and converts about 2.3 times better per session. People want more Vitality, just cheaper.
- Upsell 1 offers 6 more bottles to everyone, including buyers who just paid $264 for 6. I'd send 6-bottle buyers to a different product first.
- Article buyers barely see an upsell. The Article page made 27 sales, but the upsell page that follows it had 3 sessions.
- Upsells 2 and 3 are nearly dead: 3 sales from 103 sessions. Glow Collagen costs as much as upsell 1, for a product nobody came for.
Sessions after checkout overcount reloads, so the per-session rates understate take per buyer. The ranking between steps still holds.
Two of those four bullets are wrong. Hold on to them; the agent finds out in step 7. The other two held up: the half-price downsell beating the upsell is the most useful single fact in the whole session.

The funnel also had a split test on upsell 1. The agent read it the way the skill says to:
$ ef stats split 3107 --range 30d Upsell 1 story test (#3107, 2026-08-30 → 2026-09-28, America/New_York) VARIANT SESSIONS CONVERSION_RATE SALES ----------------------- -------- --------------- ----- Control (control) 81 7.41% 6 Story version 70 12.86% 9 p-value 0.2915 power 18.7% No winner yet — each arm needs ~520 sessions before a call can be made.
The story version converts at almost twice the control's rate, but the app hasn't marked a winner (p-value 0.29). Each arm needs about 520 sessions and gets about 70 a month, so that's roughly 7 months away. Waiting won't produce an answer at this traffic. Whether to call it anyway is your decision, not the statistics'. There's no entry for this test in split-tests.md, so I don't know what the story version was meant to prove.
"Set the story version as the winner"
The owner made the business call: the story version wins. This is the first time in the session the agent changes anything, and only because it was told to.
$ ef splits variants 3107 Upsell 1 story test (#3107, active) VARIANT NODE CODE ------------- --------- Control k2x8qd Story version r7m4tw $ ef splits winner 3107 "Story version" --yes Upsell 1 story test (#3107) → winner Story version r7m4tw ! The server has not named a winner (p-value 0.4803, ~520 sessions per arm needed). This is a manual call. ✓ #3107 finalized — all traffic now goes to "Story version". The graph changed on the server — run "ef pages events pull upsell-1" before editing it. Record the outcome in elasticfunnels/split-tests.md.
Two details worth copying. The CLI prints the app's significance result before it acts, so an early call is a deliberate one, and it refuses to run off a terminal without --yes. The p-value it quotes (0.48) covers the whole test, not the last 30 days, which is why it differs from the 0.29 above. Then the agent pulled the page's graph to confirm the winner had reached what the runtime serves, and wrote the decision into the project:
## #3107 — Upsell 1 story test (page upsell-1, #2402) - 2026-09-28: Story version (r7m4tw) set as winner. Manual call by the owner. - Last 30 days (America/New_York): Control 81 sessions, 7.41%, 6 sales · Story version 70 sessions, 12.86%, 9 sales. App: no winner, p 0.29 (0.48 over the whole test), ~520 sessions per arm needed.4 · Keeping the method
"Make this a skill so it's easier next time"
A Claude Code skill is a Markdown file of instructions the agent loads when a task matches it. The owner asked for the analysis above to become one, so the next session would start from the method instead of rediscovering it. That session is where the CLI's analysis skills came from: ef-funnel-analysis (the step table, including the jq join above), ef-upsell-diagnosis (the questions in the order that costs most) and later ef-funnel-performance (the whole playbook). ef init and ef claude install them, so your agent starts with them too.
This is the step I'd tell anyone to steal. Every correction later in this post went back into a skill the same day, so the agent doesn't make the same mistake twice.
5 · Two numbers that disagree"CVR is crazy low. Can we tell why?"
The owner pasted a screenshot of a dashboard widget for the mobile VSL funnel: 33,000 visitors, 23 sales, 0.07% conversion. Step 1 said 3.08%.
$ ef stats --funnel 587 --metrics sessions,sales,revenue,conversion_rate --range 30d last 30 days (2026-08-30 → 2026-09-28, America/New_York) METRIC VALUE CHANGE VS --------------- --------- ------- ------------------ sessions 487 -21.30% vs Jul 31 - Aug 29 sales 23 -41.03% vs Jul 31 - Aug 29 revenue $4,430.10 -38.62% vs Jul 31 - Aug 29 conversion_rate 3.08% -18.52% vs Jul 31 - Aug 29 $ ef sessions list --funnel 587 --range 7d --limit 12 last 7 days (2026-09-22 → 2026-09-28, America/New_York) TIME SESSION EVENTS DEVICE COUNTRY HOSTING BOT USER AGENT PATH ------------------- -------- ------ ------- ------- ------- --- ----------------------------------- ------ 2026-09-28 09:19:22 Qm4…f2Lk 0 - US yes no Mozilla/5.0 (compatible; UptimeCra… /watch 2026-09-28 09:19:21 Rt8…a0Wc 0 - US yes no Mozilla/5.0 (compatible; UptimeCra… /watch 2026-09-28 09:19:20 Lp2…x7Hd 0 - US yes no Mozilla/5.0 (compatible; UptimeCra… /watch … 2026-09-28 09:04:14 Vb6…k1Qe 0 - US yes no Mozilla/5.0 (compatible; UptimeCra… /watch 2026-09-28 08:57:40 Hs3…p9Zn 14 mobile US no no Mozilla/5.0 (iPhone; CPU iPhone OS… /watch 12 of 7,683 page load(s). "ef sessions show <session>" for one visit's timeline.
The funnel's conversion rate isn't 0.07%. The widget divided by a different number. On sessions that loaded a page, it converts at 3.08%, which is better than the main VSL funnel's 2.46%.
| Widget | ef stats | |
|---|---|---|
| Traffic | 33,000 "visitors" | 487 sessions |
| Sales | 23 | 23 |
| Conversion | 0.07% | 3.08% |
The two count different things. That widget's "visitors" counted click records: every hit on a tracking link, whether or not a page ever loaded. ef stats sessions count visits with a page view. The page-load list shows what fills the gap: rows with zero events, no device, and the same crawler user agent, in bursts one second apart. In a sample of 200 recent rows, 191 were that crawler. They never load the page, so they never become sessions. Nobody is being lost here.

The lesson isn't about that widget. It's that two numbers with the same label can count different things, and an agent is very good at reconciling them if you ask. "Why do these two disagree?" is one of the most valuable prompts you can type.
6 · Is this traffic real?Reading the junk
The agent traced one of the crawler hits:
$ ef sessions show Qm4Tn8Wc2xLa9Rv5Hd1Kp7Ze3Yb6f2Lk --full-urls Session Qm4Tn8Wc2xLa9Rv5Hd1Kp7Ze3Yb6f2Lk Started 2026-09-28 09:19:22 (America/New_York) Landing https://northwind-example.com/watch?aff_id=2210&subid={gclid}&subid2={acct} Referrer - Affiliate 2210 Funnel 587 Device - Country United States User agent Mozilla/5.0 (compatible; UptimeCrawler/2.4) Flags bot: no hosting: YES vpn: no proxy: no tor: no Page loads 1 Path - Timeline TIME EVENT PAGE URL NOTE ---- ----- ---- --- ---- 0 event(s).
Affiliate 2210 is Dev. Something opens his links about 1,056 times a day, and the pattern says what:
- Perfectly regular. The same count every day for 30 days, no weekday or time-of-day shape. Real traffic never does that.
- Bursts. 11 hits one second apart, then 15 minutes of nothing. 11 link variants checked 96 times a day is 1,056.
- Unfilled macros.
subid={gclid}is a tracking-link template pasted somewhere as-is, never filled in by an ad click. - No page load, ever.
That's almost certainly an uptime monitor checking that Dev's links still work. It's harmless: you pay him per sale. But most of it comes from data-center IPs, which the clicks metric already drops, and the ~1,050 hits that don't still count as his clicks. His EPC shows $1.60 on 1,290 clicks. Per real session he earns about $7.40. Rank affiliates on revenue per session, not per click, until this is cleaned up.
The owner's guess ("I think it's his internal tool that does uptime checks") fit the data better than the agent's first one ("a crawler"). That's the right division of labour: you know your partners, the agent knows the rows.
The same step produced a mistake of its own. An affiliate with the id 40117 had sent 505 sessions to the main VSL funnel and 0 sales, surrounded by about 1,860 click rows that never loaded a page, all with browser versions two years old. The agent called it a bot farm and suggested banning it. A closer look split the two groups apart:
- The never-loaded rows had a webmail referrer: email link scanners, which open every link in an email before the reader does. They were never in the session counts.
- The 505 sessions with a page view had current browsers, 83% scrolled or played the video, and the page view arrived about a second after the click, like everyone else's.
So 40117 is a real email list that clicks and doesn't buy. That's an affiliate-quality problem, not a filter problem, and hiding those sessions would have hidden the problem. The fair way to show it: without 40117's traffic, the VSL page converts at 2.8%, not 2.1%. It also works the other way round. Two buyers the agent traced were flagged as data-center traffic (hosting: YES) and were real people on iCloud Private Relay, so a hosting flag alone never makes a visit a bot.
"Are you sure?"
Later, summarising what to change, the agent repeated its step-2 advice: send 6-bottle buyers to a different product first. The owner pushed back.
Good challenge. I said that without checking what each type of buyer actually took. Checking the funnel graph and the orders now.
$ ef orders buyers --funnel 412 --range 30d Buyers — funnel #412 last 30 days (2026-08-30 → 2026-09-28, America/New_York) FRONT END TITLE LIST MEDIAN PAID BUYERS TOOK ANY TAKE % BUMP ----------- ----------------------- ------ ----------- ------ -------- ------ ---- VIT_6B Vitality 6 bottles 264.00 264.00 39 10 25.6% 9 VIT_3B Vitality 3 bottles 159.00 159.00 31 5 16.1% 6 VIT_2B Vitality 2 bottles 138.00 138.00 15 0 0.0% 3 VIT_6B_SALE Vitality 6 (sale) 528.00 84.00 12 0 0.0% 2 TOTAL 97 15 15.5% Takes by offer FRONT END OFFER TITLE BUYERS % OF GROUP BOUGHT ON ----------- --------- ------------------------ ------ ---------- --------- VIT_6B VIT_6B_UP 6 more bottles 5 12.8% upsell-1 VIT_6B VIT_3B_DS 3 bottles (downsell) 4 10.3% downsell VIT_6B OMG_3B Omega Plus 3 bottles 2 5.1% upsell-2 VIT_3B VIT_6B_UP 6 more bottles 1 3.2% upsell-1 VIT_3B VIT_3B_DS 3 bottles (downsell) 3 9.7% downsell VIT_3B GLW_4B Glow Collagen 4 bottles 1 3.2% upsell-3 133 purchase row(s) · bump/bonus codes: SHIP_PROTECT · take window 1440 min.
You're right, and I was wrong. Every buyer's first upsell is already 6 more bottles, whatever they bought, and it works best on the buyers who bought 6:
| First purchase | Buyers | Took more Vitality |
|---|---|---|
| 6 bottles, full price | 39 | 9 (23.1%), the highest |
| 3 bottles | 31 | 4 (12.9%) |
| 2 bottles | 15 | 0 |
| 6 bottles on the sale page, paid ~$84 | 12 | 0 |
Buyers who bought the biggest package are the committed ones. "More of the same" isn't the problem. The problems are buyers who buy small and get the biggest offer, and sale buyers who either never see an upsell or see one that costs 2.4 times what they just paid.
This table counts buyers who took more Vitality. The 16.5% in step 2 counts every upsell sale, including the second product.

Then it ran one more cut, because the same logic threatened its other step-2 claim, the Article buyers who "barely see an upsell":
$ ef orders buyers --funnel 412 --range 30d --by page FIRST PAGE PACKAGES BUYERS TOOK ANY TAKE % BUMP MEDIAN PAID ------------------- -------------------------- ------ -------- ------ ---- ----------- watch (2311) VIT_6B, VIT_3B, VIT_2B 45 8 17.8% 10 264.00 story (2318) VIT_6B, VIT_3B, VIT_2B 27 5 18.5% 5 159.00 packages (2320) VIT_6B, VIT_3B, VIT_2B 13 2 15.4% 3 159.00 weekend-sale (2344) VIT_6B_SALE 12 0 0.0% 2 84.00 TOTAL 97 15 15.5%
A second correction: there's no Article leak. Its buyers take upsells at the same rate as everyone else. The compiled funnel flow shows them going to the $150 alternative page; at runtime they reach the main upsell 1. The 3 sessions on that page were real, and they weren't where Article buyers went. The real leak is the sale page: 12 buyers, 0 upsells. I've corrected both points in the skills so the next analysis checks orders before it claims a leak.
This is the moment that made me trust the tool more, not less. The agent was wrong twice in one table, and both times the fix was the same: stop reasoning from page sessions, count buyers from orders. It retracted in plain words and changed its own instructions. A human analyst who does that is rare.
8 · Following one visitorSession traces
Before calling the sale page a leak, the agent traced its buyers, and one of Maya's buyers to explain step 1's impossible 40%:
$ ef sessions show Ws7Kd2Lm9Qa4Xe1Rb8Tn5Hc3Vy6p0Gj Session Ws7Kd2Lm9Qa4Xe1Rb8Tn5Hc3Vy6p0Gj Started 2026-09-21 20:14:07 (America/New_York) Landing /weekend-sale on northwind-example.com Referrer https://mail.google.com/ Affiliate 7702 Funnel 412 Device mobile / iOS / Mobile Safari Country United States / Texas / Austin User agent Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 … Flags bot: no hosting: no vpn: no proxy: no tor: no Page loads 2 Path /weekend-sale → checkout → /members Timeline TIME EVENT PAGE URL NOTE -------- --------- ---- ------------------- ----------- 20:14:07 page-view 2344 /weekend-sale 20:14:15 buy-link 2344 /weekend-sale VIT_6B_SALE 20:15:02 page-view 2390 /members 3 event(s). $ ef sessions show Nf3Bq8Zt1Kc6Wm4Ys9Dh2Lx7Ra5e0Uv … Affiliate 5120 Device mobile / iOS / Instagram Path buy /packages Timeline TIME EVENT PAGE URL NOTE -------- -------- ---- --------- ------------------------------------------------ 14:02:11 buy-link 2520 /packages TRACKING GAP: buy-link with no earlier page-view 1 event(s) · 1 tracking gap(s).
- Sale buyers go from checkout straight to the members area. The sale page isn't wired into the funnel's upsell path, so they never see an offer. That may be deliberate for the house list. If it isn't, they need an upsell priced for someone who just paid $84, not $204.
- Maya's buyers click buy on a page that never recorded its view. They arrive in Instagram's in-app browser, and the page view isn't tracked there. That's why her funnel shows 40%: the buyers are real, the sessions are missing. Until it's fixed, her conversion rate and step table can't be used.
"What about the mobile VSL funnel?"
$ ef stats by page --funnel 587 --metrics sessions,sales,conversion_rate,revenue --range 30d --limit 4 by page last 30 days (2026-08-30 → 2026-09-28, America/New_York) PAGE SESSIONS SALES CONVERSION_RATE REVENUE --------------- -------- ----- --------------- --------- VSL 298 10 3.36% $2,580.40 Article 74 2 2.70% $402.00 Packages 5 0 0.00% $0.00 Long letter 0 2 - $418.00 4 of 11 rows — raise --limit to see more. $ ef funnels pull k8v2rn && jq -r '…node table…' elasticfunnels/funnels/k8v2rn.flow.json 6 purchase output_1->7 7 router output_1->8,9,10,11,12 8 product_check_product vit_1 output_1->20 9 product_check_product vit_2 output_1->21 10 product_check_product vit_3 output_1->22 11 product_check_product vit_6 output_1->23 12 product_check_all output_1->23 22 page up-3-more-84 output_1->30 output_2->14 23 page up-6-more-150 output_1->30 output_2->14 14 page omega-3 output_1->30 output_2->18 18 split_test output_1->41 output_2->42 output_3->43 output_4->44 … $ ef orders buyers --funnel 587 --range 30d FRONT END TITLE LIST MEDIAN PAID BUYERS TOOK ANY TAKE % BUMP --------- ------------------ ------ ----------- ------ -------- ------ ---- VIT_6B Vitality 6 bottles 264.00 264.00 6 3 50.0% 3 VIT_3B Vitality 3 bottles 159.00 159.00 4 0 0.0% 2 VIT_2B Vitality 2 bottles 138.00 138.00 4 0 0.0% 1 TOTAL 14 3 21.4%
The landing page is the VSL, the same page the main funnel uses as a front end: about 60% of this funnel's traffic, most of it Dev's.
Upsell 1 is picked by the product bought, but the checks list the old product codes (vit_3). The VSL sells VIT_3B, which matches none of them, so every VSL buyer falls through to the default: 6 more bottles for $150. A 3-bottle buyer never sees the 3-more-for-$84 offer built for them.
- Keep the $150 six-pack for 6-bottle buyers: 3 of 6 took it, the same pattern as the main funnel.
- Add the current codes to the product checks, so smaller buyers get the offer sized for them.
- There's no same-product downsell. Declining sends buyers to Omega Plus, which sold nothing. On the main funnel, the half-price downsell is the best-converting step.
- Everything after upsell 1 made nothing, and a 4-way split on about 14 buyers a month can never finish. I'd cut it to upsell 1, a Vitality downsell, one second product, members area.
14 buyers is a signal, not a verdict. The code mismatch is certain; the size of the win isn't.
"Dev's AOV? Main and upsell separately"
$ ef stats --aff 2210 --funnel 587 --range 30d --metrics aov,main_order_value,upsell_order_value,sales,revenue,refund_pct last 30 days (2026-08-30 → 2026-09-28, America/New_York) METRIC VALUE CHANGE VS ------------------ --------- ------- ------------------ aov $291.60 +8.12% vs Jul 31 - Aug 29 main_order_value $226.10 +3.02% vs Jul 31 - Aug 29 upsell_order_value $65.50 +30.22% vs Jul 31 - Aug 29 sales 13 -38.10% vs Jul 31 - Aug 29 revenue $2,068.40 -33.94% vs Jul 31 - Aug 29 refund_pct 13.80% +2.10% vs Jul 31 - Aug 29
Dev's AOV is $291.60: $226.10 from the main order and $65.50 in upsells per buyer, against a brand average of $258.40 ($214.30 + $44.10). Upsell order value is upsell revenue spread over his buyers, not the price of an upsell. These are the app's figures; its divisor isn't a whole number of orders, so I'm quoting them rather than recomputing. Sales don't add up to main plus upsell because 3 are shipping-protection bumps.
Then the owner asked the question that turned into the best table of the session: how do his affiliates differ? The agent profiled two of them with the same commands, scoped by --aff:
$ ef stats by device --aff 7702 --funnel 412 --range 30d --metrics sessions,sales,conversion_rate,average_session_duration,aov $ ef stats by device --aff 2210 --funnel 587 --range 30d --metrics sessions,sales,conversion_rate,average_session_duration,aov $ ef orders buyers --funnel 412 --aff 7702 --range 30d

| House email list (7702) | Dev (2210) | |
|---|---|---|
| Traffic | Internal email, mobile | Cold mobile Google Ads to the VSL |
| Landing | Weekend sale page, 6 bottles at ~$84 | VSL |
| Time on page | ~8 seconds | ~4.5 minutes |
| Conversion | 14% | 2.5% |
| AOV | $92 | $291.60 |
| Upsells | 0 of 12: never shown one | 6-bottle buyers take the $150 six-pack |
| What their traffic needs | A price-matched upsell after the sale page | Keep the six-pack upsell, add a Vitality downsell, tune the VSL for phones |
The funnel average hid both. The list's 14% made the funnel look healthy, and Dev's upsell success was invisible inside a 16.5% take rate. Different traffic needs different funnels. To act on it: a page-event rule on aff_id that loads a different upsell for one affiliate, or a dedicated funnel for a big one, then a split test on that affiliate's traffic only.
"Per affiliate, but also traffic source: Meta, Google, YouTube"
Northwind's affiliates don't pass UTMs, so no report could answer this directly. Claude Code did it the slow way: it pulled every buyer's session id from ef orders list --funnel … --all --json, opened each visit with ef sessions show --full-urls (one call per session, so a few minutes), and classified the first visit from its landing URL and user agent with its own rules:
fbclidin the URL, or the Facebook or Instagram in-app browser: Meta (see what fbclid is)gclidin the URL, or a Google click ID forwarded in an affiliate's sub-ID: Google Ads (what gclid is)- A webmail referrer or mail-app user agent: email
- An order with no web visit: phone, through the call center
| Source (my classification) | Buyers | Revenue | Main affiliates |
|---|---|---|---|
| Meta, in-app browser | 99 | $28.4k | Maya |
| Phone / call center | 6 (54 orders) | $13.9k | Call-center partners |
| Google Ads | 32 | $7.7k | Carlos, Lucía, Dev |
| Affiliate presell pages | ~28 | ~$7.0k | Several |
| Affiliate link, no signal | 15 | $3.5k | Two small affiliates |
| 15 | $2.2k | House list | |
| Direct | ~8 | ~$1.8k | - |
- Maya's traffic is Meta, almost all through Instagram's in-app browser, with no click ID and no referrer. That fits the tracking gap in step 8, and it fits her refund rate: impulse buyers refund more.
- About a fifth of revenue is phone orders. They were most of the unlabeled row in step 1.
- Carlos and Lucía run Google search ads: buyers who searched for it. That's why they convert several times better than cold traffic.
- Dev runs Google Ads too. His links forward the click ID in
subid2, which also explains the{gclid}template his monitor pings. - A compliance flag: a few affiliates send traffic from domains that imitate the brand name (misspellings, "-usa.shop" style). Worth checking against your affiliate terms.
Read the header of that table carefully: it's the agent's classification from session data, built for this question, not a report the app printed. It's a good example of what an agent adds. When the report you want doesn't exist, it can build it from the raw visits, slowly, and tell you exactly which rules it used.
12 · The handover"Write this up for a developer"
The last deliverable was a brief in the project, research/main-vsl-dev-brief.md: issues ranked by money, each with the evidence, links, sample ids and how to tell it's fixed. The top of it:
# Main VSL funnel: developer brief (last 30 days, America/New_York) | # | Issue | Severity | Status | |---|--------------------------------------------------|----------|----------| | 1 | Sale-page buyers skip every upsell | High | Open | | 2 | No page views tracked in Instagram's in-app | High | Open | | | browser (partner funnel) | | | | 3 | Mobile VSL funnel: product checks use old codes | Medium | Open | | 4 | Upsell 1 priced 2.4x above sale-page buyers | Medium | Decision | | 5 | Affiliate 40117: real clicks, 0 sales | Low | Decision | ## 1. Sale-page buyers skip every upsell What we see: 12 buyers from /weekend-sale, 0 took an upsell (ef orders buyers --by page). Sample sessions: https://app.elasticfunnels.io/1042/clicks/session?id=Ws7Kd2Lm9Qa4Xe1Rb8Tn5Hc3Vy6p0Gj Check: the sale page's buy links and where checkout returns buyers. Done when: a test purchase on /weekend-sale lands on an upsell page, and sale buyers show takes. ## Reproduce ef orders buyers --funnel 412 --range 30d --by page ef sessions show Ws7Kd2Lm9Qa4Xe1Rb8Tn5Hc3Vy6p0Gj
No customer data in it: order codes and session ids only. A developer can reproduce every number with the commands at the bottom, which is the difference between a brief and an opinion.
What this analysis shows about Claude Code
In one session, a marketer who typed maybe fifteen short prompts got a funnel-by-funnel overview, a step table, a split-test decision carried out, a reconciliation of two numbers that disagreed by a factor of forty, a traffic audit, affiliate profiles, a source breakdown nobody had, and a developer brief. By hand that's days of exports.
It was also wrong three times: the "different product" upsell, the Article leak and the "bot farm". Every one was a claim built on page sessions and pattern-matching instead of orders and engagement, and every one fell over the moment someone asked "are you sure?" and the agent went to the orders. That's the real workflow. The agent does the reading at machine speed, and you do the doubting.
If you want to build and test pages the same way, Claude Code for marketing runs a landing-page split test from brief to result. Reading a test's numbers with an agent is covered in AI A/B testing, and ElasticFunnels analytics is where all of these numbers come from.
- Never act on an upsell or routing finding that came from page sessions. Ask for the per-buyer table from orders first; it's one command.
- When a conversion rate looks absurd, high or low, assume it's measuring something else until two numbers agree.
- Don't let the agent ban an affiliate or filter traffic out of reporting on a pattern alone. Zero sales from engaged people is a conversation with the affiliate, not a bot rule.
- Every time it gets something wrong, make it fix the skill, not just the answer. That's how the next session starts smarter.
None of this replaces knowing your business. The owner knew the uptime monitor was probably Dev's, knew the sale page was for the house list, and knew that big buyers want more of the same. What changed is how fast those hunches got checked against every row. Give the agent the data and the rules, keep the decisions, and argue with it. It argues back well.



