Meta Ad Library Winning Ads Finder & Creative Brief avatar

Meta Ad Library Winning Ads Finder & Creative Brief

Pricing

from $8.00 / 1,000 ad analyzeds

Go to Apify Store
Meta Ad Library Winning Ads Finder & Creative Brief

Meta Ad Library Winning Ads Finder & Creative Brief

Find any brand's winning Facebook & Instagram ads from the Meta Ad Library, tiered Proven Winner (30+ days), Rising or Testing, with a creative brief of hooks, angles, formats and CTAs. Flags new ads. Keyword search mode lists the brands advertising in a niche.

Pricing

from $8.00 / 1,000 ad analyzeds

Rating

0.0

(0)

Developer

Carina Hatton

Carina Hatton

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Security

Limited permissions

Share

Find any brand's winning Facebook and Instagram ads, see which ones are Proven Winners, and get a ready-to-use creative brief.

Give this Actor a brand's Facebook page, Ad Library link or name. It pulls the brand's ads from the public Meta Ad Library and sorts every creative into Proven Winner, Rising or Testing. It then writes a brand-level creative brief covering the hooks, angles, formats, CTAs and landing pages behind the winners. Schedule it and it flags new ads since the last run, so you see a competitor's new tests as they appear.

Built for DTC brands, media buyers, creative strategists and e-commerce agencies who want to know what a competitor keeps paying to run, not just a raw dump of ads.

Selling to ecommerce brands instead of studying one? Switch to keyword search mode. Type a niche keyword like "collagen" and you get a list of the advertisers running ads on it, one row per brand, with the website their ads point to and a link to their Ad Library page. Jump to keyword search.

Why this Actor

  • Winner tiers built on a media buyer's rules. The tiers follow rules from Carina Hatton, who has run Meta ads since 2013 and runs the She Crushes ECom creative agency. The core rule: an ad has to run at least 30 days to count as a winner, and a long run with many versions is the strongest signal.
  • A brief, not a spreadsheet. You get one Markdown and JSON brief per brand, ready to paste into a doc or hand to a creative team.
  • Monitoring built in. Run it daily or weekly. Each run marks new concepts, new versions of known concepts, and ads that stopped.
  • Transparent and tunable. Every threshold and weight sits in one scoringConfig input, and each row shows the score components.
  • Prospect lists from the same Actor. Keyword search mode finds the brands paying to advertise on a keyword, so you know who has an ad budget before you pitch them.

What you get

OutputWhat's in it
Ranked concepts (dataset)One row per concept, with tier, score, days running, versions, active status, format, hook, primary text, CTA, landing page, angles, media URLs, Ad Library link and isNew / newAdIds
Creative brief (BRIEF.md, BRIEF)Tier counts, recommendations, top hooks with links, angle share among winners vs all, formats, CTAs, landing-page patterns and copy length
Run summary (RUN_SUMMARY)Counts, tier counts, the monitoring diff (new ads, new concepts, no-longer-active ads), the scoring config used and the source scraper run ID
Advertisers (dataset, keyword search mode)One row per advertiser across all your search terms: page name and ID, website and domain, active ads seen, first seen date, matched terms, sample ad copy and Ad Library link

Winner tiers

TierRule (defaults)What it means
Proven WinnerRunning 30+ days (hard minimum)The brand has kept paying for it for a month or more
Rising14–29 days and 3+ versionsYoung, but the brand is already iterating on it
TestingEverything elseNew or lightly tested

Inside each tier, concepts are ranked by a 0–100 score. Days running carries the most weight, and versions count most when the ad has also run a long time:

days = min(daysRunning / 180, 1) ^ 0.5
variation = min(ln(versions) / ln(20), 1)
longRunVar = days * variation
active = 1 if still running else 0
spend = only if Meta reports spend (see Limitations); otherwise dropped, never estimated
score = 100 * (0.45*days + 0.30*longRunVar + 0.05*variation + 0.10*active [+ 0.10*spend]) / (sum of weights used)
  • A concept is one Meta collation group. The Ad Library groups an ad's versions under one collation_id, and each group is one concept. Ads are never merged by similar copy, launch date or guesswork. An ad without a collation_id is its own concept.
  • Versions is Meta's collation_count, summed over the group's ad cards.
  • Days running runs from the start date to today if the ad is active, or to its end date, inclusive.

