Every ad-spy tool stops at the creative. This one goes past the click: you give it a competitor and it returns a ranked map of their proven landing pages, a teardown of why each converts, and a walk of their pre-purchase upsell funnel up to the edge of checkout. Full build in Claude Code, with the honesty guardrails that keep it client-safe. The last section is straight about what AutoAdy does and doesn't do here.
A hook and a thumbnail tell you almost nothing. The value is in what happens after the click: the landing pages a competitor kept after months of testing, why those pages convert, and the upsell stack quietly funding their ad spend. This guide builds a Claude Code plugin that reads all of it from public data, packaged so you install once and run on any competitor with a single command. The example plugin is funnel-teardown; rename it freely.
The principle that keeps it honest You score "proven" from observable signals, never guesses. Meta hides spend and impressions for commercial ads, so the only truthful proxies are how many ads point at a page and how long they've been running. Everything downstream reads public storefront data. Nothing requires logging into anyone's account or completing a purchase. Where you genuinely can't see something, you flag it rather than fake it. That line is exactly what makes the output safe to put in front of a client.
The architecture
Five stages, each with a clear owner:
| Stage | What it does | How |
|---|---|---|
| 1 · Ingest | Pull a competitor's active ads and their destination URLs | Ad Library scraper |
| 2 · Rank | Cluster ads by landing page, score the proven winners | Python |
| 3 · Teardown | Analyze the ads and the pages: why do they convert? | vision models |
| 4 · Fingerprint | Detect the store platform and which upsell apps run | Python (HTML signatures) |
| 5 · Funnel depth | Capture cart and checkout upsells, stopping before payment | storefront APIs + a browser driver |
The stack: Claude Code for orchestration, the plugin shell, and the landing-page vision teardown done in-session at no metered cost. An Ad Library scraper for the ads. A headless-browser scrape service for page screenshots and for driving the cart and checkout. A flash-tier vision model for the one paid step that actually watches the video creatives. Python for normalization, clustering, ranking, cart probing, and the gallery build.
Keys are environment variables, never hardcoded:
APIFY_TOKEN=...
FIRECRAWL_API_KEY=...
GEMINI_API_KEY=...
Step 0: Scaffold the plugin
A plugin is a folder with a manifest and one or more skills. Namespace the skill so it's invoked as /funnel-teardown:scan:
funnel-teardown/
├── .claude-plugin/
│ ├── plugin.json # name, version 0.1.0, the scan command
│ └── marketplace.json
├── skills/
│ └── scan/
│ ├── SKILL.md # the run sheet: stage order, when to call each script
│ ├── scripts/
│ └── references/
├── .env.example
└── README.md
The SKILL.md is the brain. It's the run sheet, not the code: it tells Claude the order of stages, when to call each script, and how to assemble the final report. Reference scripts with ${CLAUDE_PLUGIN_ROOT} so paths survive install.
Stage 1: Ingest
The one field this whole tool depends on is the ad's destination URL, and Meta's official Graph API does not reliably expose it for commercial ads. Frontend-snapshot Ad Library scrapers do return it, so that's what you use (the curious_coder/facebook-ads-library-scraper actor is one that returns the destination URL, ad copy, creative URLs, and days-running).
scripts/scrape_ads.py takes a competitor's page or a keyword, calls the actor with APIFY_TOKEN, filters to ACTIVE ads, and captures per ad: destination URL, ad copy (primary text plus headline), creative URLs, and how many days it's been running.
One rule that pays for itself: every metered pass writes to disk. Cache the raw pull to a JSON file keyed by competitor so re-running a scan on the same brand is free.
Stage 2: Rank the proven winners, not the tests
A competitor might run 200 ads pointing at 30 URLs. Most are tests. You want the handful of pages with real, sustained ad volume.
scripts/rank_winners.py does three things:
- Normalize each destination URL: strip tracking params (
utm_*,fbclid, and friends), lowercase the host, keep host plus path only. Variations collapse into one page. - Cluster ads on the normalized URL.
- Score each cluster:
import math
def winner_score(ad_count, max_days_active):
return ad_count * (1 + math.log10(1 + max_days_active))
Ad volume is how much creative they're pushing; run duration is how long it survived. A page with 40 ads running 90 days is a proven winner. A page with 2 ads up for 3 days is a test. The log dampens runtime so one ancient ad doesn't outrank a high-volume current push. Use the max days_active in the cluster.
Output winners.csv ranked by score, columns landing_page, ad_count, max_days_active, winner_score, sample_ad_copy. Surface the top five as the leaderboard. Everything downstream runs only on those.
Stage 3: Teardown (the heart of the tool)
For each proven page, produce three reads.
3A, Ad-cluster analysis. Send the creatives behind each winning page to a flash-tier vision model so it actually watches the video, not just reads the caption (this is the one metered model call). Ground it in a real creative-strategy framework: hook type, concept versus angle versus offer, emotional driver, and the script beats. Then group creatives by underlying concept to surface the one-concept-times-many-creators families that signal a scaled winner.
3B, Landing-page teardown. Claude Code screenshots the page and reads it in-session at no external cost. This is the highest-value output, so spend your attention here. Tear down the hook, the above-fold offer, the social-proof placement, the CTA logic, the price framing, and the urgency mechanics. The single question on every page: why does this page convert?
3C, Ad-to-page throughline. A short, descriptive read of how the ad sets up the page. Does the hook's promise match the above-fold offer? Keep this descriptive only. You cannot see a competitor's conversion rate, so never fake a "match score." Describe the handoff; don't grade it.
scripts/teardown_pages.py runs all three for the top five and writes each page's output to teardowns/{slug}/teardown.md. Put the creative-strategy framework in references/teardown-rubric.md so the analysis stays consistent across every scan and every client.
Stage 4: Fingerprint the funnel
Before you walk the funnel, identify what's running it. Page HTML carries signatures for the store platform and for the upsell and subscription apps installed (the common ones include ReConvert, AfterSell, Zipify, and Rebuy). Detecting these does two things: it tells you what upsell behavior to expect downstream, and it lets you flag the post-purchase offer you can't read.
scripts/fingerprint.py pulls each winner's HTML and matches it against references/fingerprint-signatures.md to detect the platform and the installed app stack, then appends both to the teardown file.
Stage 5: Funnel depth (what no ad-spy tool does)
This captures the pre-purchase monetization funnel, the upsells that quietly fund their ad spend, in three layers. Everything here is public storefront data: building a cart and reaching checkout is exactly what any abandoned-cart shopper does.
Layer 1, Cart data (no browser, fast, runs on all winners). On Shopify the cart is readable through public Ajax endpoints. Resolve the page's hero product and variant, then:
POST /cart/add.js # add the hero product
GET /cart.js # read what's in the cart now
GET /recommendations/products.json?intent=complementary
That surfaces auto-added gift-with-purchase items, bundles and their components, applied or automatic discounts, totals, and configured cross-sell SKUs. Record absences too. "No subscription offer, no gift-with-purchase" is itself a finding worth reporting.
Layer 2, Rendered cart. The reward-tier progress bar ("$12 away from free shipping") and the in-cart cross-sell carousel are app-rendered and won't show in /cart.js. Populate a cart via the Shopify permalink, load /cart, and screenshot it for a vision read:
/cart/{variant_id}:1 # then load /cart and screenshot
Layer 3, Checkout walk (top five, configurable). Drive a scripted browser to the standard checkout, fill dummy contact and shipping data to reach the information step, and capture the in-checkout upsells: add-on order bumps like shipping protection, payment-plan options such as pay-in-four, and trust badges. It never clicks "Pay now."
scripts/probe_cart.py handles layers one and two; scripts/walk_checkout.py handles layer three using a browser-driver in code mode, with dummy data from references/dummy-checkout.md.
Bake in graceful degradation: if a product won't resolve, a bot wall appears, or checkout is unreachable, write a note and continue. Never crash the whole scan over one page.
The line the tool stops at, and why that's a feature
Everything up to and including the checkout information step is capturable from public data without buying anything. The wall is the "Pay now" click. A store's true post-purchase one-time offer only renders after a real, completed, paid order, so it cannot be read without buying.
Don't fake it. Flag it. Stage 4's fingerprint already detected the app powering that post-purchase offer, so label it "flagged, not read." On Shopify the post-purchase steps are states, not public URLs, which is exactly why the pre-purchase layers are read live and the post-purchase layer is inferred. Being honest about this line is what makes the tool trustworthy in front of a client.
Outputs and guardrails
Build the result into an HTML gallery: the ranked leaderboard, plain-language metrics ("12 ads · running 84 days · 6 creatives"), one-line play descriptors, side-by-side ad-to-page views, and locally downloaded creatives, pulling teardown content from each teardowns/{slug}/teardown.md.
Guardrails to bake in:
- Score "proven" only from observable signals (ad count, run-time). Never invent metrics you can't see.
- Never fake a match score in the ad-to-page read.
- Record absences as findings.
- Label the post-purchase offer "flagged, not read."
- Degrade gracefully on every metered layer, and cache every metered pass so re-runs are free.
Package it like any plugin: bump version in plugin.json, reference scripts with ${CLAUDE_PLUGIN_ROOT}, add it through your plugin marketplace path, install, and run /funnel-teardown:scan on one real competitor.
The honest part: what AutoAdy does and doesn't do here
This is the build where I'll be most direct, because it's the one furthest from what AutoAdy is.
AutoAdy does not spy on competitor funnels. It doesn't scrape the Ad Library at scale, it doesn't walk a competitor's Shopify checkout, and it shouldn't pretend to. That reconnaissance is exactly what this DIY build is for, and a scraper plus a browser driver are the right tools. If a tool tells you it reads your competitor's conversion rate or their post-purchase upsell, it's guessing. The guardrails above exist precisely because the honest version flags what it can't see.
Where AutoAdy takes over is your own side of the glass. A funnel teardown is research; the payoff is building something better. Once you've seen the angles a competitor proved, AutoAdy turns them into your own tests: the ad copy generator and creative brief build the ads around the winning angle, the account health grader and drop diagnostic keep your own account honest, and the connected agent publishes and prunes against your live data.
So the division of labor is clean. Use this build to read what competitors spent months and real ad dollars to validate. Use AutoAdy to turn that into your own proven funnel, on your own account, where the data is actually yours to see.