# Facebook Ad Library Full Scraper (`fanndev/facebook-ad-library-scraper`) Actor

Pull complete ad records from Meta's public Ad Library: full ad copy, headline, CTA, destination URL, every image and video creative including carousel cards, flight dates, days on air, platforms and advertiser. Search by keyword or advertiser, any country. Paginates past the first 30.

- **URL**: https://apify.com/fanndev/facebook-ad-library-scraper.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 results

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 Ad Library Full Scraper

Pull complete ad records out of Meta's public Ad Library — the full ad copy, the
headline, the call-to-action, the destination URL, every image and video file
including carousel cards, the flight dates, how many days the ad has been on air,
which Meta surfaces it runs on, and who is paying for it.

Search by **keyword** (everything a niche is running) or by **advertiser**
(everything one page is running), in any country the Ad Library covers.

No login, no cookies to paste, no proxy required.

### What you get

One row per ad:

| Field | Example |
| --- | --- |
| `pageName`, `pageId`, `pageUrl` | `Nordstrom Rack`, `89516513179`, … |
| `adText` | `Buy online and pick up in store for free! Get up to 70% off …` |
| `title`, `linkDescription`, `caption` | `Fast & Free Pickup`, …, `nordstromrack.com` |
| `ctaText`, `ctaType` | `Learn more`, `LEARN_MORE` |
| `linkUrl` | where the click goes |
| `displayFormat` | `VIDEO`, `IMAGE`, `DCO`, … |
| `imageUrls`, `videoUrls`, `thumbnailUrls` | direct CDN files |
| `cards[]` | per-card copy for carousel ads |
| `startDate`, `endDate`, `runningDays`, `isActive` | `2026-09-10T07:00:00Z`, …, `9`, `true` |
| `publisherPlatforms` | `["FACEBOOK","INSTAGRAM","MESSENGER"]` |
| `pageLikeCount`, `pageCategories` | `2518041`, `["Clothing (Brand)"]` |
| `adLibraryUrl` | open the same ad in a browser |
| `totalMatchingAds` | how many ads the search matches in total |

Plus a `RUN_SUMMARY` row with ads collected, how many are active, how many
distinct advertisers and the median days on air.

### Two things worth knowing before you buy

**1. Spend and impressions exist only for political and issue ads.**
`spendText`, `impressionsText` and `targetedCountries` are populated when
`adType` is `political_and_issue_ads`. For ordinary commercial ads Meta does not
publish them anywhere, at any price, so they come back empty. That is Meta's
policy, not a gap in this actor — and no scraper can fill it. Everything else in
the table above is available for commercial ads.

**2. Demographic targeting is not published for commercial ads either.**
The "target wilayah/demografi" you may be looking for is available as
`targetedCountries` on political and issue ads. Age/gender breakdowns are shown
in Meta's own EU transparency reports for a subset of ads and are not part of the
Ad Library search response. This actor returns what the Ad Library actually
serves and leaves the rest null rather than inventing a plausible number.

### Advertiser input takes a numeric page id

The Ad Library addresses advertisers by numeric page id, not by vanity slug. Open
the advertiser in the Ad Library and copy the URL — it contains
`view_all_page_id=<id>` — or paste the id on its own. A plain
`facebook.com/<slug>` URL is rejected with a log line rather than silently
returning nothing.

### Advertiser searches include the influencer ads a brand pays for

Search one advertiser and a few rows will carry somebody else's name in
`pageName` — a creator. Those are branded-content ads: the creator's page posts
them, the brand pays. The row tells you both:
`isBrandedContent: true`, `sponsorPageName: "Nordstrom Rack"`. Nothing has
leaked; this is part of what that advertiser is running, and it is usually the
part a competitor analysis wants to see.

### Input example

```json
{
  "searchTerms": ["running shoes", "protein powder"],
  "countries": ["US", "GB"],
  "adType": "all",
  "activeStatus": "active",
  "mediaType": "video",
  "maxAdsPerQuery": 120,
  "minRunningDays": 14,
  "exportFormats": ["csv"]
}
```

That runs four searches (2 terms × 2 countries), keeps video creatives that have
been on air at least two weeks, and drops a CSV in the key-value store alongside
the dataset.

### How many ads can it get?

Facebook server-renders the first 30 ads of a search. Beyond that, this actor
replays Facebook's own pagination query, 30 ads at a time, up to
`maxAdsPerQuery`. A search matching 26,000 ads will happily give you 1,000 of
them; set the cap to what you actually want to pay for.

