# Facebook Ads Library Monitor (`zerrouki-samir/facebook-ads-library`) Actor

Scrape the public Meta Ad Library and watch competitor ads over time. Every row includes daysRunning. On scheduled re-runs, ads are labelled new, ongoing, or ended. No Meta token.

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

## Pricing

from $0.30 / 1,000 ad scrapeds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Facebook Ads Library Monitor

A **competitor-ad monitor** for the public Meta Ad Library, priced like a dump.

Every ad row includes **`daysRunning`**. On a scheduled re-run with the same `monitorKey`,
each ad is labelled **`new`**, **`ongoing`**, or **`ended`**. No Meta token. No official API.

That combination is the point: you can watch a competitor’s ads week after week without
paying a luxury SKU for a field the library already shows.

### What you get

One flat dataset row per ad:

```json
{
  "adArchiveId": "1234567890",
  "pageName": "Example Brand",
  "isActive": true,
  "startDate": "2026-01-15",
  "daysRunning": 48,
  "bodyText": "Free shipping this week.",
  "ctaText": "Shop now",
  "imageUrls": ["https://..."],
  "watchStatus": "ongoing",
  "firstSeenAt": "2026-03-01T12:00:00Z",
  "lastSeenAt": "2026-04-12T12:00:00Z"
}
```

- **`daysRunning`** — days from the library start date to today (or to the end date if the ad has stopped).
- **`watchStatus`** — only when you set `monitorKey`. Compare this run to the previous snapshot for that key.
- **Media URLs** — image and video addresses the library already publishes. This Actor does not host files.
- **Spend / impressions** — included when the public library shows them. Many ads omit them.

Empty searches and blocked loads write an **error row** and the run still succeeds. They are not charged.

### 🤖 Use with AI agents

