# Facebook Ad Library Scraper (`sarel-data/facebook-ad-library-scraper`) Actor

Scrape ads from the Meta Ad Library by keyword, Page ID or Ad Library URL. Ad copy, CTA, links, image and video URLs, dates, platforms, plus optional EU reach and payer/beneficiary data.

- **URL**: https://apify.com/sarel-data/facebook-ad-library-scraper.md
- **Developed by:** [Sarel Data](https://apify.com/sarel-data) (community)
- **Categories:** Social media, Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 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.

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 Ad Library Scraper: ads, EU reach and political spend for $0.60 per 1,000

Get ads out of the [Meta Ad Library](https://www.facebook.com/ads/library/) by keyword, by advertiser Page, or from any Ad Library URL. Each ad comes with its copy, CTA, landing-page links, image and video URLs, run dates and platforms. Turn on one option and EU-delivered ads also get the reach and payer/beneficiary data Meta publishes under the DSA. **$0.60 per 1,000 ads, and EU transparency data costs nothing extra.** You don't need a Facebook login, cookies or a Meta developer token.

### Features

- **Three ways to search:** keywords (`searchTerms`), advertiser Page IDs (`pageIds`, which also accepts Ad Library URLs containing `view_all_page_id=`), or any Ad Library search URL (`startUrls`). Filters in a URL are used exactly as they appear.
- **All the Ad Library filters:** country (or `ALL`), ad category (all, political/issue, housing, employment, financial products), active/inactive/both, media type (image, video, meme, image and meme, none), any-word or exact-phrase matching, platforms (Facebook, Instagram, Audience Network, Messenger, Threads), date window, and sorting by relevance or most impressions.
- **Strict start-date filter:** `strictStartDate` drops long-running ads that started before your date window.
- **EU transparency and advertiser details** (`includeAdDetails`): EU total reach, reach broken down by age, gender and country, targeted location/age/gender audience, payer and beneficiary, page spend by disclaimer, page verification, Instagram username and follower count, the page's About text, and UK transparency data.
- **Political and issue ad data:** `spend`, `currency`, `impressionsText`, `reachEstimate`, `bylines` ("paid for by") and `disclaimerLabel`, whenever Meta publishes them for an ad.
- **Full creative data:** body text, title, caption, link description, CTA text and type, every landing-page URL, image URLs, video URLs (HD where available), video previews, and per-card data for carousel and DPA ads.
- **Deduplication** by Ad Library ID within an input and, optionally, across inputs (`deduplicateAcrossInputs`, on by default).
- **Reliable runs:** The scraper reads the JSON the Ad Library loads for itself, not the visible page layout, so cosmetic redesigns don't break it. Meta's first-visit 403 challenge is handled. Real blocks are retried with a fresh browser session and proxy IP (`maxRequestRetries`, default 6). A search with no results finishes cleanly with 0 items. Scrolling stops when Meta reports there are no more pages.
- **Run monitoring:** Every run writes a `RUN_STATS` record (inputs, successes, failures, items, success rate) to the key-value store.

### Use cases

- **Competitor monitoring:** schedule a run with `pageIds` and `activeStatus: "active"`, then compare `adArchiveId` between runs to spot new creatives.
- **Creative research:** find hooks and formats in a niche, filtered to video only, sorted by most impressions.
- **Agency prospecting:** find brands advertising in a country and see how long their ads have run (`startDate` / `endDate`).
- **EU reach analysis:** reach by age, gender and country for ads shown in the EU, plus who paid for them.
- **Political and issue ad research:** spend and impression ranges and "paid for by" disclaimers.

### Input example

```json
{
  "searchTerms": ["running shoes"],
  "pageIds": ["15087023444"],
  "country": "US",
  "adType": "all",
  "activeStatus": "active",
  "mediaType": "video",
  "platforms": ["facebook", "instagram"],
  "sortBy": "total_impressions",
  "maxItems": 200,
  "includeAdDetails": false,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

**To find a Page ID,** open the brand's ads in the Ad Library and copy the number after `view_all_page_id=` in the address bar. You can also paste the whole URL.

`maxItems` limits the number of ads saved **per input** (search term, Page or URL). The default is 100 and the maximum is 100,000.

### Output example

You get one dataset item per ad. Here is a real item from a run on Nike's Page (`page:15087023444`, country `US`). URLs are shortened, and the arrays are cut to their first entry.

```json
{
  "adArchiveId": "1016451784037642",
  "adId": null,
  "collationId": null,
  "collationCount": null,
  "pageId": "15087023444",
  "pageName": "Nike",
  "pageUrl": "https://www.facebook.com/nike/",
  "pageProfilePictureUrl": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/509357337...",
  "pageLikeCount": 39494291,
  "pageCategories": ["Sportswear"],
  "isActive": true,
  "startDate": "2025-06-20T07:00:00.000Z",
  "endDate": "2025-08-28T07:00:00.000Z",
  "startDateUnix": 1750402800,
  "endDateUnix": 1756364400,
  "totalActiveTimeSeconds": null,
  "publisherPlatforms": ["FACEBOOK", "INSTAGRAM"],
  "displayFormat": "DPA",
  "adText": "Get the gear that goes hard on and off the field.",
  "title": "Nike Air Monarch IV",
  "linkDescription": null,
  "caption": "NIKE.COM",
  "ctaText": "Shop now",
  "ctaType": "SHOP_NOW",
  "linkUrl": "https://www.nike.com/us/en_us",
  "linkUrls": ["https://www.nike.com/us/en_us"],
  "imageUrls": ["https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/509359383..."],
  "videoUrls": [],
  "videoPreviewUrls": [],
  "cards": [
    {
      "title": "Nike Air Monarch IV",
      "body": "Get the gear that goes hard on and off the field.",
      "linkUrl": "https://www.nike.com/t/air-monarch-iv-mens-workout-shoes-ext...",
      "linkDescription": null,
      "ctaText": "Shop Now",
      "imageUrl": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/509359383...",
      "videoUrl": null
    }
  ],
  "categories": ["UNKNOWN"],
  "bylines": null,
  "disclaimerLabel": null,
  "currency": null,
  "spend": null,
  "reachEstimate": null,
  "impressionsText": null,
  "targetedOrReachedCountries": [],
  "details": null,
  "containsSensitiveContent": false,
  "containsDigitallyCreatedMedia": false,
  "snapshotUrl": "https://www.facebook.com/ads/library/?id=1016451784037642",
  "searchCountry": "US",
  "searchInput": "page:15087023444",
  "scrapedAt": "2026-10-27T23:30:05.003Z"
}
```

This is a commercial US ad, so `spend`, `impressionsText` and `details` are `null`. For political and issue ads, Meta fills in `spend`, `currency`, `impressionsText`, `reachEstimate`, `bylines` and `disclaimerLabel`.

When `includeAdDetails` is `true`, `details` is an object with these keys:
`euTotalReach`, `euTransparency` (`targetsEu`, `locationAudience`, `genderAudience`, `ageAudience`, `totalReach`, `ageCountryGenderReachBreakdown`), `ukTransparency`, `payerBeneficiary`, `pageSpend`, `pageVerification`, `instagramUsername`, `instagramFollowers` and `pageAbout`.

### Pricing

**$0.60 per 1,000 ads saved**, plus the standard Apify start fee of $0.00005. EU transparency and advertiser details are **included at no extra charge**. You pay only for ads saved to the dataset. Retries, blocked attempts and empty searches don't add results.

The table compares the main Facebook Ad Library actors on Apify Store (competitor prices collected on 2026-10-28):

| Actor | Price per 1,000 ads | EU reach / transparency data |
|---|---|---|
| **This actor** | **$0.60** | **Included** |
| curious_coder – Facebook Ad Library Scraper | $0.75 + $0.00005 per start | n/a |
| memo23 – FB Ad Library · Meta Ads · Reach | $0.50 (FREE tier) down to $0.30 (GOLD+), $0.005 per start | +$1/1K ("additional-data") |
| constructive_calm – Facebook Ad Library Scraper Pro | $0.49 | +$0.25/1K details, +$1/1K advertiser resolves |
| practicaltools – Facebook Ad Library Scraper | $0.50 + $0.00005 per start | +$0.30/1K EU-transparency details |
| thodor – FB & IG Ad Library Scraper | $4.99 (FREE) to $2.99 (GOLD+), $0.00005 per start | n/a |
| whoareyouanas – Meta Ad Library Scraper | $10 | n/a |
| jy-labs – Meta Ad Library Multi-Search | $10 (FREE) to $9, $0.02 per start | n/a |

"n/a" means the competitor's listing doesn't give a separate price for EU data.

### FAQ

**Do I need a Facebook account or an API token?**
No. The actor reads only the public Ad Library. Nothing behind a login is accessed.

**How do I get EU reach data?**
Set `includeAdDetails: true` and set `country` to an EU country. Meta publishes EU reach only for ads delivered in the EU.

**Why are `spend` and `impressionsText` empty?**
Meta publishes spend and impression ranges only for political and issue ads. Commercial ads outside the EU/UK have no spend or reach data.

**Can I search several brands or keywords in one run?**
Yes. Each search term, Page ID and URL is a separate input with its own `maxItems` limit. With `deduplicateAcrossInputs` on, an ad that appears in several inputs is saved only once.

**What does `collationCount` mean?**
Meta groups near-identical ads together. `collationCount` shows how many versions sit behind an item, and each version has its own Ad Library ID.

**Which proxy should I use?**
The default is residential Apify Proxy, which gives the most stable results on large runs and with high concurrency.

**Can I scrape a single ad URL (`?id=`)?**
Not yet. Single-ad URLs are rejected with an error. Use a search URL, keyword or Page ID instead.

### Limitations

- **Media URLs expire.** Facebook CDN links are signed and stop working after a few days, so download the files you want to keep.
- **Result caps.** The Ad Library stops paginating at some point per search. For large pulls, split by country, date window, media type or Page.
- **Spend and reach exist only where Meta publishes them:** political/issue ads, and EU/UK transparency data via `includeAdDetails`.
- **`includeAdDetails` is slower.** It makes one extra request per ad. If the details dialog can't be opened, the run saves search data only and logs a warning.
- **Single-ad URLs (`?id=`) aren't supported.** `startUrls` must be `facebook.com/ads/library` search URLs.
- **`pageIds` must be numeric IDs** or URLs that contain one. Vanity page names such as `nike` aren't resolved.

# Actor input Schema

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

Keywords to search, one per line. Each term is a separate search.

## `pageIds` (type: `array`):

Numeric Page IDs (or Ad Library URLs containing view_all_page_id=). Returns every ad the Page runs.

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

Paste any search URL from facebook.com/ads/library. Filters in the URL are used as-is and override the filters below.

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

Two-letter country code (US, GB, FR, DE...) or ALL.

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

Ad category

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

Active status

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

Media type

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

Keyword matching

## `platforms` (type: `array`):

Leave empty for all platforms.

## `startDateMin` (type: `string`):

Facebook's own date filter: keeps ads that were delivering on or after this date (YYYY-MM-DD). Combine with "Filter by start date" for ads that *started* in the range.

## `startDateMax` (type: `string`):

Keeps ads that were delivering on or before this date (YYYY-MM-DD).

## `strictStartDate` (type: `boolean`):

Also drop ads whose start date is outside the range above (e.g. long-running ads that started earlier).

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

Sort

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

Upper limit of ads saved for each search term / Page / URL. You are only charged for ads saved.

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

Adds EU reach (total and by age/gender/country), targeting, payer/beneficiary, page spend and Instagram info. Most useful with EU countries. Slower: one extra request per ad.

## `deduplicateAcrossInputs` (type: `boolean`):

Save an ad only once even if several inputs return it.

## `directFirst` (type: `boolean`):

Connect without a proxy first and use the proxy below only after a block. Turn off to send all traffic through the proxy.

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

Fallback proxy. By default the scraper connects directly (cheapest) and only switches to this proxy when Facebook blocks a request. Residential is recommended as the fallback.

## `maxConcurrency` (type: `integer`):

Parallel browsers

## `maxRequestRetries` (type: `integer`):

Each retry uses a fresh browser session and proxy IP.

## Actor input object example

```json
{
  "searchTerms": [
    "nike"
  ],
  "country": "US",
  "adType": "all",
  "activeStatus": "active",
  "mediaType": "all",
  "searchType": "keyword_unordered",
  "strictStartDate": false,
  "sortBy": "relevancy",
  "maxItems": 100,
  "includeAdDetails": false,
  "deduplicateAcrossInputs": true,
  "directFirst": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 3,
  "maxRequestRetries": 6
}
```

# Actor output Schema

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

Ads from the Meta Ad Library (dataset items).

## `runStats` (type: `string`):

Summary of inputs processed, ads saved and blocks retried.

# 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"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("sarel-data/facebook-ad-library-scraper").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"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("sarel-data/facebook-ad-library-scraper").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"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call sarel-data/facebook-ad-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sarel-data/facebook-ad-library-scraper"
        }
    }
}
```

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/ourRLgO1d4EFZTJVG/builds/S8FH0TWGMQbmsx3aI/openapi.json
