# Facebook Ads Library Scraper — Meta Ad Creatives & Advertisers (`tortuga/facebook-ads-library-scraper`) Actor

Scrape ads from the public Meta (Facebook) Ad Library by keyword, advertiser page or Ad Library URL: ad text, creatives, CTA, platforms, dates, active status and political spend.

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

## Pricing

from $3.50 / 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 Ads Library Scraper — Meta Ad Creatives & Advertisers

Scrape ads from the public **Meta Ad Library** (facebook.com/ads/library) by **keyword**, by **advertiser** (Facebook page ID, page URL or name) or straight from an **Ad Library URL** you copied from your browser. You get the ad text, title, link, call to action, image and video URLs, carousel cards, the platforms the ad runs on (Facebook, Instagram, Messenger, Audience Network, Threads), start and end dates, whether it is still running, and how many versions it has. For ads about social issues, elections or politics you also get spend and impression ranges and "Paid for by", and with **Include ad details** the age/gender split and the regions the ad reached.

No Facebook account, no login, no Meta API token: the Actor reads the same public pages any visitor sees.

### What data does Facebook Ads Library Scraper extract?

| Field | Example | Notes |
|---|---|---|
| `adArchiveId` | `1228023139489736` | Ad Library ID of the ad |
| `adLibraryUrl` | `https://www.facebook.com/ads/library/?id=1228023139489736` | Link to the ad |
| `pageId`, `pageName`, `pageUrl` | `15087023444`, `Nike`, `https://www.facebook.com/nike/` | The advertiser's Facebook page |
| `pageLikeCount`, `pageCategories`, `pageProfilePictureUrl` | `39504313`, `["Sportswear Store"]` | As shown on the ad |
| `isActive` | `true` | Still running |
| `startDate`, `endDate` | `2026-09-02`, `2026-10-05` | `endDate` only once the ad has stopped (for running ads Meta just shows "today") |
| `publisherPlatforms` | `["facebook", "instagram", "messenger"]` | Where the ad is shown |
| `displayFormat` | `IMAGE`, `VIDEO`, `CAROUSEL`, `DPA`, `DCO` | Creative type |
| `body`, `title`, `caption`, `linkDescription` | ad text, headline, domain | For catalog ads whose top-level text is a template (`{{product.name}}`), the first card's text |
| `linkUrl`, `ctaText`, `ctaType` | `https://www.nike.com/...`, `Shop now`, `SHOP_NOW` | Landing page and button |
| `imageUrls` | `["https://scontent...jpg"]` | Full-size creative images |
| `videos` | `[{"hdUrl", "sdUrl", "previewImageUrl"}]` | Video files and thumbnail |
| `cards` | `[{"title", "body", "linkUrl", "imageUrl", "ctaText", ...}]` | Carousel / catalog cards |
| `extraTexts`, `extraLinks` | | Alternative texts and links of dynamic ads |
| `collationId`, `collationCount` | `726473194573350`, `2` | Ads that share this creative ("N ads use this creative and text") |
| `categories` | `["POLITICAL"]` | Meta's ad category |
| `paidForBy`, `disclaimerLabel` | `Facebook, Inc.`, `PAID_FOR_BY` | Political and issue ads |
| `currency`, `spend`, `impressions`, `reachEstimate` | `{"text": "$800K - $900K", "lower": 800000, "upper": 900000}` | Political and issue ads; ranges as Meta shows them, plus numbers |
| `containsSensitiveContent`, `containsAiGeneratedMedia` | `false` | Flags shown by Meta |
| `searchInput` | `running shoes` | Which search or advertiser produced the ad |

With **Include ad details** (one extra request per ad, separate `details` event):

| Field | Notes |
|---|---|
| `demographicDistribution` | Political/issue ads: share of impressions by age range and gender |
| `regionDistribution` | Political/issue ads: share of impressions by region (state / province), largest first |
| `euTotalReach`, `euTargeting`, `euReachBreakdown` | Ads shown in the EU (Digital Services Act data): total EU reach, targeted locations, gender and ages, reach by country, age and gender |
| `ukTotalReach`, `ukTargeting`, `ukReachBreakdown` | The same for the UK when Meta shows it |
| `payerBeneficiary` | EU ads: who paid for the ad and who benefits |
| `advertiser` | Page alias, category, verification, likes, Instagram username, followers and verification, page "about" text |
| `pagePoliticalSpendLifetime` | Pages with political ads: lifetime spend per "Paid for by" disclaimer |

