# Facebook Ads Library Scraper - Meta Ad Library API + Creatives (`vonsensey/facebook-ads-library-scraper-meta-ad-api`) Actor

Scrape the Facebook Ads Library (Meta Ad Library) by keyword, advertiser or pasted URL for every ad creative, including the dynamic and carousel variants others drop. Filters are enforced and reported, result caps are exact, and failed or filtered rows are free.

- **URL**: https://apify.com/vonsensey/facebook-ads-library-scraper-meta-ad-api.md
- **Developed by:** [Blackcube Agency AB](https://apify.com/vonsensey) (community)
- **Categories:** Marketing, Social media, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 ads

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/platform/actors/running/actors-in-store#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 Scraper — Meta Ad Library API + Creatives

**More from this account:** [YouTube Transcript Suite](https://apify.com/vonsensey/youtube-transcript-scraper) · [Website Contact & Email Suite](https://apify.com/vonsensey/website-contact-email-extractor) · [Career Site & ATS Jobs Suite](https://apify.com/vonsensey/career-page-job-postings-scraper-api) · [Google News Suite](https://apify.com/vonsensey/google-news-scraper-api) · [Keyword Research Suite](https://apify.com/vonsensey/google-keyword-ideas-scraper) · [Shopify Store Intelligence Suite](https://apify.com/vonsensey/shopify-store-leads-scraper) · [eBay Data Suite](https://apify.com/vonsensey/ebay-scraper-api) · [Amazon Reviews Suite](https://apify.com/vonsensey/amazon-reviews-scraper-api) · [Reddit](https://apify.com/vonsensey/reddit-scraper-posts-comments-api) · [Vinted](https://apify.com/vonsensey/vinted-scraper-api)

**You are charged for one thing: an ad that matched what you asked for and came back with something you can use.** Filtered ads, ads Meta has withheld, empty results, bad inputs and failed requests are all free rows that tell you why. There is no start fee, so a run that returns nothing costs nothing.

Search the Facebook Ads Library and Meta Ad Library by keyword, by advertiser, or by pasting a URL straight from your browser. Get the ad copy, the headline, the call to action, the destination link, the advertiser, how long the ad has been running — and the actual image and video files.

***

### Why ads come back empty from other tools

About **42% of the ads in the Ad Library are dynamic** — the creative rotates across several variants instead of being one fixed image or video. Tools that read only the ad's main creative slot find nothing on those, so they hand you a row with no picture, no video, and a headline like `{{product.name}}`.

Measured on a single 500-ad run of this Actor, on live data:

| | Share of the 500 ads |
|---|---|
| Ads that returned **at least one image or video** | **100%** |
| Same ads, counting only the main creative slot | **58.2%** |
| Ads that were dynamic (multi-variant) | 41.8% |
| Ads that returned real ad copy | 98.8% |
| Ads that returned a destination link | 98.8% |

Every variant comes back as its own entry with its own media, copy, headline and link, and the row still counts as **one ad on your bill**.

Meta also ships unfilled template slots — `{{product.name}}`, `{{product.brand}}` — as if they were real ad text. Those are removed rather than passed through, and the row is flagged so a pipeline can tell "the advertiser wrote no headline" apart from "Meta didn't fill this one in".

***

### What you get for every ad

| | |
|---|---|
| **Creative** | ad copy, headline, caption, call-to-action text and type, destination link |
| **Media** | every image and video, highest quality available, unwatermarked where Meta offers both, plus video poster frames |
| **Variants** | each dynamic-creative variant with its own copy, headline, link and media |
| **Timing** | start date, whether it is still running, and `daysRunning` as a number you can sort on |
| **Scale signal** | how many near-identical ads Meta groups with this one |
| **Advertiser** | page name, page ID, page URL, profile picture, page likes, page categories |
| **Distribution** | which of Facebook, Instagram, Messenger, Threads, WhatsApp and Audience Network it runs on |
| **Flags** | AI-generated media, sensitive content, gating status, format |

Identical field names on every row, every format, every country.

#### About spend, reach and impressions

**Meta publishes these only for political and social-issue ads.** On ordinary commercial ads they do not exist — across 500 live commercial ads, the fill rate for spend, reach and impressions was **0%**.

You will see other tools advertise them anyway. Here those fields are `null` on commercial ads and populated on political ones, because a fabricated number silently corrupts whatever you filter or model with it. If you are researching political advertising you get them; if you are researching a supplement brand, nobody has them.

***

### Filters are enforced, and the run tells you what they removed

Every filter is applied to **every** ad before it can be delivered or billed, and the run reports how many ads each one removed. A filter that cannot be honoured is never silently dropped — a date you type in a shape we cannot read comes back as a row saying so, instead of quietly widening your search.

- `startedAfter` / `startedBefore` — ISO dates
- `minDaysRunning` — the usual way to find proven winners
- `minCollationCount` — only creatives the advertiser is scaling
- `mediaType` — checked against the media actually extracted
- `publisherPlatforms`, `advertiserNameContains`, `textContains`, `onlyDynamicCreative`, `excludeGated`

**Ads removed by your filters are never charged.**

***

### Caps are exact

`maxAds: 500` delivers 500 ads and stops. Not 530. The run report states how many ads it saw, how many it delivered, and how many each filter removed, so the arithmetic always closes.

### Nothing is truncated silently

Every run says why it stopped:

| `stopReason` | What it means |
|---|---|
| `endOfResults` | Meta had nothing more. This is a complete answer. |
| `capReached` | Your own `maxAds` stopped it — more ads exist. |
| `pageBudgetReached` | The run's page budget stopped it — more ads exist. |
| `sourceStopped` | Meta stopped answering — some ads were not reachable. |

`isComplete` is `true` only in the first case. No other tool in this category publishes this, and it is the difference between "that is all there is" and "that is all we got".

***

### Monitoring: pay only for what is new

Set `onlyNew: true` and schedule the run. Ads already delivered for that input are skipped and **not charged** unless something about them changed — whether it is still live, when it ended, or how many siblings Meta groups with it.

**Rewritten ad copy does not count as a change.** Advertisers reword constantly, and billing you for that would re-charge a whole dataset for a typo fix.

Each input keeps its own memory, so a second scheduled task over an overlapping brand list still returns everything on its first run.

***

### How to scrape the Facebook Ads Library by keyword

Put your terms in `searchTerms` and pick `countries` — the Ad Library is country-scoped, so the country decides which ads exist at all. Use `exactPhrase` for a phrase rather than loose keywords.

```json
{
  "searchTerms": ["protein powder", "creatine"],
  "countries": ["US"],
  "activeStatus": "active",
  "maxAds": 500
}
```

### How to scrape a competitor's Facebook ads

Open the advertiser in the Ad Library, copy the URL from your address bar, and paste it in. The search term, country, active status and media type in that URL are used as-is — you do not have to re-enter them.

```json
{
  "adLibraryUrls": ["https://www.facebook.com/ads/library/?active_status=all&country=US&view_all_page_id=590393174322587"],
  "maxAds": 1000
}
```

Or give `advertiserPageIds` a list of numeric page IDs to track a whole competitor set in one run, with every row attributed to the advertiser it came from.

### How to find winning Facebook ads

A creative still running after months is one the advertiser keeps paying for. Sort or filter on `daysRunning`, and use `minCollationCount` to keep only creatives being scaled rather than one-off tests.

```json
{
  "searchTerms": ["skincare"],
  "countries": ["US"],
  "activeStatus": "active",
  "minDaysRunning": 60,
  "minCollationCount": 3,
  "maxAds": 300
}
```

### How to scrape Instagram ads

Meta runs one library across its platforms. Filter with `publisherPlatforms: ["INSTAGRAM"]` to keep only ads that run on Instagram; each row also lists every platform the ad appears on.

### How to download Facebook ad creatives

Each row carries direct media URLs — highest quality Meta offers, unwatermarked where there is a choice, with poster frames for video. Take the `media` array and fetch the URLs. Dynamic ads carry each variant's media separately, with the variant it belongs to.

### How to use it as a Meta Ad Library API

Start a run and read the dataset over HTTP like any other endpoint. The official Meta Ad Library API needs identity verification and returns political and social-issue ads only; this covers commercial advertising, needs no key or approval, and returns a flat, consistent schema. Output is available as JSON, CSV or Excel.

***

### Output

Two row types, told apart by `type`.

**`ad`** — the thing you asked for, and the only thing charged.

```json
{
  "type": "ad",
  "adArchiveId": "3604351659703170",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=3604351659703170",
  "pageName": "Paleovalley",
  "pageId": "590393174322587",
  "pageLikeCount": 195792,
  "pageCategories": ["Food & Drink"],
  "title": "BOGO Bone Broth Protein",
  "body": "If you've been waiting to try Paleovalley's bone broth protein…",
  "ctaText": "Shop now",
  "ctaType": "SHOP_NOW",
  "linkUrl": "https://paleovalley.com/offers/grass-fed-bone-broth-protein-bogo-a",
  "displayFormat": "DCO",
  "isDynamicCreative": true,
  "hasUnresolvedPlaceholder": true,
  "mediaCount": 2,
  "media": [{ "kind": "video", "url": "https://…", "previewImageUrl": "https://…", "cardIndex": 0 }],
  "variants": [{ "index": 0, "title": "BOGO Bone Broth Protein", "body": "2 bags for $60…", "media": [] }],
  "startDate": "2026-07-09",
  "endDate": null,
  "isActive": true,
  "daysRunning": 39,
  "collationCount": 1,
  "publisherPlatforms": ["FACEBOOK", "INSTAGRAM", "MESSENGER", "THREADS"],
  "spend": null,
  "currency": null,
  "impressions": null,
  "reachEstimate": null,
  "searchTerm": "protein powder",
  "searchCountry": "US",
  "scrapedAt": "2026-08-17T13:52:00.000Z"
}
```

**`notice`** — free, and always says why: `filteredOut`, `gatedByMeta`, `noResolvableCreative`, `noResults`, `invalidInput`, `notAttempted`, `requestFailed`, `blocked`.

`endDate` is `null` while an ad is still running. Meta returns a far-future placeholder there, which is not a real end date, so `isActive` and `daysRunning` carry the meaning instead.

A full completeness and billing report is written to the key-value store as `RUN_REPORT`.

***

### Pricing

**From $1.00 per 1,000 ads**, tiering down with your Apify plan. No start fee. No monthly minimum.

You are charged for delivered ads only. Filtered, gated, empty, failed and never-attempted rows are free, and with `onlyNew` a repeat run charges only for ads that are new or that changed.

Nothing is ever added to your search. If you supply Ad Library URLs or advertiser IDs, those are the only things searched. A run started with no input at all returns a small 10-ad sample so you can see the output shape, and says so in the log — it can never run up a bill on your behalf.

### Input reference

Every field is documented in the Input tab. The proxy setting should be left at its default — it is the only configuration this Actor is tested on.

### Notes

- Please raise anything wrong or missing on the **Issues** tab.
- The Ad Library is public transparency data. No login, no cookies and no account are required or accepted.
- **Unofficial.** This Actor is not affiliated with, endorsed by, or connected to Meta Platforms, Inc., Facebook or Instagram. All trademarks belong to their owners. Use the data in line with Meta's terms and the law that applies to you.

> **Run it without configuring anything** — [See which ads are running for a term](https://apify.com/vonsensey/facebook-ads-library-scraper-meta-ad-api/examples/ads-running-for-a-search-term), a ready-made example you can start as-is or copy.

### Use cases

- **See what a competitor is spending on.** Every live creative an advertiser is running, including the variants most scrapers drop.
- **Research a category before you buy media.** Search by keyword to see the angles already saturating a market.
- **Track a campaign over time.** A scheduled run captures creatives before they are rotated out.
- **Build a swipe file.** Creative, copy, run dates and platform on every row.

### Run it on a schedule

A one-off pull answers a question; a schedule answers it every day without you. Open **Schedules** in the Apify Console, point a cron at this Actor, and the dataset keeps filling on its own — no server, no cron box, no babysitting. Everything here is built to be re-run: you are billed per ad delivered, so a scheduled run that finds nothing new costs nothing.

### FAQ

#### Do I need a Meta or Facebook account?

No. The Ad Library is public and this reads it directly.

#### Are carousel and dynamic creatives included?

Yes. Those variants are the ones most scrapers silently drop, and they are often the best-performing ones.

#### Can I search by advertiser instead of keyword?

Yes — keyword, advertiser or a pasted Ad Library URL.

#### Which countries can I filter to?

Any country the Ad Library covers, selected per run.

***

Something wrong, or a field you need that is missing? Open an issue on the **Issues** tab — it is read and it gets fixed. If this saved you time, a rating on the Store page helps the next person find it.

# Actor input Schema

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

Keywords to search the Ad Library for, one per line. Example: protein powder. Leave empty if you are using Ad Library URLs or advertiser IDs instead — nothing is added to your search.

## `adLibraryUrls` (type: `array`):

Paste Ad Library URLs straight from your browser. The search term, country, active status and media type in the URL are used as-is, so you do not have to re-enter them below.

## `advertiserPageIds` (type: `array`):

Scrape every ad from specific advertisers. Accepts a numeric Facebook page ID, or an Ad Library URL containing view\_all\_page\_id.

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

Two-letter country codes to search in, or ALL. The Ad Library is country-scoped, so this changes which ads exist.

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

Hard cap on billable ads for the whole run. This is exact: the run delivers this many and stops. A call that omits this field gets a small 10-ad sample rather than a large bill; set it explicitly for a real run, or clear it in the Console for no cap.

## `maxAdsPerTarget` (type: `integer`):

Optional per-search cap, so one broad keyword cannot consume the whole run.

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

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

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

Restrict to image or video ads. This is checked against the creative actually extracted, so it is never silently ignored.

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

Keep only ads shown on at least one of these platforms.

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

Search for the search term as an exact phrase rather than as unordered keywords.

## `startedAfter` (type: `string`):

ISO date, e.g. 2026-01-31. Ads that started before this are dropped and never charged. An unreadable date is reported as a row, never silently ignored.

## `startedBefore` (type: `string`):

ISO date, e.g. 2026-06-30. Ads that started after this are dropped and never charged.

## `minDaysRunning` (type: `integer`):

Keep only ads that have been running at least this long. The usual way to find proven winners: an ad still live after 30+ days is one the advertiser keeps paying for.

## `minCollationCount` (type: `integer`):

Keep only creatives Meta groups with at least this many near-identical siblings. A high number means the advertiser is scaling that creative.

## `advertiserNameContains` (type: `string`):

Case-insensitive substring match on the advertiser name.

## `textContains` (type: `string`):

Case-insensitive substring match on the ad copy, headline or caption. Dynamic-creative variants are searched too.

## `onlyDynamicCreative` (type: `boolean`):

Keep only ads whose creative rotates across multiple variants.

## `excludeGated` (type: `boolean`):

Gated ads are never billed either way; this also removes their free notice rows from the output.

## `includeEmptyCreatives` (type: `boolean`):

Off by default. Ads carrying no media, no text and no link are normally reported as free notice rows rather than billed.

## `onlyNew` (type: `boolean`):

For scheduled monitoring. Remembers what this input already returned and bills only ads that are new, or whose live status, end date or sibling count changed. Rewritten ad copy does not count as a change, so you are never re-billed for a typo fix.

## `concurrency` (type: `integer`):

How many search terms to walk at once.

## `docId` (type: `string`):

Advanced. Only needed if Meta ships a new build of the Ad Library and this Actor has not been updated yet.

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

Leave this exactly as it is. The default is the only configuration this Actor is tested and supported on; changing it will make runs return nothing.

## Actor input object example

```json
{
  "searchTerms": [
    "protein powder"
  ],
  "adLibraryUrls": [],
  "advertiserPageIds": [],
  "countries": [
    "US"
  ],
  "maxAds": 100,
  "activeStatus": "active",
  "mediaType": "all",
  "publisherPlatforms": [],
  "exactPhrase": false,
  "onlyDynamicCreative": false,
  "excludeGated": false,
  "includeEmptyCreatives": false,
  "onlyNew": false,
  "concurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `runReport` (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": [
        "protein powder"
    ],
    "countries": [
        "US"
    ],
    "maxAds": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vonsensey/facebook-ads-library-scraper-meta-ad-api").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": ["protein powder"],
    "countries": ["US"],
    "maxAds": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

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

```

## MCP server setup

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

```

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/nQE8kC1ctDaybcQul/builds/zJg663zybRsxpbHdW/openapi.json