Connected to the [Apify MCP server](https://mcp.apify.com)? Ask for this Actor by name:
`zerrouki-samir/facebook-ads-library`

Copy this into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

```text
I want to monitor competitor ads in Meta's public Ad Library using the Apify Actor
`zerrouki-samir/facebook-ads-library`.

Use it when I need: a cheap dump of currently running (or stopped) ads for a keyword,
Facebook Page, or pasted Ad Library URL, plus how long each ad has been running.
On a schedule, pass the same monitorKey so each ad is labelled new, ongoing, or ended
against the previous run. No Meta access token is required.

Don't use it when: I need targeting/demographics, the official ads_archive API, hosted
video files, or a crawl of the landing page HTML.

How to call it: pass searchQueries (keywords), and/or pageUrls, and/or adsLibraryUrls.
Set country to a two-letter ISO code (default US), activeStatus to active / inactive / all,
optional mediaType (all / image / video / meme / image_and_meme / none),
and maxAds as a safety cap per task (default 50, max 1000). Optional monitorKey is a
stable string per watch, e.g. nike-us-weekly.

Each successful ad is one dataset row with adArchiveId, page name, copy, media URLs,
daysRunning, and optional watchStatus. Empty searches return an error row and are not
charged. Residential proxies are required on the Apify platform.

Start with this input:
{
  "searchQueries": ["running shoes"],
  "country": "US",
  "activeStatus": "active",
  "maxAds": 50,
  "monitorKey": "running-shoes-us-weekly"
}

Ask me which brands or keywords to watch, then run the Actor and summarise new vs ended
ads. If I asked for creatives, show bodyText, ctaText, and the image/video URLs.
```

**Things to ask your agent for:**

- *Which of this competitor’s ads are new since last week, and how long has each been running?*
- *Dump active ads for this Page URL and give me the copy plus image URLs.*
- *Watch this keyword in the US every Monday and tell me what stopped.*

A machine-readable description of this Actor is published at
[apify.com/zerrouki-samir/facebook-ads-library.md](https://apify.com/zerrouki-samir/facebook-ads-library.md)
once the Actor is live on the Store.

### Input

Provide **at least one** of `searchQueries`, `pageUrls`, or `adsLibraryUrls`.

```json
{
  "searchQueries": ["running shoes"],
  "pageUrls": ["https://www.facebook.com/examplebrand"],
  "adsLibraryUrls": [],
  "country": "US",
  "activeStatus": "active",
  "maxAds": 50,
  "monitorKey": "running-shoes-us-weekly"
}
```

| Field | Type | Default | Notes |
|---|---|---|---|
| `searchQueries` | array of strings | — | Keywords in the public Ad Library. |
| `pageUrls` | array of strings | — | Advertiser Facebook Page URLs. |
| `adsLibraryUrls` | array of strings | — | Full URLs copied from `facebook.com/ads/library`. Country and filters already in the URL are kept. |
| `country` | string | `US` | ISO country code for keyword and Page searches. Ignored when a pasted library URL already has a country. |
| `activeStatus` | string | `active` | `active`, `inactive`, or `all`. |
| `mediaType` | string | `all` | Keyword and Page searches only. `all`, `image`, `video`, `meme`, `image_and_meme`, or `none`. Pasted library URLs keep the filter already in the URL. |
| `maxAds` | integer | `50` | Safety cap **per task** (each keyword, Page, or library URL). Maximum 1000. |
| `monitorKey` | string | — | Optional. Same key on scheduled re-runs labels ads `new` / `ongoing` / `ended`. |

### Output

**Dataset — one flat row per ad** (or one error row per failed/empty task):

| Field | Description |
|---|---|
| `adArchiveId`, `adLibraryUrl` | Library identity and a link back to the public ad |
| `collationId`, `collationCount` | Meta’s grouping of near-duplicate creatives |
| `pageId`, `pageName`, `pageUrl`, `pageLikeCount`, `pageProfilePictureUrl` | Advertiser Page |
| `isActive`, `startDate`, `endDate`, `daysRunning` | Delivery window |
| `platforms`, `categories`, `targetedCountries`, `entityType` | Where and how the library classifies the ad |
| `containsDigitalCreatedMedia`, `isAaaEligible` | AI-media flag and EU AAA eligibility when present |
| `bodyText`, `title`, `ctaText`, `ctaType`, `caption`, `linkUrl`, `linkDescription`, `byline`, `displayFormat` | Creative |
| `imageUrls`, `videoUrls`, `cards` | Media addresses and carousel cards |
| `extraLinks`, `extraTexts` | Extra creative links/text when the library sends them |
| `spend`, `impressions`, `reachEstimate`, `currency` | Present only when the library shows them |
| `aaaInfo`, `fevInfo` | EU/political transparency blobs when present |
| `watchStatus`, `firstSeenAt`, `lastSeenAt` | Filled when `monitorKey` is set |
| `query`, `country` | Which search produced the row |
| `error` | Set on empty searches and task failures; the run still succeeds |

**Key-value store** (named `fb-ads-monitor`) — when `monitorKey` is set, a snapshot under
`snapshot-<monitorKey>` so the next run can diff. The run-default store does not survive
schedules; this named store does.

### Why a monitor, not another dump

The public Ad Library is already scraped by several Store Actors. This one is built for
**re-runs**:

| | Typical dump | This Actor |
|---|---|---|
| One-shot keyword scrape | Yes | Yes |
| `daysRunning` on every row | Rare / expensive | Always |
| `new` / `ongoing` / `ended` on a schedule | Rare | When `monitorKey` is set |
| Meta access token | Sometimes | Never |
| Empty search | Often fails the run | Error row, not charged |

### Pricing

Pay-per-event. There is **no start fee**.

| Event | Free tier | Charged |
|---|---|---|
| `ad-scraped` | **$0.0005** ($0.50 / 1k) | Once per successfully stored ad |

Gold is about **$0.0003** per ad. Error rows, empty searches, duplicates, and `ended` rows
inferred from a previous snapshot are **not charged**.

Residential proxy and compute are billed by Apify on top of PPE. A 200-ad platform run used
about 3 MB of residential traffic and finished in under two minutes.

### How it works

1. The Actor opens the **public** Ad Library the way a person does (keyword, Page, or a URL you pasted).
2. It reads the ad records the page already renders and paginates until `maxAds`.
3. Rows are normalised to the dataset schema. `daysRunning` is computed from the library dates.
4. If `monitorKey` is set, this run is compared to the last snapshot for that key.

No login. No official Graph `ads_archive` API. Residential proxies are **required** on the
Apify platform; local runs can hit the library directly.

### Limitations

- The public library page changes. This is a maintenance product, not a one-shot scrape.
- Spend, impressions, and some creatives are missing for many ads — that is how the library publishes them.
- Video files are not downloaded or hosted; you get URLs only.
- Demographics / targeting breakdowns are out of scope.
- Landing-page HTML is not crawled.
- `maxAds` is a cap, not a guarantee the library has that many ads.

### Support

Report problems through the **Issues** tab on the Actor page.

### License

MIT

# Actor input Schema

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

Keywords to search in the public Ad Library (e.g. a competitor brand or product).

## `pageUrls` (type: `array`):

Advertiser Page URLs. Ads are collected from that Page in the Ad Library.

## `adsLibraryUrls` (type: `array`):

Full URLs copied from facebook.com/ads/library. Country and filters already in the URL are kept.

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

ISO country code used for keyword and Page searches. Ignored when you paste a full Ad Library URL that already has a country.

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

Whether to collect ads that are currently running, stopped, or both.

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

Filter keyword and Page searches to a creative format. Ignored when you paste a full Ad Library URL that already has a media\_type.

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

Safety cap per keyword, Page, or library URL. The Actor stops that task when this many unique ads are stored.

## `monitorKey` (type: `string`):

Optional. When set, this run is compared to the previous run with the same key. Each ad is labelled new, ongoing, or ended, and daysRunning is filled. Use one key per scheduled watch (e.g. nike-us-weekly). Requires a named key-value store that survives between runs.

## Actor input object example

```json
{
  "searchQueries": [
    "running shoes"
  ],
  "pageUrls": [],
  "adsLibraryUrls": [],
  "country": "US",
  "activeStatus": "active",
  "mediaType": "all",
  "maxAds": 50
}
```

# Actor output Schema

## `ads` (type: `string`):

One row per ad: identity, copy, media URLs, daysRunning, and optional watchStatus.

# 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 = {
    "searchQueries": [
        "running shoes"
    ],
    "pageUrls": [],
    "adsLibraryUrls": [],
    "country": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("zerrouki-samir/facebook-ads-library").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 = {
    "searchQueries": ["running shoes"],
    "pageUrls": [],
    "adsLibraryUrls": [],
    "country": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("zerrouki-samir/facebook-ads-library").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 '{
  "searchQueries": [
    "running shoes"
  ],
  "pageUrls": [],
  "adsLibraryUrls": [],
  "country": "US"
}' |
apify call zerrouki-samir/facebook-ads-library --silent --output-dataset

```

## MCP server setup

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

```

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/k1WVIlncFBUXEfbcK/builds/AHjRMP44EayqAU5oq/openapi.json