Sample output (AG1 by Athletic Greens, US, active ads)

This sample is from a live run on 2026-10-30 that analyzed 100 active US ads from Ad Library page 183869772601, re-scored with the collation-only grouping above. The run produced 69 concepts: 32 Proven Winners, 13 Rising and 24 Testing.

RankTierScoreDaysVersionsFormatCTAHook
1Proven Winner56.41481DCOShop now"💪 90 Days of AG1."
2Proven Winner48.61011DCOShop now"🚨 Military and First Responders get 25% off AG1 🚨"
4Proven Winner43.7384DCOShop now"Feeling the slump?"
5Proven Winner41.7383DCOShop now"🚨AGZ is here — AG1's first-ever sleep product 🚨"
34Rising45.72613DCOShop now"Hugh Jackman has been drinking AG1 since 2021…"

Brief excerpt (examples/sample-BRIEF-ag1.md):

Based on 8 Proven Winners out of 69 concepts (100 ad cards). Tiers: 32 Proven Winner (30+ days), 13 Rising (14-29 days, several variations), 24 Testing.

  • Lean into "New / launch": 50% of Proven Winners vs 34.8% of all concepts.
  • Dominant format among Proven Winners: DCO (100% of Proven Winners).
  • Default CTA on Proven Winners: "Shop now" (100%).
  • Most Proven Winners send traffic to drinkag1.com/best-green-powder-offers.

Sample dataset row: examples/sample-concept-row-ag1.json. The sample shows one brand on one day. It describes what that brand was running, not how those ads performed.

Pricing (pay per event)

You pay only for what a run produces.

