# Facebook Ads Monitor: Track Competitor Ads (`deepmine/facebook-ads-monitor`) Actor

Track competitors' Facebook and Instagram ads in the Meta Ad Library. Add their pages, schedule a daily run and get only the ads they started since the last run: ad text, images, videos, landing page, CTA, start date and platforms. You pay only for new ads. No login.

- **URL**: https://apify.com/deepmine/facebook-ads-monitor.md
- **Developed by:** [DeepMine](https://apify.com/deepmine) (community)
- **Categories:** Marketing, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 ads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Facebook Ads Monitor: Track Competitor Ads

Add your competitors' Facebook pages, schedule a daily run, and get **only the ads they started since the last run** from the Meta Ad Library, with text, headline, creative, landing page and start date.

| Advertiser | Ad text | Headline | Button | Format | Started |
|---|---|---|---|---|---|
| Gymshark | Use code Extra20 to get an extra 20% off\* Last Cha… | Extra 20% off\* | Shop now | Dynamic creative | 2026-09-27 |
| Sephora | Such a fun day exploring all the fragrances at the… | – | Shop now | Video | 2026-09-25 |
| Sephora | Everday glamour made just for you. Patrick Ta's Cr… | Patrick Ta Major Headlines Cream Powder Blush Duo | Shop now | Video | 2026-09-24 |

<sub>Collected 2026-09-28 from the prefilled run (Gymshark and Sephora, US, only new ads): a first run returns each advertiser's newest ads. Every row also has the creative's picture, the video file, the landing page, the platforms and the ad's Ad Library link.</sub>

**$0.60 per 1,000 ads** on Starter ($0.75 Free, $0.50 Scale, $0.40 Business); with ad details $1.25 per 1,000 ($1.40 Free, $1.15 Scale, $1.05 Business). The prefilled run (up to 100 ads) costs about $0.06.

**You pay only for the new ads**, not for the ads a run reads to find them. On 2026-09-28, the second run of the prefilled monitor read 882 of Sephora's ads and returned none, because Sephora had started none since the first run: that part of the run cost nothing. On 2026-09-26, a monitor on Gymshark's US page read 446 ads on its second run and returned the 2 ads started since the run before.

No Facebook account, login or cookies. Platforms covered: Facebook, Instagram, Messenger, Threads, Audience Network and WhatsApp.

### Set it up in three steps

1. Put your competitors' Facebook page links, page IDs or Ad Library links in **Advertisers**. Keywords in **Search terms** work too (a brand, a product).
2. Keep **Only new ads since the last run** on (the default).
3. Save it as a task and schedule it daily. The first run returns the newest ads (up to *Max ads per search*); every later run returns only the new ones.

Turn on **Include ad details** to add the advertiser's verification and Instagram account and followers and, for ads shown in the EU or UK, **reach by country**, targeted ages and gender, payer and beneficiary.

### What you can do with it

- **Competitor monitoring**: see every new ad your competitors launch, the day after they launch it, however many ads they already run.
- **Creative swipe file**: collect their new images, videos, hooks, headlines and buttons as they appear.
- **Offer and promo tracking**: new discount codes, launches and landing pages show up as new ads (Gymshark's "Extra20" sale above).
- **Brand and agency reporting**: a daily feed of what each brand in a list is running, straight into a sheet or a Slack alert.

### Input

| Field | What it does |
|---|---|
| **Advertisers** | Competitors to monitor: a Facebook page link (`facebook.com/nike`), a page id (`15087023444`), an advertiser's Ad Library link, or the page name exactly as it appears on its ads. |
| **Only new ads since the last run** | On by default: return only ads started since the last run that this Actor hasn't returned before for the same advertiser or search. *Max ads per search* then limits the new ads per run. You pay only for the new ads. Off: every ad, each run. |
| **Search terms** | Keywords to monitor too, as in the Ad Library search box. One search per line. |
| **Country** | Two-letter code where the ads ran (`US`, `GB`, `DE`, ...) or `ALL` (US in the form, `ALL` if left out). |
| **Max ads per search** | Stop each advertiser or search after this many ads (50 in the form, 200 if left out, up to 10,000). You pay per ad. |
| **Include ad details** | Adds the advertiser's profile and EU/UK reach data to each ad, at the with-details price (one more request per ad, so slower). |

Under **Filters and Ad Library URLs**:

| Field | What it does |
|---|---|
| **Ad Library URLs** | Searches copied from [facebook.com/ads/library](https://www.facebook.com/ads/library/). The link's own filters are used. |
| **Ad status** | Active (default), inactive, or both. |
| **Media type** | All, images, videos, memes, images and memes, or no media. |
| **Ad category** | All ads, or only ads about social issues, elections or politics (these carry spend, impressions and reach). |
| **Exact phrase** | Match the search terms as an exact phrase. |
| **Shown on or after / before** | The Ad Library's date filter: ads that were shown in that period, whatever day they started. |

Give at least one advertiser, search term or Ad Library URL; a run with none fails with a message. Example:

```json
{
  "advertisers": ["https://www.facebook.com/Gymshark/", "https://www.facebook.com/sephora/"],
  "onlyNewAds": true,
  "country": "US",
  "maxAdsPerQuery": 50
}
```

### How monitoring works

- Monitoring reads the Ad Library's **Most recent** order. The first run returns the newest ads, up to *Max ads per search*.
- Later runs go back to about two days before the previous run, so ads that appear in the Library a little late aren't missed. They return every ad started since then that wasn't returned before, even if the advertiser runs thousands of ads (up to the read limit below).
- Meta orders ads by the month they started, but not exactly by day inside a month. So a run reads the current month's ads (and the previous month's early in a month), not just the top few, and a daily run on a big advertiser can read a few hundred ads to find two new ones. You pay only for the new ads.
- If more new ads started than *Max ads per search*, the run returns that many and the next run continues where it stopped.
- A run reads at most 1,000 ads past *Max ads per search*. If a search has more ads this month than that, the run can't read back to the previous run: it returns the new ads it found, and its status message and OUTPUT `warning` say that new ads further down Meta's list were skipped. The next run starts from this one, so the monitor keeps working, but running more often doesn't help: the month's ads are still in the way. To check deeper, raise *Max ads per search* or narrow the search (country, media type).
- The history is kept per advertiser or search in your account, in this Actor's own `facebook-ads-monitor-monitor` key-value store (the run log names it). A search is the same search when its words or advertiser and all its filters are the same.

### Output

One row per new ad, most recent first. An ad found by several searches appears once, under the first search that found it.

Each row has two parts:

1. **The clean fields first** (below): flat, readable values for spreadsheets and the Console tables. Every row has them in the same order; a value Meta doesn't give is `null`.
2. **Then the apify/facebook-ads-scraper fields**, under that Actor's names: Meta's full ad record ([below](#facebook-ads-scraper-fields)). They're what makes a row about 15 KB; skip them if you only need the clean part.

Clean fields, in row order:

- `image`: the creative's picture: its first image, the video's preview frame, or the first card's image.
- `pageName`: the advertiser (its Facebook page).
- `adText`, `adTextSnippet`: the ad's primary text in full, and its first 50 characters on one line.
- `headline`: the headline under the creative.
- `adLibraryUrl`: the ad in the Meta Ad Library.
- `landingUrl`: where the ad's button leads.
- `ctaText`: the button text, e.g. *Shop now*.
- `format`: Image, Video, Carousel, Dynamic creative, Catalog, ...
- `platforms`: Facebook, Instagram, Messenger, Threads, Audience Network, WhatsApp.
- `isActive`: still running when the run read it.
- `pageLikes`: likes of the advertiser's Facebook page.
- `collationCount`: how many ads use this creative and text. Meta says it on one ad of each group, so it's often `null`.
- `daysActive`: days from `startedAt` to `lastShownAt`.
- `startedAt`: the day the ad started (YYYY-MM-DD).
- `lastShownAt`: the last day it ran: the day it stopped, or the run's day for active ads.
- `rank`: the ad's place in its search.
- `videoUrl`: the ad's video file (standard definition), for video ads.
- `pageUrl`: the advertiser's Facebook page.
- `adArchiveId`, `pageId`: the ad's Library ID and the advertiser's page id.
- `searchInput`: the advertiser, search term or Ad Library URL that found the ad.
- `scrapedAt`: when the run started (UTC).

Dynamic and catalog ads keep placeholders like `{{product.name}}` in their top text; the clean fields take the text, headline and picture of the ad's first card instead, as the Ad Library shows it.

**Images and videos expire.** `image`, `videoUrl` and the media links in `snapshot` are Meta's signed CDN links (fbcdn.net). They stop working about 5 days after the run, so download what you want to keep.

When a search is for **Issues, elections or politics** (*Ad category*, or `ad_type=political_and_issue_ads` in an Ad Library URL), the clean part also has these fields after `isActive`. Meta publishes them for those ads only:

- `spend`: Meta's spend range as shown, e.g. `$45K - $50K`.
- `spendMin`, `spendMax`: the same range as numbers in `currency` (`spendMax` is `null` for an open range like `>$1M`).
- `currency`: e.g. `USD`.
- `impressions`, `reachEstimate`: Meta's impressions range (e.g. `>1M`) and estimated audience size (e.g. `100K - 500K`).
- `paidFor`: the *Paid for by* disclaimer.

With **Include ad details**, these fields come in after `pageUrl` (details are fetched per ad; a row whose details are missing is charged as a plain ad):

- `detailsStatus`: OK, Not available (Meta has none for this ad) or Failed.
- `isPageVerified`: the advertiser's page has Meta's verified badge.
- `instagramUrl`, `instagramFollowers`: the advertiser's Instagram account and its followers.
- `euReach`, `ukReach`: people reached in the EU and in the UK (ads shown there only).
- `targetAges`, `targetGender`: the targeted age range (e.g. `18-65+`) and gender.
- `reachByCountry`: people reached per country, most first (e.g. `DE: 14,914`).
- `payer`, `beneficiary`: who paid for the ad and who it benefits.

In the Console, the dataset has these tables: **📊 Overview** (creative, advertiser, text, headline, links, button, format, platforms, active, started, days active, search), **📈 Stats** (rank, days active, page likes, same-creative count, dates), **🎨 Creatives** (picture, text, headline, button, video, landing page), **🏛️ Political** (spend, impressions and reach; fills on political searches) and **📋 Details** (fills with *Include ad details*). *All fields* shows the whole row, the facebook-ads-scraper part included.

#### facebook-ads-scraper fields

After `scrapedAt`, every row has the same fields as apify/facebook-ads-scraper, under the same names: Meta's raw ad record with camelCase keys, plus `inputUrl`, `pageID` / `adArchiveID` and `startDateFormatted` / `endDateFormatted`. Pipelines built on that Actor keep working when you switch. This part is nested (`snapshot` holds the creative), uses Meta's constants (`FACEBOOK`, `VIDEO`), and has every image and video variant (HD and SD). With *Include ad details*, Meta's `ad_details` object comes as is, including reach by age and gender.

A field that is in both parts appears once, in the clean part, with the same value: `pageName`, `isActive`, `pageId`, `adArchiveId`, `collationCount` and, on political searches, `spend`, `currency` and `reachEstimate`. Meta's own `adId` (usually `null`) is a different id from `adArchiveId`.

Main fields of this part:

- `adArchiveID`, `pageID`: the ad's Library ID and the advertiser's page id (the same as `adArchiveId` and `pageId`).
- `snapshot`: the creative. It holds `body.text`, `title`, `caption`, `ctaText`, `linkUrl`, `displayFormat` (IMAGE, VIDEO, CAROUSEL, DCO, DPA), `images`, `videos`, `cards` (carousel and dynamic-ad variants, each with its own text and media), and the page's likes, categories and picture.
- `startDateFormatted` / `endDateFormatted` (also `startDate` / `endDate` in unix seconds), `publisherPlatform`.
- `ad_details` (with *Include ad details*): the advertiser profile and, when `isAaaEligible` is true (the ad was shown in the EU/UK), reach and audience data. `detailsStatus` in the clean part says whether they came.
- `spend`, `impressionsWithIndex`, `reachEstimate`, `currency`: Meta fills these **only for political and issue ads**. They're empty for commercial ads in every Ad Library tool, because Meta doesn't publish them.

#### Run summary

The run's **OUTPUT** record lists every advertiser and search with Meta's result count (`metaCount`), the ads read, the ads delivered, how many were already returned before (`previouslySeen`) and why it stopped: `caughtUp` (the monitor reached the ads of its last run), `maxAds`, `complete`, `chargeLimit`, `readCap` (the monitor couldn't read back to its last run; see `warning`), `duplicate` (the same search was given twice), or a failure: `blocked`, `error`, `incomplete`.

### Reliability

- Uses Apify **residential proxies** by default. Meta refuses most datacenter IPs after the first page of results, so datacenter proxies aren't recommended.
- A refused or failed request is retried on a new IP.
- If Meta still refuses a search, the ads already collected are kept and the run **fails with the reason**. It doesn't report success on an incomplete search.
- With *Include ad details*, failed details get a second try on new IPs. If details are still missing for more than half the ads, the run fails with the reason (the ads are kept, and charged as plain ads).
- Each ad appears once per run, even when several searches find it.

### Pricing

Pay per ad, by your Apify plan. No start fee, no monthly fee.

| Per 1,000 ads | Free | Starter | Scale | Business |
|---|---|---|---|---|
| Ad | $0.75 | $0.60 | $0.50 | $0.40 |
| Ad with details | $1.40 | $1.25 | $1.15 | $1.05 |

- With *Only new ads* you pay only for the new ads, not for the ads the run reads to find them. An advertiser with no new ads since the last run adds nothing to the bill.
- The with-details price is the ad price plus a flat $0.65 per 1,000 for the details, the same on every plan.
- An ad is charged the with-details price only when its details came; an ad whose details failed is charged as a plain ad.
- Set a maximum cost per run in the run options: the run stops before it would go past it and keeps what it collected (`OUTPUT.chargeLimitReached` is then `true`).

### Limitations

- Advertiser names are matched against the names on their ads. If a name isn't found, use the page link or page id; the run's OUTPUT lists names it couldn't find.
- Links to a single ad (`?id=...`) aren't supported as input yet. Monitor its advertiser instead.
- Spend and impressions exist only for political and issue ads (Meta's rule, see above).
- If you monitor the same advertiser with the same filters here and in our Meta Ad Library Actor, the two share the monitor history, so each new ad is returned by whichever runs first.

### FAQ

**Do I need a Facebook account?** No. Everything comes from the public Ad Library, without logging in.

**Which countries work?** Every country in the Ad Library, or `ALL`.

**How often should I run it?** Daily is enough for most brands. Running more often doesn't find more ads; it just spreads them over more runs.

**Can I get all of a competitor's current ads, not just new ones?** Turn off *Only new ads since the last run*: each run then returns the advertiser's ads, most impressions first, up to *Max ads per search*.

### Feedback

Found a bug or missing a field? Open an issue on the **Issues** tab and we'll look at it within 48 hours. Happy with the data? A short review on the Store helps others find this Actor.

# Actor input Schema

## `advertisers` (type: `array`):

Competitors to monitor, one per line: a Facebook page link (facebook.com/nike), a page id, an Ad Library link of the advertiser, or the page name exactly as it shows on its ads.

## `onlyNewAds` (type: `boolean`):

Return only ads started since the last run that this Actor hasn't returned before for the same advertiser or search, reading up to 1,000 ads past Max ads per search to find them. You pay only for the new ads. The first run returns the newest ads, up to Max ads per search. Save the input as a task and schedule it (e.g. daily). Turn it off to get every ad each run.

## `searchQueries` (type: `array`):

Keywords to monitor too (a brand, a product, a competitor's name), as you'd type them in the Ad Library search box. One search per line.

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

Two-letter country code where the ads ran (US, GB, DE, ...), or ALL for every country.

## `maxAdsPerQuery` (type: `integer`):

Stop each search, advertiser or URL after this many ads. You pay per ad, so this caps the cost of each search. Ads come most impressions first (with Only new ads, most recent first, and this limits the new ads per run). About 3 minutes per 1,000 ads.

## `includeAdDetails` (type: `boolean`):

Also fetch each ad's details: the advertiser's verification and Instagram account and followers and, for ads shown in the EU or UK, the reach, reach by country, targeted ages and gender, plus the payer and beneficiary (Meta's full details, with reach by age and gender, are in ad\_details). An ad with details costs more than a plain ad (see Pricing), and each needs one more request, so runs are slower. An ad whose details fail is charged as a plain ad.

## `startUrls` (type: `array`):

Searches copied from facebook.com/ads/library in your browser. The filters in the link (country, active status, media type, dates) are used.

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

Only active ads, only ads that stopped, or both.

## `mediaType` (type: `string`):

Only ads with this kind of media.

## `adType` (type: `string`):

All ads, or only ads about social issues, elections or politics (these carry spend and impressions ranges).

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

Match the search terms as an exact phrase instead of any order.

## `dateFrom` (type: `string`):

Only ads that were shown on or after this date (YYYY-MM-DD), whatever day they started. This is the Ad Library's own date filter.

## `dateTo` (type: `string`):

Only ads that were shown on or before this date (YYYY-MM-DD).

## `proxyConfiguration` (type: `object`):

Residential proxies are the default and recommended: Meta refuses most datacenter IPs past the first page of results.

## Actor input object example

```json
{
  "advertisers": [
    "https://www.facebook.com/Gymshark/",
    "https://www.facebook.com/sephora/"
  ],
  "onlyNewAds": true,
  "country": "US",
  "maxAdsPerQuery": 50,
  "includeAdDetails": false,
  "activeStatus": "active",
  "mediaType": "all",
  "adType": "all",
  "exactPhrase": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (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 = {
    "advertisers": [
        "https://www.facebook.com/Gymshark/",
        "https://www.facebook.com/sephora/"
    ],
    "country": "US",
    "maxAdsPerQuery": 50,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("deepmine/facebook-ads-monitor").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 = {
    "advertisers": [
        "https://www.facebook.com/Gymshark/",
        "https://www.facebook.com/sephora/",
    ],
    "country": "US",
    "maxAdsPerQuery": 50,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("deepmine/facebook-ads-monitor").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 '{
  "advertisers": [
    "https://www.facebook.com/Gymshark/",
    "https://www.facebook.com/sephora/"
  ],
  "country": "US",
  "maxAdsPerQuery": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call deepmine/facebook-ads-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,deepmine/facebook-ads-monitor"
        }
    }
}
```

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/oON6fTdVabAK5S4pk/builds/IzqsaxBSidxLXbKw1/openapi.json
