# Facebook Ads Library Scraper - New and Stopped Ads Monitor (`neverempty/facebook-ads-library-monitor`) Actor

For marketers and agencies tracking competitors on Facebook and Instagram: Meta Ad Library ads by keyword or page, with days running, active status, text, links, images and videos. Monitor mode returns only ads that are new since your last run, plus the ones that stopped.

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

## Pricing

from $2.50 / 1,000 ad returneds

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 Library Scraper - New and Stopped Ads Monitor

Track your competitors' Facebook and Instagram ads from the Meta Ad Library, by keyword or by page. Every ad comes with **how many days it has been running**, whether it is **still active**, its text, headline, call to action, landing page, images and videos. Turn on **monitor mode** and each run returns only the ads that are **new since your last run**, plus the ads that **stopped** since then.

### Who it is for

- **Marketers and media buyers** who want to know the day a competitor launches a new creative, and which ads they turned off.
- **Agencies** reporting on several brands: one run checks up to 30 keywords and pages.
- **Creative strategists** looking for winning ads: ads that have been running for many days are the ones an advertiser keeps paying for. Sort the dataset by `runDays` to see them first.

### What you get for every ad

| Field | What it holds |
|---|---|
| `adArchiveId`, `adLibraryUrl` | The Ad Library ID and a link that opens the ad in the Ad Library |
| `pageName`, `pageId`, `pageUrl`, `pageLikeCount`, `pageCategories` | The advertiser |
| `isActive`, `startDate`, `endDate`, `runDays` | Active or stopped, the day it started, the day it stopped (stopped ads only), and the number of days it has run (up to today for active ads) |
| `lastShownDate` | The last day Meta reports the ad as shown |
| `platforms` | facebook, instagram, messenger, audience_network, threads |
| `body`, `title`, `caption`, `linkDescription`, `ctaText`, `ctaType`, `linkUrl` | The ad copy, headline, call to action and landing page |
| `imageUrls`, `videoUrls`, `videoPreviewImageUrls`, `cards` | Media of single, carousel, dynamic creative and catalog ads |
| `collationCount`, `collationId` | How many ads use the same creative (1 when the creative is used once) |
| `euReach`, `reachedCountries` | Reach published by Meta for ads shown in the EU |
| `spend`, `impressions`, `currency` | Ranges published by Meta for issue, election and political ads |
| `pageAdsInRun`, `pageActiveAdsInRun`, `pageActiveAdCount` | Per-advertiser counts: ads of this page in the run, active ones, and the page's full number of active ads when the page was read to the end |
| `changeType` | With the monitor on: `new` or `stopped` |

### What the monitor returns

1. **First run**: reads the ads of each search and page, compares and remembers them. No ad row is returned; the ads compared are charged as ad checks (see below), and one free row per search says the watch started.
2. **Every later run**: returns only
   - **new ads** (`changeType: "new"`): ads it has not seen before whose start date is no earlier than two days before the watch began (the Ad Library lists new ads with a delay and gives the start day, not the hour), and
   - **stopped ads** (`changeType: "stopped"`, `isActive: false`): ads that were active at the last check and are not active now.
3. A run with no change returns one free row per search (`status: "no-changes"`), so a schedule never fails silently.

One memory is shared by all searches and pages of a watch and across runs, so an ad that shows up for two of your keywords is returned once, not twice. Schedule it daily (or hourly) in Apify and connect the dataset to Slack, email, Google Sheets or a webhook to get a launch-and-stop feed of your competitors' ads.

### Why this one

- **New and stopped ads, not the full list again.** A plain scrape returns the full list every time and leaves the comparing to you; the monitor does the comparing and returns only the changes.
- **Days running on every row**, counted to today for active ads. Meta's own "end date" on an active ad is only the last day it was shown; this Actor keeps that in `lastShownDate` and never reports an active ad as ended.
- **Media for dynamic creative and catalog ads.** Image and video URLs come from the ad's cards as well, so dynamic creative (DCO) and catalog (DPA) ads keep their media. Template text such as `{{product.name}}` is not passed off as ad copy.
- **Per-page counts** on every row, so you can see how many active ads each advertiser runs.
- **Clear answers instead of empty runs**: a keyword with no ads, a page that does not exist, a country code Meta does not accept, or a page of results that could not be read each come back as a free row that says what happened.