### How to use Facebook Ads Library Scraper

1. Enter **Search terms** (e.g. `running shoes`), **Advertiser pages** (e.g. `15087023444`, `nike` or `https://www.facebook.com/nike`) and/or **Ad Library URLs** copied from facebook.com/ads/library.
2. Pick filters for search terms and advertisers: **Country**, **Ad category** (all ads or issues, elections and politics), **Active status**, **Media type**, **Platforms**, **Impressions from / until** dates, **Exact phrase**, **Sort by**. Ad Library URLs keep the filters already in the URL.
3. Set **Max ads** (and optionally **Max ads per search**), then run. Download the results as JSON, CSV or Excel, or read them through the API.

### Input example

```json
{
  "searchTerms": ["running shoes"],
  "pageIds": ["15087023444", "https://www.facebook.com/AutoScout24"],
  "startUrls": [
    { "url": "https://www.facebook.com/ads/library/?active_status=all&ad_type=political_and_issue_ads&country=US&q=senate&search_type=keyword_unordered&media_type=all" }
  ],
  "country": "US",
  "activeStatus": "active",
  "platforms": ["instagram"],
  "maxItems": 200,
  "maxItemsPerQuery": 50
}
```

### Output example

```json
{
  "adArchiveId": "1249575052243232",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1249575052243232",
  "pageId": "248986568895243",
  "pageName": "Speechify",
  "pageUrl": "https://www.facebook.com/getspeechify/",
  "pageLikeCount": 99994,
  "isActive": false,
  "startDate": "2022-06-01",
  "endDate": "2022-09-30",
  "publisherPlatforms": ["facebook", "instagram", "audience_network", "messenger"],
  "displayFormat": "DCO",
  "caption": "itunes.apple.com",
  "linkUrl": "http://itunes.apple.com/app/id1209815023",
  "ctaText": "Download",
  "ctaType": "DOWNLOAD",
  "collationCount": 1,
  "categories": ["POLITICAL"],
  "currency": "USD",
  "spend": { "text": "$800K - $900K", "lower": 800000, "upper": 900000 },
  "impressions": { "text": ">1M", "lower": 1000000 },
  "searchInput": "senate",
  "demographicDistribution": [{ "ageRange": "25-34", "female": 0.15, "male": 0.15, "unknown": 0 }],
  "regionDistribution": [{ "region": "California", "share": 0.11 }, { "region": "Texas", "share": 0.09 }]
}
```

### How to scrape all ads of a Facebook page (competitor ads)?

