# Meta Ad Library Scraper (`lergassy/meta-ad-library-scraper`) Actor

Scrape Facebook and Instagram ads from Meta's Ad Library by keyword or advertiser page: ad text, headline, call to action, link, image and video URLs, format, platforms and run dates. One flat row per ad, with a hard cap. Export data, run via API, schedule runs.

- **URL**: https://apify.com/lergassy/meta-ad-library-scraper.md
- **Developed by:** [Matvey](https://apify.com/lergassy) (community)
- **Categories:** Agents, MCP servers
- **Stats:** 3 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.70 / 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/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

**Meta Ad Library Scraper** turns Meta's public Ad Library into clean rows: for any keyword or any advertiser's page, every ad running on **Facebook, Instagram, Messenger, Audience Network and Threads** — headline, body text, call to action, destination link, image and video URLs, format, platforms and run dates, **one flat row per ad**.

**$1 per 1,000 ads. The cap you set is checked before every request, so a run stops where you told it to and never bills you past it. Rows that explain a block or an empty result are free.**

![Meta Ad Library scraper output: one row per Facebook and Instagram ad with advertiser, headline, body text, call to action and format](https://raw.githubusercontent.com/lergassy/apify-actor-assets/main/meta-ad-library-scraper/meta-ad-library-scraper-output-table.png)

*One run on the keyword «nike»: 30 ads in 7 seconds, 0.68 MB of traffic.*

![Watch the demo: 30 Facebook and Instagram ads in 6 seconds, no login and no API review](https://raw.githubusercontent.com/lergassy/apify-actor-assets/main/meta-ad-library-scraper/meta-ad-library-scraper-video.png)

*The whole thing, start to finish: a keyword in, 30 ads out in 6 seconds for $0.001, and a run summary that shows the cap was honoured. [Watch the 84-second demo](https://youtu.be/8uEwsG2lJJQ).*

Paste a keyword or a page ID, press Start, and download JSON, CSV or Excel — or pull the rows through the API into Google Sheets, n8n, Make, a database or an AI agent.

### What is Meta Ad Library Scraper?

Meta publishes every ad running on its platforms in the [Ad Library](https://www.facebook.com/ads/library/), and offers an official API only for political and social-issue ads, behind an access review. This Actor is the **Ad Library API alternative** for everything else: competitor and creative research, ad-copy swipe files, agency reporting, e-commerce and dropshipping research, lead generation from active advertisers, brand protection, and political-ad monitoring.

It reads the same public data the Ad Library page shows any visitor, without a login and without an access review.

| You give | You get |
|---|---|
| **Keywords** — `nike`, `meal kit`, `crypto wallet` | every matching ad, from every advertiser |
| **Page IDs** | every ad of one advertiser, their whole current campaign set |
| **Country, status, format, platform, date range** | the same filters the Ad Library page offers, applied by Meta itself |
| **Political and social-issue shelf** | the ads where Meta also publishes **spend, impressions and reach** |

### What other Ad Library scrapers get wrong — and what this one does instead

The most-used Actor in this niche carries a 4.2 rating across 57 reviews and eight pages of open issues. Those complaints fall into three groups, and each one is a design decision here.

| What buyers write on other Actors | Meta Ad Library Scraper |
|---|---|
| "onlyTotal ignored — **127K items returned instead of 583, $95 wasted**" | `maxAds` is counted here and checked **before every request**, not after rows arrive. Ask for 30, get 30 |
| "Instead of returning six results it kept scraping for an hour without signalling. **I spent $17 for nothing**" | One cap, one meaning. There is no second limit field that quietly does nothing |
| "Analyzed 3000+ ads despite less than 200 being active" · "Filter on Active ads not working" · "Mediatype prefilter issue (video)" | Meta accepts a date range and a media type and then ignores them — measured 19 September 2026: a search for ads started after 1 September returned 28 of 30 that started earlier, one in March 2025, and a video-only search returned 14 ads with no video. Here every row is re-checked on our side, an ad that fails a filter is **never charged**, and the run summary shows `scanned` against `filteredOut` so you can see how much of Meta's answer did not match |
| "**Meta says 34k results but scraper only returned 2.5k**" | Every run writes a summary with what you asked for, what was delivered, how many requests it took and **why it stopped**: `MAX_REACHED`, `END_OF_RESULTS`, `NO_RESULTS` or `BLOCKED` |
| "Actor gives incomplete data" · "Returning no ads results" | Meta answers a datacentre address with **HTTP 200 and an empty body** — it looks exactly like "no ads matched". This Actor detects that and says `blocked` in plain words instead of handing you an empty dataset |
| "Spend, reach and ad text never being pulled" · "ad spend around 100 billion, I doubt that is real" | Spend, impressions and reach exist **only** on the political and social-issue shelf. Set **Ad type** to that shelf and the columns carry Meta's own values (`>$1M`, `$350K - $400K`). On ordinary ads they are null, and the README says so rather than inventing a number |
| "After several runs the number of exported fields decreases even when selecting all fields" | A fixed row shape. Every ad has the same columns in the same order, run after run |
| Raw Meta JSON with `fev_info`, `tw_anti_scam`, `hide_data_status` inside | A flat row in plain names. No unwrapping before a spreadsheet, an n8n node or a database can read it |

### What data does it extract?

One row per ad. Values below are from a real run on the keyword `nike`, United States.

#### The ad

| Field | Example |
|---|---|
| `adArchiveId`, `adLibraryUrl` | `925321173274919` · `https://www.facebook.com/ads/library/?id=925321173274919` |
| `title`, `bodyText` | "Fast & Free Pickup" · "Buy online and pick up in store for free! Get up to 70% off…" |
| `caption`, `linkUrl`, `linkDescription` | display domain · destination URL · text next to the link |
| `ctaText`, `ctaType` | "Learn more" · `LEARN_MORE` |
| `displayFormat` | `IMAGE`, `VIDEO`, `DPA`, `DCO`, `CAROUSEL` |
| `imageUrls`, `videoUrls` | direct media URLs — links, not downloads, so nothing bloats your run |
| `cardCount`, `cardTexts` | carousel cards and the text on each |
| `isDynamicTemplate` | `true` when the headline still reads `{{product.name}}` — catalogue ads are served that way, and a flag beats a surprise |
| `isActive`, `startDate`, `endDate` | `true` · `2026-08-03` · `2026-09-30` |
| `publisherPlatforms` | `["FACEBOOK","INSTAGRAM","MESSENGER"]` |
| `variantCount` | how many near-identical variants Meta groups under this ad |

#### The advertiser

| Field | Example |
|---|---|
| `pageId`, `pageName`, `pageUrl` | `89516513179` · Nordstrom Rack · link to the page |
| `pageLikes`, `pageCategories`, `pageProfilePicture` | 2 518 019 · page categories · avatar URL |

#### Transparency — political and social-issue ads only

| Field | Example |
|---|---|
| `spend`, `currency` | `">$1M"`, `"$350K - $400K"` · `USD` |
| `impressionsText`, `reachEstimate` | `">1M"` · `">1M"` |
| `categories`, `targetedCountries` | `["POLITICAL"]` · countries the ad was aimed at |

Meta publishes these for the political shelf and for nothing else. On commercial ads they are `null`, and that is Meta's limit, not a bug in the Actor.

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

**$1 per 1,000 ads delivered.** One ad, one row, one charge — ads are deduplicated inside a run, so a keyword that matches the same ad twice is billed once.

- **No start fee, no rental, no per-run charge.**
- **Diagnostic rows are free**: a block, a bad page ID or an empty result costs nothing.
- **The cap is real.** `maxAds` is checked before each request. A run cannot overshoot it.
- Higher Apify plans pay less: Bronze $0.90, Silver $0.80, Gold and above $0.70 per 1,000.
- The **$5 of free credit** on Apify's free plan is about **5,000 ads** before you pay anything.

### How to scrape the Meta Ad Library

1. Click **Try for free** and sign in to Apify.
2. Put keywords into **🔍 Search keywords**, or page IDs into **📄 Facebook page IDs**.
3. Set **🌍 Country** — Meta indexes ads per country, so this decides which ads exist at all.
4. Set **💯 Maximum ads**. The default of 30 finishes in seconds.
5. Press **Start**, then download JSON, CSV or Excel, or read the dataset through the API.

#### ⬇️ Input

![Meta Ad Library Scraper input form: search keywords, page IDs, country, ad status, ad type and maximum ads](https://raw.githubusercontent.com/lergassy/apify-actor-assets/main/meta-ad-library-scraper/meta-ad-library-scraper-input-form.png)

```json
{
  "queries": ["nike"],
  "country": "US",
  "activeStatus": "active",
  "maxAds": 30
}
```

Every ad of one advertiser:

```json
{
  "pageIds": ["89516513179"],
  "country": "US",
  "activeStatus": "all",
  "maxAds": 500
}
```

Political ads with spend and reach:

```json
{
  "queries": ["election"],
  "country": "US",
  "adType": "political",
  "activeStatus": "all",
  "maxAds": 200
}
```

#### ⬆️ Output

```json
{
  "adArchiveId": "925321173274919",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=925321173274919",
  "pageId": "89516513179",
  "pageName": "Nordstrom Rack",
  "pageLikes": 2518019,
  "title": "Fast & Free Pickup",
  "bodyText": "Buy online and pick up in store for free! Get up to 70% off Nike, Vince and more.",
  "ctaText": "Learn more",
  "displayFormat": "VIDEO",
  "videoUrls": ["https://video.xx.fbcdn.net/..."],
  "isDynamicTemplate": false,
  "isActive": true,
  "startDate": "2026-08-03",
  "publisherPlatforms": ["FACEBOOK", "INSTAGRAM", "MESSENGER"],
  "variantCount": 2,
  "spend": null,
  "status": "ok",
  "scrapedAt": "2026-09-19T08:12:03Z"
}
```

#### Run summary

Every run writes a `RUN_SUMMARY` record to the key-value store:

```json
{
  "askedFor": 30,
  "delivered": 30,
  "targets": [{ "target": "nike", "kind": "query", "ads": 30, "pages": 1, "scanned": 30, "filteredOut": 0, "stopReason": "MAX_REACHED" }],
  "requests": 2,
  "megabytes": 0.68,
  "blockedResponses": 0
}
```

That is the answer to "Meta says 34k, I got 2.5k": you can see what was asked, what arrived, and why it stopped. `scanned` and `filteredOut` appear per target when a date range or a media type is set — on one measured run, 181 ads were read from Meta, 161 did not match the date filter, and 20 were delivered and charged.

### Use cases

#### Competitor and creative research

Pull every active ad of a competitor by page ID and you have their current positioning: which products they push, which hooks they repeat, which formats they bet on, and how long each ad has been running. `startDate` plus `isActive` is the closest public proxy for what is working — advertisers do not keep losing creatives alive.

#### Ad copy and hook libraries

`title`, `bodyText`, `ctaText` and `cardTexts` across a whole vertical give you a swipe file in a spreadsheet instead of a folder of screenshots.

#### E-commerce and dropshipping research

Search a product category, sort by how long ads have run, and read `linkUrl` to see which stores are behind them.

#### Agency reporting and pitching

Show a prospect what their competitors are running today, with dates and platforms, straight from Meta's own public record.

#### Political ad monitoring

On the political shelf every row carries spend, impressions and reach ranges. Schedule a daily run over a set of keywords or pages and you have a spend timeline of a campaign.

#### Lead generation

An advertiser running ads is an advertiser with a budget. `pageName`, `pageUrl`, `pageLikes` and `pageCategories` qualify them before you ever write the first line of outreach.

#### AI agents

Pay-per-event, limited permissions, no Standby — callable from the Apify MCP server. "What is this brand advertising right now?", "Which ads has this page run since August?", "How much has this campaign spent?"

### Integrations

**API**, **scheduler**, **webhooks**, and the connectors for **n8n**, **Make**, **Zapier**, **Google Sheets**, **Slack**, **LangChain** and **LlamaIndex**.

```bash
curl -X POST "https://api.apify.com/v2/acts/lergassy~meta-ad-library-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries":["nike"],"country":"US","maxAds":50}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("lergassy/meta-ad-library-scraper").call(run_input={
    "pageIds": ["89516513179"],
    "country": "US",
    "activeStatus": "all",
    "maxAds": 500,
})
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(ad["pageName"], ad["startDate"], ad["title"])
```

#### MCP server

Point an agent at `https://mcp.apify.com?tools=lergassy/meta-ad-library-scraper` and it can call this Actor as a tool.

### Error rows and troubleshooting

Problems become rows, and none of them are charged.

| `errorCode` | What it means | What to do |
|---|---|---|
| `blocked` | Meta did not serve this exit address — usually a datacentre proxy | Leave the proxy on its residential default |
| `no-results` | The search genuinely matched nothing | Widen the country, the date range or the status; check the page ID |
| `no-input` | Neither a keyword nor a page ID was given | Fill one of them |
| `scrape-failed` | Something unexpected, with Meta's own message attached | Open an issue with the run id |

### ❓ FAQ

#### Is it legal to scrape the Meta Ad Library?

The Ad Library is published by Meta deliberately, as advertising transparency, and is readable without an account. This Actor reads that public record and bypasses no protection. How you then use the data — republishing creatives, storing personal data — is a separate question governed by copyright, by data-protection law and by Meta's terms. Use it for research and analysis, and take legal advice before republishing someone's creative.

#### Do I need a Facebook account or an API access review?

No. Meta's official Ad Library API covers political and social-issue ads and requires identity verification; this Actor needs neither.

#### Why are spend and reach empty?

Because Meta only publishes them for political and social-issue ads. Set **Ad type** to that shelf and they fill in. No scraper can produce them for a commercial ad, and one that shows a number there is showing you something invented.

#### Why does a headline say `{{product.name}}`?

That is a catalogue ad whose headline is generated per product at delivery, and the Ad Library stores the template. The row carries it verbatim and flags it with `isDynamicTemplate`.

#### Can I get every ad of one advertiser?

Yes — put their numeric page ID into **📄 Facebook page IDs**. It is the `pageId` on any row of theirs, and the `view_all_page_id` in an Ad Library URL.

#### Will the run stop where I tell it to?

Yes. `maxAds` is checked before every request. That is the main reason this Actor exists.

#### Can I use it through an MCP server?

Yes — see Integrations.

### Your feedback

Found an advertiser or a country where results look wrong? Open an **Issue** with the run id — that is the fastest way to get it fixed. If it did the job, a short review helps more than anything I can write myself.

### You might also like

| Actor | What it does |
|---|---|
| [Threads Scraper](https://apify.com/lergassy/threads-scraper) | Posts, replies, profiles and keyword search on Meta Threads |
| [YouTube Transcript Scraper](https://apify.com/lergassy/youtube-transcript-scraper) | Transcripts with timecodes from videos, playlists and channels |
| [Website Contacts API](https://apify.com/lergassy/website-contacts-api) | E-mails, phones and social links from any website |
| [Email & Phone Verifier](https://apify.com/lergassy/email-phone-verifier) | Checks contacts before you send anything |

# Actor input Schema

## `queries` (type: `array`):

Words or brand names to search the Ad Library for, one per line. Every ad whose text, advertiser or link matches is returned.

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

Numeric page IDs to pull every ad of one advertiser, instead of searching by keyword. The ID is the number in an ad's Ad Library link, and it is also returned as <code>pageId</code> on every row.

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

Two-letter country code of the audience the ads were shown to, for example <code>US</code>, <code>GB</code>, <code>DE</code>. Meta indexes ads per country, so this changes which ads exist, not just how they are filtered.

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

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

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

All ads, or only the political and social-issue shelf. Meta publishes spend, impressions and reach for that shelf and for nothing else, so the <code>spend</code>, <code>impressionsText</code> and <code>reachEstimate</code> columns only carry numbers here.

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

Keep only ads of one format. Meta applies this itself, so it changes which ads are fetched and therefore what you pay for.

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

Limit to ads shown on particular Meta surfaces. Leave empty for all of them.

## `startDate` (type: `string`):

Only ads that began running on or after this date, as <code>YYYY-MM-DD</code>.

## `endDate` (type: `string`):

Only ads that began running on or before this date, as <code>YYYY-MM-DD</code>.

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

Hard cap for the whole run. It is checked before every request, so the run stops at this number and never bills you past it.

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

Optional per-target cap, so one broad keyword cannot eat the whole run.

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

Meta serves the Ad Library to residential addresses only; from a datacentre address it answers with an empty response. Leave this on the default unless you know why you are changing it.

## Actor input object example

```json
{
  "queries": [
    "nike"
  ],
  "pageIds": [],
  "country": "US",
  "activeStatus": "active",
  "adType": "all",
  "mediaType": "all",
  "platforms": [],
  "maxAds": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

One row per ad: advertiser, creative, link, platforms and dates.

# 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 = {
    "queries": [
        "nike"
    ],
    "country": "US",
    "activeStatus": "active",
    "maxAds": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("lergassy/meta-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 = {
    "queries": ["nike"],
    "country": "US",
    "activeStatus": "active",
    "maxAds": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("lergassy/meta-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 '{
  "queries": [
    "nike"
  ],
  "country": "US",
  "activeStatus": "active",
  "maxAds": 30
}' |
apify call lergassy/meta-ad-library-scraper --silent --output-dataset

```

## MCP server setup

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