If Facebook reshuffles its JavaScript bundles mid-run and the pagination query
cannot be located, the actor keeps the 30 server-rendered ads, logs a warning and
carries on. You get a smaller honest answer, never a failed run.

### Common uses

- **Competitor monitoring** — track every ad a competitor is running, and how
  long each has survived. A creative on air for 90 days is one that works.
- **Swipe files** — collect the copy and creative of a whole niche in one run.
- **Landing-page research** — `linkUrl` shows the exact destination of every ad.
- **Political transparency** — spend and impression ranges, per ad, per country.

### Notes

- Dynamic-creative ads legitimately contain Meta template tokens such as
  `{{product.name}}` in `adText`. That is the published text, not a parse error.
- `videoUrls` and `imageUrls` are signed CDN links and expire after a while.
  Download what you need during or shortly after the run.
- `endDate` is filled in for running ads too — it is the current end of the
  flight, not a stop date. Read it with `isActive`.
- The Ad Library answers the first request of a session with HTTP 403 and a
  verification challenge. This actor solves it automatically; nothing is required
  from you.

# Actor input Schema

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

One keyword or phrase per line. Each is searched in every country you list below, so 3 keywords x 2 countries = 6 searches.

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

Pull every ad from a specific advertiser. Paste the Ad Library URL of the advertiser (the one containing view\_all\_page\_id=...) or just the numeric page id. A plain facebook.com/<slug> URL will NOT work here: the Ad Library addresses advertisers by numeric id only, and this actor tells you rather than silently returning nothing.

## `countries` (type: `array`):

ISO country codes to search. The Ad Library is country-scoped, so a term with no ads in one country legitimately returns zero there.

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

'All ads' covers commercial advertising. The political & issue category is the only one where Facebook publishes spend and impression ranges - for every other category those fields come back empty, by Meta's policy and not because they were missed.

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

Whether to return ads currently running, ads that have stopped, or both.

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

Unordered matches the words in any order; exact phrase matches the phrase as typed.

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

Restrict to image, video or meme creatives.

## `publisherPlatforms` (type: `array`):

Restrict to ads delivered on these Meta surfaces. Leave empty for all of them.

## `contentLanguages` (type: `array`):

Two-letter language codes, e.g. en, id, es. Leave empty for any language.

## `startDateFrom` (type: `string`):

YYYY-MM-DD. Filters on the day the ad started running.

## `startDateTo` (type: `string`):

YYYY-MM-DD.

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

Facebook server-renders the first 30 ads; anything beyond that is fetched by replaying its own pagination query, 30 at a time. Keep this low while you are tuning a search.

## `onlyActive` (type: `boolean`):

Drop ads Facebook reports as no longer active.

## `onlyWithVideo` (type: `boolean`):

Keep only creatives that carry at least one video file.

## `minRunningDays` (type: `integer`):

Long-running ads are the ones an advertiser kept paying for. Ads with no usable flight dates are dropped rather than admitted on a guess.

## `adTextContains` (type: `string`):

Case-insensitive substring match across the ad body, headline, caption and link description.

## `emitSummary` (type: `boolean`):

Append one RUN\_SUMMARY record: ads collected, how many are active, how many advertisers and the median days on air.

## `exportFormats` (type: `array`):

Also write the results to the key-value store in these formats. The dataset is always produced regardless.

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

Optional. The Ad Library's HTTP 403 is a verification challenge this actor solves on its own, not an IP block, so the direct connection works. Use a proxy only to spread a very large run across IPs, or to see a country's library from inside that country.

## Actor input object example

```json
{
  "searchTerms": [
    "running shoes"
  ],
  "countries": [
    "US"
  ],
  "adType": "all",
  "activeStatus": "active",
  "searchType": "keyword_unordered",
  "mediaType": "all",
  "publisherPlatforms": [],
  "contentLanguages": [],
  "maxAdsPerQuery": 60,
  "onlyActive": false,
  "onlyWithVideo": false,
  "emitSummary": true,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Every ad collected this run, plus the run summary and any error rows.

# 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"
    ],
    "countries": [
        "US"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/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": ["running shoes"],
    "countries": ["US"],
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/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": [
    "running shoes"
  ],
  "countries": [
    "US"
  ]
}' |
apify call fanndev/facebook-ad-library-scraper --silent --output-dataset

```

## MCP server setup

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