### Input

| Field | What it does |
|---|---|
| `searchTerms` | Words to search, one per line (a brand, product or competitor). An Ad Library search link also works. |
| `pages` | Advertisers to watch: a numeric page ID or an Ad Library link with `view_all_page_id`. |
| `country` | Two-letter country code (US, GB, DE…) or ALL. |
| `activeStatus` | Active ads only (default), stopped ads only, or both. |
| `adType` | All ads, or one Ad Library category (issues and politics, housing, employment, credit, financial products). |
| `mediaType` | All media, images, videos, memes, images and memes, or ads with no image or video. |
| `searchType` | All words in any order, or the exact phrase. |
| `sortBy` | The Ad Library's relevance order, or its recently started order. |
| `maxItems` | How many ads to read for each search or page (1 to 5,000; 100 when empty). |
| `monitor` | On = return only new and stopped ads since the last run. |
| `watchName` | Optional label to keep separate memories for separate watches. |
| `resetMonitoringState` | Forget the watch and start again (the next run is a first run: ad checks, no ad rows). |

### Input example

```json
{
    "searchTerms": ["nike", "running shoes"],
    "pages": ["https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&view_all_page_id=15087023444"],
    "country": "US",
    "activeStatus": "active",
    "maxItems": 200,
    "monitor": true
}
```

### Output example

```json
{
    "status": "ok",
    "changeType": "new",
    "adArchiveId": "1234567890123456",
    "pageName": "Example Brand",
    "isActive": true,
    "startDate": "2026-10-24",
    "endDate": null,
    "runDays": 2,
    "platforms": ["facebook", "instagram"],
    "body": "Our new running shoe is here.",
    "title": "Shop the new collection",
    "ctaText": "Shop now",
    "linkUrl": "https://www.example.com/",
    "imageUrls": ["https://scontent.xx.fbcdn.net/..."],
    "videoUrls": [],
    "collationCount": 1,
    "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1234567890123456",
    "pageId": "100000000000000",
    "pageActiveAdsInRun": 12,
    "country": "US"
}
```

### How much it costs

You pay per ad returned, plus a small fee per run that reads the Ad Library and compares or returns its ads. With the monitor on, each ad read and compared with the watch's memory is also charged as an ad check, on every monitor run including the first run and the run after a reset; new and stopped ads are charged as ad rows on top of that. Rows that explain why something was not returned are free.

| Event | When | Price |
|---|---|---|
| Ad returned | each ad row | $3.50 per 1,000 on the Free plan, lower on paid plans (down to $2.50) |
| Ad checked | monitor on: each ad compared | $0.70 per 1,000 on the Free plan, lower on paid plans (down to $0.50) |
| Run start | once per run that read a list and compared or returned its ads (not the first run of a watch) | $0.05 |

Set a maximum cost per run in Apify and the Actor stops cleanly at that limit. Ads it could not return are not remembered, so the next run returns them; with the monitor on, ads it did not get to compare are not charged and not remembered, and nothing is reported as stopped from that search in that run.

### Good to know

