# Meta Ad Library Winning Ads Finder & Creative Brief (`kingly_contest_7cb/meta-ad-library-winning-ads`) Actor

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.

- **URL**: https://apify.com/kingly_contest_7cb/meta-ad-library-winning-ads.md
- **Developed by:** [Carina Hatton](https://apify.com/kingly_contest_7cb) (community)
- **Categories:** Social media, E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 ad analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Meta Ad Library Winning Ads Finder & Creative Brief

**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](#keyword-search-find-advertisers-in-a-niche).

### 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

| Output | What'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

| Tier | Rule (defaults) | What it means |
|---|---|---|
| **Proven Winner** | Running **30+ days** (hard minimum) | The brand has kept paying for it for a month or more |
| **Rising** | 14–29 days **and** 3+ versions | Young, but the brand is already iterating on it |
| **Testing** | Everything else | New 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**.

| Rank | Tier | Score | Days | Versions | Format | CTA | Hook |
|---|---|---|---|---|---|---|---|
| 1 | Proven Winner | 56.4 | 148 | 1 | DCO | Shop now | "💪 90 Days of AG1." |
| 2 | Proven Winner | 48.6 | 101 | 1 | DCO | Shop now | "🚨 Military and First Responders get 25% off AG1 🚨" |
| 4 | Proven Winner | 43.7 | 38 | 4 | DCO | Shop now | "Feeling the slump?" |
| 5 | Proven Winner | 41.7 | 38 | 3 | DCO | Shop now | "🚨AGZ is here — AG1's first-ever sleep product 🚨" |
| 34 | Rising | 45.7 | 26 | 13 | DCO | Shop now | "Hugh Jackman has been drinking AG1 since 2021…" |

Brief excerpt ([full sample](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`](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.

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

Keyword search mode charges only the start fee and `ad-analyzed`. See [what keyword search costs](#keyword-search-find-advertisers-in-a-niche).

**The ad data also costs money.** Ads are fetched by calling the public Store Actor [`curious_coder/facebook-ads-library-scraper`](https://apify.com/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**
| Item | Cost |
|---|---|
| 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:

```json
{ "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:

```json
{ "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.

| Advertiser | Domain | Active ads seen | First seen | Ad Library |
|---|---|---|---|---|
| SpoiledChild | spoiledchild.com | 8 | 2026-05-09 | [page 103345698622969](https://www.facebook.com/ads/library/?active_status=active\&ad_type=all\&country=US\&view_all_page_id=103345698622969) |
| True Sea Moss Health | trueseamoss.com | 3 | 2026-05-19 | [page 101371852670671](https://www.facebook.com/ads/library/?active_status=active\&ad_type=all\&country=US\&view_all_page_id=101371852670671) |
| Beauty From Bees | beautyfrombees.ca | 2 | 2026-01-19 | [page 2003695169857895](https://www.facebook.com/ads/library/?active_status=active\&ad_type=all\&country=US\&view_all_page_id=2003695169857895) |

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

| Field | What it is |
|---|---|
| `pageName`, `pageId` | The Facebook page running the ads |
| `website`, `domain` | Most common site the page's ads link to, and its root domain |
| `websiteSource` | `ad_link_url` or `ad_caption`, where the domain came from |
| `domainIsRetailer` | `true` when the only domain found is a marketplace or shoppable-link service (Amazon, Costco, Sam's Club, mikmak and similar) |
| `otherDomains` | Other domains seen in the same page's ads |
| `activeAdCount` | Active ads from this page among the ads fetched in this run |
| `activeAdCountIncludingVersions` | Same, counting Meta's "N ads use this creative" versions |
| `adsSeen` | All ads from this page in this run |
| `activeAdCountMayBeCapped` | `true` when a matched term had more results than `maxAdsPerTerm` let through |
| `firstSeenDate` | Earliest start date among this page's ads in the run |
| `adLibraryUrl` | The page's active ads in the Ad Library |
| `pageProfileUrl`, `pageCategories`, `pageLikeCount` | As Meta shows them on the ad |
| `matchedTerms` | Which of your search terms found this page |
| `sampleAdCopy`, `sampleAdUrl` | Primary 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

| Field | Default | Notes |
|---|---|---|
| `mode` | `brand` | `brand` (analyze one brand, the default) or `keywordSearch` (find advertisers) |
| `brand` | required in brand mode | Ad Library URL, Facebook page URL, or brand name |
| `searchTerms` | none | Keyword search mode. One Ad Library keyword search per term |
| `maxAdsPerTerm` | 100 | Keyword search mode. Ads fetched per term. This drives cost. |
| `keywordActiveStatus` | `active` | Keyword search mode. `active`, `all` or `inactive` |
| `exactPhrase` | false | Keyword search mode. Match the exact phrase instead of all words in any order |
| `country` | `US` | Ad Library country code, or `ALL` |
| `activeStatus` | `all` | Brand mode. `all`, `active` or `inactive` |
| `maxAds` | 200 | Brand mode. Cap on ads fetched and analyzed. This drives cost. |
| `rescopeToPage` | true | After a name search, re-fetch the matched page's ads only |
| `discoveryMaxAds` | 20 | Ads fetched in the name-search pass that only finds the page ID |
| `scoringConfig` | see below | Tiers, weights and thresholds |
| `briefTopN` | 10 | Concepts the brief is built from |
| `outputMode` | `all` | `all` concepts, or `winners` (Proven Winners only) |
| `monitoring` / `monitorStoreName` | true / `winning-ads-monitor` | Per-brand history in a named key-value store in your account |
| `dataSource` / `sourceDatasetId` | `curious_coder` | `apify_official` (billed at that Actor's own price, brand mode only), or `dataset` |
| `scraperMaxChargeUsd` | auto | Spending cap passed to the scraper run |
| `llmProvider` / `llmApiKey` / `llmModel` | none | Optional AI summary using your own key (secret input, never written to output) |

Default `scoringConfig`:

```json
{ "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](#keyword-search-find-advertisers-in-a-niche)).

**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

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

# Actor input Schema

## `mode` (type: `string`):

'Analyze one brand' (default) ranks a brand's ads and writes the creative brief. 'Keyword search (find advertisers)' searches the Ad Library for keywords and returns one row per unique advertiser (page), with website domain, active ad count and an Ad Library link. Keyword search charges only per ad processed (no brief).

## `brand` (type: `string`):

Required in 'Analyze one brand' mode. Facebook page URL (best, e.g. https://www.facebook.com/allbirds), a Meta Ad Library URL, or a plain brand name (keyword search, filtered to pages whose name matches).

## `searchTerms` (type: `array`):

Keyword search mode only. One Ad Library keyword search per term (all words, any order). Advertisers are de-duplicated by page ID across all terms.

## `maxAdsPerTerm` (type: `integer`):

Keyword search mode only. Ads fetched per search term (sorted by impressions). This is your cost control: you are charged per ad processed, and the underlying scraper bills about $0.00075 per ad. Active ad counts per advertiser only cover the ads fetched, so they can be capped by this number.

## `keywordActiveStatus` (type: `string`):

Keyword search mode only. Search active ads (default), all ads, or only inactive ads.

## `exactPhrase` (type: `boolean`):

Keyword search mode only. Match the exact phrase instead of all words in any order.

## `country` (type: `string`):

2-letter ISO country code the ads were shown in, or ALL.

## `activeStatus` (type: `string`):

Analyze all ads (recommended: stopped ads show what was cut), only active, or only inactive.

## `maxAds` (type: `integer`):

Upper limit of ad cards fetched from the Ad Library (sorted by impressions).

## `rescopeToPage` (type: `boolean`):

When the brand is given as a name (or a page URL the scraper can't open), the Ad Library keyword search also returns other advertisers. If a page whose name matches is found, the Actor re-fetches only that page's ads (one extra scraper call). Turn off to keep the cheaper, noisier keyword-only result.

## `scoringConfig` (type: `object`):

How winners are scored and tiered. Tiers: Proven Winner = running at least minDaysForWinner days (default 30, hard minimum); Rising = minDaysForRising-29 days (default 14) with at least minVariationsForRising variations (default 3); Testing = everything else. Score 0-100 ranks concepts inside each tier: days running carries the most weight (sqrt curve up to daysCap=180 days), longRunVariations = days x variations so many versions only count when the ad has also run long, small weights for variations alone and still-active, and reportedSpend only for ads where Meta discloses spend (political/social-issue ads); otherwise that weight is dropped, never estimated. See README. Concepts are Meta's own collation groups (collation_id); ads are never merged by similar copy or launch date.

## `briefTopN` (type: `integer`):

Maximum number of top winners the creative brief is built from.

## `outputMode` (type: `string`):

Output every ranked concept, or only winners.

## `monitoring` (type: `boolean`):

Remember which ads were seen per brand and flag ads/concepts that are new since the previous run. Schedule the Actor daily or weekly to get alerts.

## `monitorStoreName` (type: `string`):

Named key-value store in your account that keeps the per-brand state. Use different names to keep separate watchlists.

## `dataSource` (type: `string`):

Where ads come from. Store scrapers are billed on their own pricing in addition to this Actor. 'dataset' re-analyzes ads you already scraped (no scraping cost).

## `sourceDatasetId` (type: `string`):

Only for data source 'dataset': ID or name of a dataset produced by one of the supported scrapers.

## `scraperMaxChargeUsd` (type: `number`):

Maximum the underlying scraper run may charge. Leave empty to derive it from max ads.

## `llmProvider` (type: `string`):

Add an AI-written strategy summary to the brief using your own API key. Included in the creative-brief price; your LLM provider bills you directly for its tokens.

## `llmApiKey` (type: `string`):

Your OpenAI or Anthropic API key. Stored encrypted; never written to output.

## `llmModel` (type: `string`):

Optional model name override (defaults: gpt-4o-mini / claude-3-5-haiku-latest).

## `discoveryMaxAds` (type: `integer`):

When a brand name (or a page URL the scraper rejects) needs a keyword search to find the page ID, fetch only this many ads in that first pass before re-fetching the page's own ads. Keeps scraper cost down.

## Actor input object example

```json
{
  "mode": "brand",
  "brand": "https://www.facebook.com/allbirds",
  "searchTerms": [
    "collagen",
    "dog treats"
  ],
  "maxAdsPerTerm": 100,
  "keywordActiveStatus": "active",
  "exactPhrase": false,
  "country": "US",
  "activeStatus": "all",
  "maxAds": 50,
  "rescopeToPage": true,
  "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
  },
  "briefTopN": 10,
  "outputMode": "all",
  "monitoring": true,
  "monitorStoreName": "winning-ads-monitor",
  "dataSource": "curious_coder",
  "llmProvider": "none",
  "discoveryMaxAds": 20
}
```

# Actor output Schema

## `concepts` (type: `string`):

No description

## `brief` (type: `string`):

No description

## `briefMarkdown` (type: `string`):

No description

## `files` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "brand": "https://www.facebook.com/allbirds",
    "maxAds": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingly_contest_7cb/meta-ad-library-winning-ads").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "brand": "https://www.facebook.com/allbirds",
    "maxAds": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("kingly_contest_7cb/meta-ad-library-winning-ads").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "brand": "https://www.facebook.com/allbirds",
  "maxAds": 50
}' |
apify call kingly_contest_7cb/meta-ad-library-winning-ads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingly_contest_7cb/meta-ad-library-winning-ads"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/QaP6vTwtdAhrqmd8Y/builds/guDbqre2JndQx1zTL/openapi.json