Put the advertiser in **Advertiser pages**: the numeric page ID (in the Ad Library URL as `view_all_page_id=...`, or on the page's "Page transparency" panel), the page URL or just its name as in the URL (`nike`). Names are looked up with the Ad Library's own search box, so a page that has never advertised may not be found; the numeric ID always works. Set **Active status** to "Active and inactive" to get the ad history too.

### Does it work without login or a Meta API token?

Yes. The Ad Library is a public transparency tool that anyone can browse without an account, and the Actor reads it the same way: no login, no cookies of yours, no Graph API access token (the official API only covers political ads and needs an identity-verified developer account). It does not solve CAPTCHAs; if Meta asks for one, the Actor switches to another IP.

### How many ads can I get per search, and how fast?

Meta's site returns 30 ads for the first page of a search and 10 per "load more" request. The Actor pages through a search until it runs out or reaches **Max ads** / **Max ads per search**; in testing it read 900 ads from three searches in about a minute, and 2,500 ads of a single search in 10 minutes, with no failed requests (one search is read page after page, about 4 ads per second; several searches run in parallel). Very broad keywords can match tens of thousands of ads; narrow them with country, dates, platforms or media type, or use advertiser pages.

### How much does it cost to scrape the Facebook Ad Library?

You pay per ad saved ($3.50 per 1,000 ads), plus $1.50 per 1,000 ads when **Include ad details** is on, plus the Apify platform usage of your run. Usage is small: about 2-5 MB of residential proxy traffic plus a little compute and storage per 1,000 ads, roughly $0.03-0.05 per 1,000 ads in our tests (about $0.10 more per 1,000 ads with details).

**Proxy note:** keep the default **residential** proxy. Meta serves the Ad Library page to datacenter IPs but rate-limits the "load more" requests coming from them, so a datacenter run only gets the first 30 ads of each search. The Unblocker is not needed.

### Monitor changes on a schedule

Turn on **Only new or changed items** and the Actor remembers which ads earlier runs of the same input returned: each run outputs only ads that are new, or ads whose status changed (`isActive` / `endDate`, i.e. the ad stopped running). Unchanged ads are dropped before anything is saved, so they are not charged (nor are their details fetched). Save the input as a task, for example a competitor's page ID with **Active status** "Active ads" and **Max ads** 100, then add a Schedule to it in Apify Console (Schedules → Create new, daily). Every output ad carries `changeType` (`new` or `changed`), and changed ones carry `previousValues`, e.g. `{"isActive": true, "endDate": null}` for an ad that has just stopped. With "Active ads", stopped ads simply leave the list; choose "Active and inactive" if you want them reported as `changed`. The Ad Library orders results by relevance, not by date, so keep **Max ads** above the number of ads the search matches (advertiser pages and narrow searches work best); otherwise older ads moving into the checked window can show up as new. Use **Monitoring key** to keep several schedules on the same search separate.

### Legal and data notes

The Actor collects only what Meta publishes in its public Ad Library for transparency: ads, the advertiser pages that ran them, and the spend/reach data Meta discloses. It does not log in, does not access anything behind a login, and does not collect data about people who saw or interacted with the ads. The verified address, phone and e-mail that some political advertisers disclose are left out. You are responsible for using the data in line with Meta's terms and the laws that apply to you (for example GDPR if your use involves personal data).

# Actor input Schema

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

Keywords to search in the Ad Library, one search per line (e.g. 'running shoes'). Matches ad text and advertiser names, like the search box on facebook.com/ads/library.

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

Advertisers whose ads you want: a Facebook page ID (e.g. 15087023444), a page URL (https://www.facebook.com/nike), a page name as in its URL (nike) or an Ad Library advertiser URL (...view_all_page_id=...). Names are looked up with the Ad Library's own search; numeric IDs are the most reliable.

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

Ad Library URLs copied from your browser (https://www.facebook.com/ads/library/?...). All filters in the URL are kept as they are. A single-ad URL (?id=...) returns just that ad.

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

Country where the ads ran (two-letter code), or ALL. Political and issue ads need a country.

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

All ads, or only ads about social issues, elections or politics (these carry spend, impressions and 'Paid for by').

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

Only ads running now (active), only stopped ads (inactive), or both.

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

Only ads with this kind of creative.

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

Only ads shown on these Meta platforms. Leave empty for all.

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

Only ads that had impressions on or after this date (YYYY-MM-DD), the Ad Library's 'Impressions by date' filter.

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

Only ads that had impressions on or before this date (YYYY-MM-DD).

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

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

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

Ad Library order: most relevant first (default) or most impressions first.

## `includeDetails` (type: `boolean`):

Open each ad's 'See ad details' panel (one extra request per ad, charged as a separate 'details' event): age and gender split and regions for political and issue ads, EU reach with age/gender/country breakdown and payer/beneficiary for ads shown in the EU, and advertiser info (Instagram username and followers, verification, page about text).

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

Stop after this many ads in total (across all searches). Keeps cost predictable.

## `maxItemsPerQuery` (type: `integer`):

Optional cap per search term, advertiser or URL, so one big advertiser does not use up the whole run.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. For ads: new ads, and ads whose isActive / endDate changed (stopped running; use Active status 'Active and inactive' to see those). With this on, Max ads is the number of ads checked each run; keep it above the number of ads the search matches (results are ordered by relevance, not date).

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input.

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

Apify Proxy. Residential (the default) is required for more than the first 30 ads of a search: Meta answers the Ad Library page from datacenter IPs but rate-limits its 'load more' requests from them. Residential traffic is small (about 2-5 MB per 1,000 ads, roughly $0.02-0.04 at $8/GB). Datacenter works for runs that need at most 30 ads per search. No Unblocker needed.

## Actor input object example

```json
{
  "searchTerms": [
    "running shoes"
  ],
  "pageIds": [
    "15087023444"
  ],
  "country": "ALL",
  "adType": "all",
  "activeStatus": "active",
  "mediaType": "all",
  "exactPhrase": false,
  "sortBy": "relevance",
  "includeDetails": false,
  "maxItems": 100,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searchTerms": [
        "running shoes"
    ],
    "pageIds": [
        "15087023444"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/facebook-ads-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": ["running shoes"],
    "pageIds": ["15087023444"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

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

```

## MCP server setup

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