EventPriceWhen
Actor start$0.00005Once per run (Apify's standard start fee, up to 1 GB memory)
Ad analyzed$0.008 per adEach ad card analyzed
Creative brief$0.50 per brand per runOnce per run. Includes the optional AI summary (your own key)
Monitoring check$0.05 per brand per runOnce per run, only when monitoring is on

Keyword search mode charges only the start fee and ad-analyzed. See what keyword search costs.

The ad data also costs money. Ads are fetched by calling the public Store Actor curious_coder/facebook-ads-library-scraper. That run is billed to your account at its own Store price, $0.00075 per ad plus its own $0.00005 start fee at the time of writing. You'll see it as a separate run in your Apify Console.

Worked example: one brand, 100 ads, monitoring on

ItemCost
This Actor: start$0.00005
This Actor: 100 ads × $0.008$0.80
This Actor: creative brief$0.50
This Actor: monitoring check$0.05
This Actor subtotal$1.35
Scraper: 100 ads × $0.00075 + $0.00005 start$0.075
Total for the run≈ $1.43

Ways to control cost:

  • maxAds caps how many ads are fetched and analyzed.
  • scraperMaxChargeUsd caps the scraper run.
  • Apify's maximum cost per run caps this Actor. The Actor first sets aside enough for the brief (and the monitoring check), then analyzes only as many ads as the rest allows. The minimum useful limit is about $0.56 with monitoring on (start + brief + monitoring check + one ad).
  • Turn monitoring off for one-off research.
  • dataSource: "dataset" re-analyzes ads you've already scraped, so there's no scraper cost (this Actor's events still apply).
  • A plain brand name may trigger a short name-search pass first (discoveryMaxAds, 20 ads by default) to find the page ID, which adds scraper cost. A page URL or Ad Library link avoids it.

How to use it

  1. Paste a brand into Brand. The best input is the Ad Library link with view_all_page_id=…. A Facebook page URL or a plain name also works.
  2. Choose a country (default US) and an active status (active is best for finding current winners).
  3. Set Max ads (100 is a good start).
  4. Run it, then open the Overview dataset view and BRIEF.md in the key-value store.
  5. To monitor a brand, schedule the Actor with the same input. Later runs fill in isNew / newAdIds and the New ads view.

Example input:

{ "brand": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&media_type=all&search_type=page&view_all_page_id=183869772601",
"country": "US", "activeStatus": "active", "maxAds": 100, "monitoring": true, "briefTopN": 8 }

Keyword search: find advertisers in a niche

Brand mode tells you what one competitor is running. Keyword search tells you who's running ads in a niche at all, which is the list you want before a single cold email goes out.

Set mode to keywordSearch and give it one or more search terms. The Actor runs a Meta Ad Library keyword search for each term (active ads in the US by default), pages through the results up to your maxAdsPerTerm, and rolls the ads up by Facebook page. A brand that shows up under three of your terms is still one row.

A brand with active ads on "dog treats" this week has a Meta budget and a creative team (or a founder) making ads right now. That's a warmer lead than a scraped list of Shopify stores. You still have to pitch them well.

Keyword search doesn't score ads or write a brief. It builds the list. When a brand on it looks interesting, run brand mode on its adLibraryUrl to see what's working for them.

Example input:

{ "mode": "keywordSearch", "searchTerms": ["collagen", "dog treats"], "country": "US",
"keywordActiveStatus": "active", "maxAdsPerTerm": 100 }

Sample rows from a 50-ad test search for "collagen" (US, active ads, 2026-11-04). That run found 37 advertisers, and 35 of them had a website domain.

AdvertiserDomainActive ads seenFirst seenAd Library
SpoiledChildspoiledchild.com82026-05-09page 103345698622969
True Sea Moss Healthtrueseamoss.com32026-05-19page 101371852670671
Beauty From Beesbeautyfrombees.ca22026-01-19page 2003695169857895

Output fields (one dataset row per advertiser. Anything Meta doesn't show is null, never guessed):

FieldWhat it is
pageName, pageIdThe Facebook page running the ads
website, domainMost common site the page's ads link to, and its root domain
websiteSourcead_link_url or ad_caption, where the domain came from
domainIsRetailertrue when the only domain found is a marketplace or shoppable-link service (Amazon, Costco, Sam's Club, mikmak and similar)
otherDomainsOther domains seen in the same page's ads
activeAdCountActive ads from this page among the ads fetched in this run
activeAdCountIncludingVersionsSame, counting Meta's "N ads use this creative" versions
adsSeenAll ads from this page in this run
activeAdCountMayBeCappedtrue when a matched term had more results than maxAdsPerTerm let through
firstSeenDateEarliest start date among this page's ads in the run
adLibraryUrlThe page's active ads in the Ad Library
pageProfileUrl, pageCategories, pageLikeCountAs Meta shows them on the ad
matchedTermsWhich of your search terms found this page
sampleAdCopy, sampleAdUrlPrimary text of one of the page's ads (first 300 characters) and its Ad Library link

What keyword search costs. You pay the ad-analyzed event ($0.008) once per ad processed, plus the scraper's own $0.00075 per ad. No creative brief or monitoring check is charged in this mode. An ad that matches two of your terms is charged once. maxAdsPerTerm is your cost control: 2 terms at 100 ads each is at most 200 ads, so at most $1.60 for this Actor plus $0.15 to $0.20 for the scraper. The scraper fetches results in pages of about 30 ads and can go one page past your limit. This Actor drops those extra ads and doesn't charge for them, but the scraper bills them, so its spending cap is set to cover one extra page per term (override it with scraperMaxChargeUsd).

Read these before you trust the numbers:

  • activeAdCount is not the brand's total. It counts only the ads this run fetched that matched your keywords. A brand with 400 active ads that appears 3 times in your results shows 3. Open adLibraryUrl for the full picture. With a small maxAdsPerTerm on a big keyword, most rows will have activeAdCountMayBeCapped: true. In the "collagen" test, Meta reported more than 50,000 results and the run fetched 50.
  • Results come in Meta's impressions order, so bigger advertisers surface first. Smaller brands sit deeper in the results and need a higher maxAdsPerTerm, which costs more.
  • Some pages have no domain. Their ads link to a Facebook page, a form or nothing (2 of 37 pages in the test). Others link only to a retailer, like Amazon or Costco, flagged with domainIsRetailer. When a page's ads show both its own site and a retailer, its own site is used.
  • Creators running ads for a brand appear as their own advertiser with the brand's domain. In the "collagen" test, three different pages pointed to trueseamoss.com. Group by domain if you want one row per brand.
  • Meta matches your words anywhere in the ad text, so expect a few off-niche advertisers. exactPhrase: true tightens multi-word terms.

Input reference

FieldDefaultNotes
modebrandbrand (analyze one brand, the default) or keywordSearch (find advertisers)
brandrequired in brand modeAd Library URL, Facebook page URL, or brand name
searchTermsnoneKeyword search mode. One Ad Library keyword search per term
maxAdsPerTerm100Keyword search mode. Ads fetched per term. This drives cost.
keywordActiveStatusactiveKeyword search mode. active, all or inactive
exactPhrasefalseKeyword search mode. Match the exact phrase instead of all words in any order
countryUSAd Library country code, or ALL
activeStatusallBrand mode. all, active or inactive
maxAds200Brand mode. Cap on ads fetched and analyzed. This drives cost.
rescopeToPagetrueAfter a name search, re-fetch the matched page's ads only
discoveryMaxAds20Ads fetched in the name-search pass that only finds the page ID
scoringConfigsee belowTiers, weights and thresholds
briefTopN10Concepts the brief is built from
outputModeallall concepts, or winners (Proven Winners only)
monitoring / monitorStoreNametrue / winning-ads-monitorPer-brand history in a named key-value store in your account
dataSource / sourceDatasetIdcurious_coderapify_official (billed at that Actor's own price, brand mode only), or dataset
scraperMaxChargeUsdautoSpending cap passed to the scraper run
llmProvider / llmApiKey / llmModelnoneOptional AI summary using your own key (secret input, never written to output)

Default scoringConfig:

{ "weights": { "daysRunning": 0.45, "longRunVariations": 0.3, "variationCount": 0.05, "isActive": 0.1, "reportedSpend": 0.1 },
"daysCap": 180, "daysExponent": 0.5, "variationCap": 20, "spendCapUsd": 100000,
"minDaysForWinner": 30, "winnerScoreThreshold": 0, "minDaysForRising": 14, "minVariationsForRising": 3 }

Weights are normalized, so they don't need to sum to 1. Set a weight to 0 to ignore that signal.

Limitations (please read)

  • No spend or performance data for US commercial ads. Meta's Ad Library shows spend and impression ranges only for ads it classifies as political or social-issue. For ordinary commercial ads those fields are empty, and in the AG1 sample none of the 100 ads had spend data. "Winning" is therefore inferred from how long an ad runs and how many versions it has. It is not measured from sales, ROAS or spend, and the tiers are not a prediction of how an ad will perform for you. When Meta does report spend, the Actor uses Meta's range and labels it. It never estimates spend.
  • Long-running ads may have been paused. Days running is measured from the start date. The Ad Library doesn't show whether an ad was paused in between.
  • Strict collation grouping can split one idea into several concepts. Grouping follows Meta's collation_id only. If a brand launches the same copy as several separate ads, each one is a separate concept. In the AG1 sample, the hook "Sold out after launch." appeared in 20 separate collation groups.
  • Identical hooks can appear as separate rows. That happens when Meta lists them as separate ad groups; each row's metaCollationId shows Meta's own group ID.
  • Partial view. Ads are requested in Meta's impressions order for the page. A small maxAds only covers part of a large advertiser's library.
  • Rejected page URLs. The scraper rejects some Facebook page URLs ("Page is private"). The Actor then falls back to a name search plus page re-scoping, which costs extra scraper calls.
  • English-centric angles. Angle detection is keyword based and works best on English copy.
  • Not affiliated with Meta. The Actor reads only the public Ad Library through a third-party Store scraper, and results depend on that scraper and on Meta's Ad Library.

FAQ

Does it show how much a brand spends? Not for commercial ads, because Meta doesn't publish it (see Limitations).

Can I change the 30-day rule? Yes. Set scoringConfig.minDaysForWinner and the other thresholds yourself. The defaults follow Carina Hatton's rules.

Can I get a list of brands advertising in my niche? Yes. That's keyword search mode. It returns advertisers, not ranked ads, and the ad counts cover only what the run fetched (see Keyword search).

Is the AI summary required? No. The brief is rule-based and needs no LLM. The AI summary is optional and uses your own OpenAI or Anthropic key, and that provider bills you directly for tokens.

Local development

npm install && npm test # unit tests (node:test)
apify run # uses storage/key_value_stores/default/INPUT.json