- **Stopped ads** are found in two ways: an ad that reads as stopped in the list, or (with "Active ads only") a remembered active ad that is missing from the full active list. The second way needs the whole list read, so set `maxItems` above the number of active ads of the search or page; when a run reads only part of the list, it does not report anything as stopped from it.
- **Pages** are taken by numeric page ID or an Ad Library link with `view_all_page_id`. A page-name link (facebook.com/brand) cannot be resolved without logging in, which this Actor does not do; open the page in the Ad Library and copy its link.
- `euReach` and `reachedCountries` are filled for ads shown in the EU; `spend` and `impressions` for issue, election and political ads. Elsewhere Meta does not publish them, and the fields are `null`.
- **Order**: "Recently started" uses the Ad Library's own newest order, which puts recent ads near the top but is not strictly by start date. With the monitor on, a new ad is found wherever it sits in the part of the list that was read, so a higher `maxItems` catches more.
- The Ad Library returns about 10 ads per request and no total count, so large searches take longer. Each search is read up to `maxItems` (1 to 5,000).
- The Actor opens the public Ad Library in a real browser through Apify's residential proxy. It does not log in, use cookies or solve image challenges; if Meta shows a login or check page, the run stops and says so without charging.
- This is an unofficial tool, not affiliated with, endorsed by or sponsored by Meta Platforms, Inc. Facebook, Instagram and Meta are trademarks of Meta Platforms, Inc.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Words to search in the Meta Ad Library, one per line (for example a brand, a product or a competitor's name). An Ad Library search link (https://www.facebook.com/ads/library/?…\&q=…) also works. Empty together with Pages = the example search "nike".

## `pages` (type: `array`):

Advertisers to watch, one per line: a numeric page ID (15087023444), an Ad Library link that contains view_all_page_id=…, or a facebook.com/profile.php?id=… link. A page name link (facebook.com/nike) cannot be turned into an ID without logging in: open the page in the Ad Library and copy the view_all_page_id link instead. Up to 30 search terms and pages together.

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

Ads shown in this country: a two-letter code such as US, GB, DE or BR, or ALL for every country. Empty = ALL. Reach numbers per country (EU reach) are only published for ads shown in the EU.

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

Which ads to list. Empty = active ads only. With the monitor on, "Active ads only" also reports an ad as stopped when it leaves the full active list.

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

The Ad Library category to search. Empty = all ads. Spend and impressions ranges are published only for issue, election and political ads.

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

Only ads with this kind of media. Empty = all media.

## `searchType` (type: `string`):

How the Ad Library matches your search terms. Empty = all words in any order. Has no effect on pages.

## `sortBy` (type: `string`):

The order in which ads are read and returned. Empty = the Ad Library's own order. With the monitor on, ads are always read in the recently started order.

## `maxItems` (type: `integer`):

How many ads to read for each search term or page (1 to 5000). Empty = 100. With the monitor on, this is how far down the list each run compares; to report stopped ads, set it higher than the number of active ads of the search or page.

## `monitor` (type: `boolean`):

On = every run is charged per ad compared (ad check); the first run remembers the ads it reads and returns no ad row, and every later run returns only ads that are new since then, plus remembered active ads that stopped (changeType "stopped"). Off (empty) = every run returns all ads it reads.

## `watchName` (type: `string`):

Optional label that keeps separate memories for separate watches with the same filters (letters, numbers, dot, dash or underscore, up to 40). Empty = one shared memory per country and filter set.

## `resetMonitoringState` (type: `boolean`):

On = forget what this watch remembered and start over (the run is a first run again: ads compared are charged as ad checks, no ad row is returned). Empty = keep the memory.

## Actor input object example

```json
{
  "searchTerms": [
    "nike"
  ],
  "country": "US",
  "maxItems": 20
}
```

# Actor output Schema

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

One row per ad: Ad Library ID and link, page name, ID and URL, start date, end date, days running, active or stopped, platforms, body, headline, call to action, destination URL, image and video URLs, how many ads share the creative, EU reach, and per-page counts for the run. With the monitor on, only new ads (changeType new) and ads that stopped (changeType stopped). A search with no ads, a run with no change, a page that could not be read or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "searchTerms": [
        "nike"
    ],
    "country": "US",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/facebook-ads-library-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 = {
    "searchTerms": ["nike"],
    "country": "US",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/facebook-ads-library-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 '{
  "searchTerms": [
    "nike"
  ],
  "country": "US",
  "maxItems": 20
}' |
apify call neverempty/facebook-ads-library-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/facebook-ads-library-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/MaYfLhIqCsUhD3z60/builds/IKUhgqj56FvLKHEux/openapi.json
