# Facebook Ads Library Scraper — Meta Ad Library, No Login (`anuarbakty/facebook-ads-library-scraper`) Actor

Scrape Facebook & Instagram ads from the Meta Ad Library by keyword, brand name, page, Instagram account or Ad Library link. Get ad copy, creatives, CTA, landing page, days running, EU reach and advertiser contacts. No login. Limits that hold, empty results are free. $0.60 per 1,000 ads.

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

## Pricing

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

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** extracts ads from the **Meta Ad Library**: Facebook, Instagram, Messenger, Audience Network, Threads and WhatsApp. Search by keyword, brand name, Facebook page, Instagram account or a pasted Ad Library link and get every ad as a clean row: copy, creatives, button, landing page, start date and days running. No login, no cookies, no Meta API token, no proxy to set up.

It was built after reading 385 public reviews and support threads of Ad Library scrapers. The three complaints that come up most are about money and trust: a limit that was ignored, an empty run that was charged, and half of the ads missing without a word. This Actor is designed around those three.

![Facebook Ads Library Scraper results table: advertiser, ad text, button, format, platforms, start date, days running, winner score and landing page for Nike, adidas and Gymshark ads](https://api.apify.com/v2/key-value-stores/qOa9EAsXL9WVyHzug/records/readme-results.png)

### What does Facebook Ads Library Scraper do?

- **Finds ads every way you can name an advertiser**: keywords, brand names, Instagram accounts, Facebook page links, Page IDs, Ad Library links with any filters, single ads by Library ID. Mix them in one run, in several countries at once.
- **Returns flat, readable rows**: texts, button, landing page, images, HD videos, carousel cards, start date and **days running**. Catalog and dynamic ads come with their real creatives instead of a `{{product.name}}` template.
- **Adds EU and UK reach, payer and the advertiser's Instagram** with one switch, at no extra price.
- **Adds advertiser contacts**: website, phone, email, address with coordinates, rating.
- **Points at winners**: a `winnerScore` on every row, a long-running flag, a filter for ads running at least N days.
- **Saves creatives** to your storage, so the images and videos outlive Meta's expiring links.
- **Monitors on a schedule**: only new ads, or new, changed and stopped ones, with a message to Slack, Discord or your webhook.

### Why use this Facebook Ad Library scraper?

| What goes wrong elsewhere | What happens here |
|---|---|
| "Asked for 40 ads, got 5,505 items and a $27 charge" | The Actor stops exactly at your limit. A limit typed under a name it does not know stops the run before the first request |
| "Succeeded, no results, money gone" | A source with no ads gives a free row that says why. Counts, notices and duplicates are never charged |
| "Missed half of the active ads" | For every source you see how many ads the library holds and how many were collected, and the reason when the two differ |
| An automation built for another scraper has to be rebuilt | Inputs made for the two most used Ad Library Actors run here unchanged |
| The same ads again on every scheduled run | Monitoring delivers only what is new or changed; the rest is skipped for free |
| A start fee on every run | No start fee |

### What Facebook and Instagram ad data can you extract?

| Data | Fields |
|---|---|
| The ad | Library ID and link, active status, start and end date, days running, platforms, format |
| Copy | Body text, title, link description, button text and type, landing page |
| Creatives | Images, HD and SD videos, video previews, every carousel or catalog card, when the links expire |
| Advertiser | Page name, ID, link, categories, likes; with **Advertiser page**: website, phone, email, address, rating, Instagram |
| Transparency | With **Ad details**: who paid, EU and UK reach by country, age and gender, the audience the advertiser chose |
| Political ads | Spend, impressions, reach estimate, "paid for by" |
| Signals | Winner score, long-running flag, number of ads sharing the creative, what changed since the last run |

The full field list is in [Output](#output).

### What can you use it for?

- **Competitor ad monitoring**: every ad a brand runs right now, on a schedule, only what is new or changed.
- **Creative research and ad spying**: what advertisers in a niche say, which buttons and landing pages they use, which creatives they keep running for months.
- **Lead generation for agencies**: businesses that advertise on a topic in a country, with their website, phone, email and address.
- **Market checks before a launch**: how many advertisers and ads a keyword has in each country, free in count-only mode.
- **Transparency and research**: who paid for an ad and how many people it reached in the EU.
- **AI and automation**: clean rows for n8n, Make, Zapier, Clay or an MCP agent.

### How to scrape Facebook ads

1. Open the Actor and type a **keyword**, or paste a **Facebook page link**, a **brand name**, an **Instagram account** or an **Ad Library link**.
2. Pick the **country** and how many **ads per source** you want. The default is 100.
3. Click **Start**. A hundred ads take well under a minute.
4. Open the **Output** tab and export to JSON, CSV, Excel or HTML, or read the results through the API.

| You give | You get |
|---|---|
| Keyword `dental clinic`, country `US` | Ads that mention it, newest first after the first 30 |
| `https://www.facebook.com/nike` | Every ad of that page; the Actor finds the Page ID itself |
| Page ID `15087023444` | The same, without the lookup |
| Brand name `Aspen Dental` | The ads of the page with exactly that name |
| Instagram account `@aspendental` or its link | The ads of the page that advertises for the account |
| An Ad Library link copied from the address bar | The ads of that search, with the filters set in the link |
| `https://www.facebook.com/ads/library/?id=1870984290951787` or the Library ID | That one ad, or a free notice if the library no longer shows it |
| Any of the above with **Only count the ads** | One free row per source: how many ads the library holds |

Minimal input:

```json
{
    "searchTerms": ["dental clinic"],
    "country": "US",
    "maxAdsPerSource": 50
}
```

Several brands in several countries, with reach and payer:

```json
{
    "startUrls": [{ "url": "https://www.facebook.com/nike" }, { "url": "https://www.facebook.com/adidas" }],
    "countries": ["DE", "FR"],
    "maxAdsPerSource": 100,
    "includeAdDetails": true
}
```

Local clinics that advertise in a city, with contacts, only ads that survived a month:

```json
{
    "searchTerms": ["dental implants"],
    "country": "US",
    "minDaysRunning": 30,
    "includePageInfo": true,
    "maxAdsPerSource": 200
}
```

A competitor watch on a schedule, with a Slack message when something changes:

```json
{
    "pageNames": ["Aspen Dental", "Mayo Clinic"],
    "instagramHandles": ["@clevelandclinic"],
    "country": "US",
    "trackChanges": true,
    "saveMedia": "images",
    "webhookUrl": "https://hooks.slack.com/services/…"
}
```

### How much does it cost to scrape Facebook ads?

You pay per result, not for time or memory.

| What | Price |
|---|---|
| An ad row, with or without ad details | **$0.60 per 1,000 ads** |
| Advertiser page (optional) | $2 per 1,000 advertisers, once per advertiser in a run |
| Saved creative (optional) | $1 per 1,000 files |
| Run start, count rows, notice rows, duplicates, ads skipped by monitoring or by a filter | free |

| Example | Cost |
|---|---|
| 1,000 ads of a keyword | $0.60 |
| 5,000 ads with EU reach and payer | $3.00 |
| 1,000 ads of 200 local businesses, with their contacts | $1.00 |
| How many ads 50 keywords have in 10 countries (count only) | $0 |

Apify's free plan includes $5 of credit every month, enough for about 8,000 ads.

Two guards keep a bill from surprising you: the limits stop the Actor exactly at the number, and the maximum cost you set for a run in Apify is respected, so the Actor stops before the ad that would go over it.

### Input

Country, active status, media type, ad category, platforms, languages, exact phrase and order are separate fields. They apply to keywords, brand names, pages and Page IDs, and to links that do not set the filter themselves; a filter written in a link always wins.

**Several countries** collects every keyword and page once per country, each with its own limit and its own count. **Ads per source** (default 100) and **Ads per run** are the limits. An ad found by two sources of one run is delivered and charged once; turn **One row per ad in a run** off if every source should deliver all of its ads.

#### Two questions about dates

The Ad Library's own date filter answers "was the ad still running at that time", not "when did it start". The Actor keeps the two apart:

- **Started on or after** and **Started before** filter by the start date of the ad.
- **Still running on or after** is the library's own filter: it keeps ads that were shown on that day or later, whenever they started.
- **Running for at least this many days** keeps the ads that survived.

Dates are `YYYY-MM-DD`, or a span back from today: `7 days`, `2 weeks`, `3 months`.

#### Coming from another Ad Library scraper?

An input or an automation built for the two most used Ad Library Actors runs here unchanged. These fields are read under their own names:

| Their field | Read here as |
|---|---|
| `startUrls`, `urls`, `adLibraryUrls`, `pageUrls` | links |
| `searchQueries`, `queries`, `keywords`, `query` | keywords |
| `pageId`, `pageIds`, `advertiserPageIds` | Page IDs |
| `pageName`, `advertiserNames`, `brand`, `brands`, `competitors` | brand or page names |
| `instagramUsername`, `instagramUrl`, `igUsername` | Instagram accounts |
| `advertisers` | anything that names an advertiser: a link, a Page ID, an `@account` or a brand name, each read as what it is |
| `includeAboutPage`, `includeAdvertiserInfo` | advertiser page |
| `minDaysRunning`, `minDaysActive` | running for at least N days |
| `downloadMedia`, `downloadCreatives` | save creatives |
| `webhookUrl`, `slackWebhookUrl`, `discordWebhookUrl` | webhook address |
| `resultsLimit`, `limitPerSource`, `maxAdsPerQuery` | ads per source |
| `count`, `maxAds`, `maxItems`, `maxResults`, `limit` | ads per run |
| `onlyTotal`, `onlyTotalCount` | only count the ads |
| `isDetailsPerAd`, `scrapeAdDetails`, `includeAdReach`, `fetchDetails` | ad details |
| `activeStatus`, `adActiveStatus`, `scrapePageAds.activeStatus` | active status |
| `countryCode`, `scrapePageAds.countryCode`, `searchCountries` | country |
| `sorting`, `scrapePageAds.sortBy` | order |
| `onlyAdsNewerThan`, `scrapePageAds.period` | still running on or after |
| `onlyAdsOlderThan`, `startDateMax`, `dateTo` | started before |
| `startDateMin`, `dateFrom`, `minDate` | started on or after |
| `runTag`, `proxy` | run tag, proxy |

A field no name matches stops the run before the first request, with a free row that names the field. That is on purpose: a mistyped limit that is silently ignored is how a run meant for 40 ads collects 5,000. Turn **Stop on unknown input fields** off if you prefer a warning.

#### Finding an advertiser by brand name or Instagram account

The Ad Library suggests pages by name only to logged-in users. The Actor searches the name as a keyword and looks at the advertisers of the ads it finds:

- **Brand name**: the page whose name is exactly the name you gave, ignoring case, spaces and punctuation. When several pages carry it, the most liked one is read and the others are listed in the SUMMARY record; **Read every page with this name** reads them all. Each page then gets the limit of a source.
- **Instagram account**: the advertisers found are asked for their Instagram account, and the one that names yours is read.
- **Nothing matched**: a free notice row lists the closest advertisers with their Page IDs, so that you can pick the right one.

A page that has no ads in the library cannot be found this way; give its Facebook link or Page ID.

### Output

#### Ad rows

| Field | What it is |
|---|---|
| `adArchiveId`, `adLibraryUrl` | The Library ID and the link to the ad in the library |
| `pageName`, `pageId`, `pageUrl`, `pageCategories`, `pageLikeCount` | The advertiser |
| `isActive`, `startDate`, `endDate`, `lastSeenDate`, `daysRunning` | Dates as `YYYY-MM-DD`. `endDate` is empty while the ad is running |
| `winnerScore`, `isLongRunning` | A 0–100 ranking aid and a flag for ads running 30 days or more (the threshold is yours); see below |
| `platforms` | Where it is delivered: `facebook`, `instagram`, `messenger`, `audience_network`, `threads`, `whatsapp` |
| `displayFormat` | `image`, `video`, `carousel`, `dco` (flexible), `dpa` (catalog), and others |
| `bodyText`, `title`, `linkDescription`, `linkCaption` | The texts of the ad |
| `ctaText`, `ctaType`, `linkUrl` | The button and the landing page |
| `imageUrls`, `videoUrls`, `videoPreviewUrls` | Creatives; videos in HD when the library has it |
| `cards`, `cardCount` | Every carousel or catalog card with its own title, text, link, button, image and video |
| `isDynamicCreative` | The ad's own texts are templates such as `{{product.name}}`; the row shows the first card instead |
| `mediaExpiresAt` | When Meta's media links stop working (they are signed for a few days) |
| `versionCount`, `collationId` | How many ads share this creative |
| `adCategories`, `currency`, `spend`, `impressions`, `reachEstimate`, `paidForBy` | Published by Meta only for ads about social issues, elections or politics |
| `partnerPageName`, `partnerPageUrl` | The creator, when the ad is a partnership ad run from a creator's page |
| `containsAiMedia`, `containsSensitiveContent`, `visibility`, `hasEuTransparency` | Meta's labels |
| `pageInfo` | The advertiser's page card, with **Advertiser page** |
| `mediaFiles` | Saved copies of the creatives, with **Save creatives to storage**: `type`, lasting `url`, `key`, `sourceUrl` |
| `change`, `changedFields` | With change tracking: `new`, `changed`, `stopped` or `removed`, and what changed |
| `source`, `sourceValue`, `inputLabel`, `runTag`, `scrapedAt` | Which input found the ad, and when |
| `raw` | Meta's original object, with **Add Meta's original object** |

#### Ad details (optional)

With **Ad details** every ad row gets a `details` object:

![Facebook ads delivered in Germany with EU reach, who paid for the ad and on whose behalf, the targeted age and the largest age group reached](https://api.apify.com/v2/key-value-stores/qOa9EAsXL9WVyHzug/records/readme-eu.png)

| Field | What it is |
|---|---|
| `details.payer`, `details.beneficiary` | Who paid for the ad and on whose behalf |
| `details.eu.totalReach`, `details.uk.totalReach` | Accounts reached in the EU and in the UK |
| `details.eu.reachBreakdown` | Reach by country, age range and gender |
| `details.eu.targetLocations`, `targetGender`, `targetAgeMin`, `targetAgeMax` | The audience the advertiser chose |
| `details.page.instagramUsername`, `instagramFollowers`, `instagramVerified` | The advertiser's Instagram account |
| `details.page.category`, `likes`, `verification`, `alias`, `about` | The advertiser's page |
| `details.page.isPoliticalAdvertiser`, `politicalSpendByDisclaimer` | For registered political advertisers |

Reach and targeting exist only for ads delivered in the EU or the UK; for other ads `details.eu` and `details.uk` are empty, which is how the library itself shows them.

#### Advertiser page (optional)

With **Advertiser page** the Actor opens the Facebook page of every advertiser once per run and adds what its card shows a visitor:

![Advertiser contacts read from Facebook pages: category, website, phone, email, address, Instagram account and followers of Mayo Clinic, Cleveland Clinic and Aspen Dental](https://api.apify.com/v2/key-value-stores/qOa9EAsXL9WVyHzug/records/readme-contacts.png)

| Field | What it is |
|---|---|
| `pageInfo.website`, `phone`, `email` | Contacts as the page gives them; the website without Facebook's redirect |
| `pageInfo.address`, `latitude`, `longitude` | The address of a local business |
| `pageInfo.categories`, `bio`, `priceRange` | What the page says it is |
| `pageInfo.rating`, `recommendPercent`, `reviewCount` | "94% recommend (51 reviews)" as text and as numbers |
| `pageInfo.instagramUsername`, `instagramUrl`, `otherAccounts` | Accounts the page links to |
| `pageInfo.confirmedOwner` | The business Meta confirmed as the owner |
| `pageInfo.followers`, `followersText` | As Facebook rounds it: "1.4M followers" is 1,400,000 |
| `pageInfo.other` | Any other line of the card, as Facebook words it |

In a check of 12 pages of local businesses, 10 showed a card. A page that shows none to visitors comes with `available: false` and the reason. A page gives only what its owner filled in: a national brand usually has a website and a phone, a local business also an address and a rating.

#### Winner score

Meta publishes no results for ordinary ads. What the library does show is how long an advertiser keeps paying for an ad and how many ads reuse its creative, and advertisers stop what does not work. `winnerScore` adds that up: up to 60 points for days running (full at 90 days), up to 25 for the number of ads sharing the creative (full at 11), and 15 for still being active. It is a way to sort a list, not a measurement of sales.

#### Count rows and notice rows

- `type: "count"`: in count-only mode, `totalCount` for each source. A count of 50,001 means "more than 50,000": Meta stops counting there, and `totalCountIsMinimum` is true.
- `type: "notice"`: a source that gave nothing or less than asked, with `noticeCode` and a sentence in `noticeMessage`. Codes: `NO_ADS`, `PAGE_NOT_FOUND`, `AD_NOT_FOUND`, `ADS_NOT_SHOWN`, `PARTIAL`, `INVALID_INPUT`, `UNKNOWN_INPUT_FIELD`, `EMPTY_INPUT`, `BLOCKED`.

Both are free. Filter by `type` to keep only ads. The same numbers for every source are in the **Summary by source** output.

### Monitoring: new, changed and stopped ads on a schedule

Turn on **Only ads not delivered before** and run the Actor on a schedule. It remembers the ads of the input and delivers only the ones it has not delivered yet; the rest are skipped for free. The first runs work through the ads a source already has, up to the limit each time; after that only newly started ads come.

A **memory name** lets several schedules share one memory or start a fresh one. **Forget what was delivered** clears it.

**Also deliver changed and stopped ads** turns the same schedule into a change log. Every run delivers what is different from the run before, and each row says what in `change`:

- `new`: an ad the Actor has not delivered before;
- `changed`: the text, the button, the landing page or the number of versions is different; `changedFields` names which;
- `stopped`: an ad that ran last time and is inactive now, with its end date;
- `removed`: an ad the library no longer has; this row is free.

Ads that did not change are skipped for free. Stopped ads are looked for only when the source was read to its end, so set **Ads per source** above the number of ads the advertiser runs.

A **webhook address** gets a message when the run is over: a Slack or Discord incoming webhook gets a short text with the first ads, any other address a JSON body with the counts per source, the first ten ads and the dataset link. By default it is sent only when the run delivered something.

### Limitations, stated plainly

- **The library shows a logged-out visitor 30 ads per screen.** The Actor does not log in. It reads a source screen by screen, by start date, from the newest ads down, and checks every step against Meta's own count.
- **What that covers.** A source with up to a few hundred ads is collected whole. From a larger one you get the newest ads up to your limit: 5,000 ads of a search with 27,000 took under three minutes.
- **Deep search.** A screen shows only the top ads of a day, so on a busy day most ads never reach one. For a keyword the Actor therefore also reads every advertiser it finds, each on its own; a page in all countries it reads again country by country. Measured on 2026-10-10 without a limit: 1,831 of the 1,906 ads of one search (96%, 4 minutes), 19,036 of the 27,673 of another (69%, 26 minutes), 1,780 of the 2,958 ads of a global brand's page (60%, 8 minutes). You pay per ad, not per request. **Deep search on keywords** turns it off.
- **What it does not.** Ads of advertisers that never reach the top of any screen, and the busiest days of one page, stay out of reach. The notice row says how many of how many were collected. Narrow the source (one country, one media type, a date range) to get more of it.
- **Order.** The first 30 ads of a source come in the order you choose. Beyond that the Actor reads from the newest ads down, whichever order is chosen.
- **Ads the library counts but does not show.** Meta's count can include ads it shows only after login. The Actor reports them as "not shown to logged-out visitors" in the summary instead of counting them as missing.
- **Meta limits how much one address may ask.** Past the limit a listing comes with its count and without its ads. The Actor asks at an even pace, tells such an answer from a listing with nothing more to show, pauses and goes on; if the limit holds, the source ends with what was collected and a notice that says so. How far a run gets depends on how many proxy addresses your Apify plan gives: on 30 addresses an export of 19,000 ads met no limit; in a test squeezed onto 3 addresses the limit came after about 5,600 ads, and the run ended with 6,716 and the notice. Run such a source again an hour or two later with **Only ads not delivered before** on, and it adds what is missing.
- **Spend, impressions and reach** are published by Meta only for political ads (spend, impressions) and for ads delivered in the EU or UK (reach). For all other ads these fields are empty in the library itself.
- **Media links expire.** Image and video links are signed by Meta for a few days. `mediaExpiresAt` says until when. **Save creatives to storage** copies the files while the links work: into the run's own storage, which lives as long as run data does on your plan, or into a named one that stays. A file over 150 MB is not saved.
- **The winner score is a guess.** It ranks ads by how long they run and how often their creative is reused; it knows nothing about sales.
- **Inactive ads** are kept by the library for a limited time, depending on the country and the category of the ad.
- **The count stops at 50,000.** For broader searches Meta shows 50,001 and the true number is unknown.

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

The Ad Library is a public transparency tool: Meta publishes it so that anyone can see which ads run on its platforms, and advertisers place their ads there knowingly. The Actor reads only what the library shows to any visitor who is not logged in. It does not log in, and it does not collect private profiles or anything behind a login.

Rows can still contain personal data, for example the name of a person who advertises from a personal page, who is named as a payer, or whose phone or email stands on a business page. Collect and use it only for a purpose you can justify under the rules that apply to you, such as the GDPR. If you are not sure, ask a lawyer.

### Integrations and API

Results export to JSON, CSV, Excel and HTML. Runs can be scheduled, called through the Apify API and connected to Make, Zapier, n8n, Google Sheets or your own webhook.

```bash
curl -X POST "https://api.apify.com/v2/acts/anuarbakty~facebook-ads-library-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms": ["dental clinic"], "country": "US", "maxAdsPerSource": 30}'
```

The run also writes a `SUMMARY` record to its key-value store: for every source the number of ads in the library, collected, delivered, skipped as duplicates or as known from earlier runs, and the reason it stopped.

#### Use it from AI agents (MCP)

The Actor works as a tool through the [Apify MCP server](https://mcp.apify.com). A typical request:

> Find the 20 newest ads Nike runs in Germany and tell me which products they push.

The agent calls the Actor with `{"startUrls": [{"url": "https://www.facebook.com/nike"}], "country": "DE", "maxAdsPerSource": 20, "sortBy": "newest"}`. Because unknown input fields stop the run and limits are exact, an agent that guesses a field name gets a clear message back instead of a large bill.

### More from this developer

- **[YouTube Transcript Scraper](https://apify.com/anuarbakty/youtube-transcript-scraper)**: transcripts and subtitles of YouTube videos, Shorts, channels, playlists and search in any language, as text, timestamps, SRT, VTT or chunks for AI; no captions, no charge.
- **[Threads Scraper](https://apify.com/anuarbakty/threads-scraper)**: posts, all replies and profiles from Threads without login, keyword search and only-new monitoring.
- **[2GIS Places Scraper](https://apify.com/anuarbakty/2gis-places-scraper)**: businesses from 2GIS (Russia, Kazakhstan, Central Asia, UAE) with phones, WhatsApp, Telegram, emails and websites, the whole result instead of 60.
- **[2GIS Reviews Scraper](https://apify.com/anuarbakty/2gis-reviews-scraper)**: 2GIS reviews with star, topic and date filters, unanswered reviews across a chain, and only-new monitoring.

### FAQ

**Do I need a Facebook account, cookies or a Meta API key?**
No. The Actor reads what the Ad Library shows any visitor.

**Can I scrape Instagram ads?**
Yes. The Meta Ad Library covers Instagram, and every row says on which platforms the ad runs. To see only Instagram, choose it in **Platforms**. You can also start from an Instagram account: `@name` or its link.

**How do I get all ads of one advertiser?**
Paste the Facebook page link, or type the brand name or the Page ID. Set **Ads per source** to 0 for as many as the library gives.

**How do I get all ads of a brand in all countries?**
Use country `ALL`, or list the countries you care about in **Several countries** to get each market separately and more completely.

**Why did I get fewer ads than the library shows?**
Look at the notice row or the summary of that source. Either your limit was reached, or the source has more ads on single days than a logged-out visitor can reach, or the library counts ads it shows only after login. See [Limitations](#limitations-stated-plainly).

**Why are `spend` and `reachEstimate` empty?**
Meta publishes them only for ads about social issues, elections or politics. For EU and UK reach of ordinary ads turn on **Ad details**.

**A run finished as "succeeded" but there are no ads.**
There is a notice row instead: no ads for these filters, a page that does not exist, a link the Actor cannot read, or an input field it does not know. The run succeeds so that an automation sending one page per run is not stopped by one empty page. Turn on **Fail the run when every source gives a notice** if you want a failed status.

**Can I get only the number of ads?**
Yes, **Only count the ads** gives one free row per source.

**The image links stopped working.**
They are signed by Meta for a few days; see `mediaExpiresAt`. Turn on **Save creatives to storage** to keep the files, or run the Actor again for fresh links.

**Something does not work or a field is missing.**
Open an issue on the Issues tab with the run ID. Every source that gave less than asked already says why in its notice row, and that sentence is the fastest way to an answer.

# Actor input Schema

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

One per line:<br>• an <b>Ad Library link</b> copied from the address bar, with any filters set there (<code>facebook.com/ads/library/?…</code>)<br>• a <b>Facebook page link</b> (<code>facebook.com/nike</code>) → all ads of that page<br>• an <b>Instagram account link</b> (<code>instagram.com/aspendental</code>) → all ads of the page behind it<br>• a link to <b>one ad</b> (<code>facebook.com/ads/library/?id=…</code>)<br>Filters in a link win; the fields below fill in what the link does not set. The field is also read under the name <code>urls</code>.

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

Words or phrases to search the library for, one per line. Each keyword is a separate source with its own limit.

## `pageNames` (type: `array`):

Names of advertisers as they are written on their pages, one per line. The Actor finds the page whose name is exactly this and collects its ads; when no page matches, a free notice row lists the closest ones with their Page IDs.

## `instagramHandles` (type: `array`):

Instagram account names (<code>@aspendental</code>) or account links, one per line. The Actor finds the page that advertises for the account and collects its ads.

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

Numeric Facebook Page IDs of advertisers, one per line. A page link above works too: the Actor finds the ID itself.

## `adIds` (type: `array`):

The "Library ID" shown on an ad in the Ad Library, one per line. Each gives one row, or a free notice row when the library no longer shows the ad.

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

Two-letter country code (US, DE, BR…) or ALL for every country. Ads are matched by where they are delivered.

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

Optional. Every keyword and page is collected once per country listed here, each with its own limit and its own count. Replaces the single country above.

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

Active ads are running now. Inactive ads have stopped but are still kept in the library.

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

Keep only ads with this kind of creative.

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

The library's special categories. Spend, impressions and "paid for by" are published only for the political category.

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

Keep only ads delivered on these platforms. Empty = all.

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

Keep only ads in these languages, as two-letter codes: en, es, de, fr, pt, ru… Empty = all.

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

On: an ad must contain the keyword words together, in this order. Off: the words in any order.

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

The order of the first 30 ads of every source. Beyond 30 the Actor reads a source from its newest ads down, whichever order is chosen here.

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

Keep only ads whose start date is this day or later. A date, or a span back from today such as "7 days" or "3 months".

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

Keep only ads that started before this day.

## `activeSince` (type: `string`):

Keep only ads that were still being shown on this day or later, whenever they started. For active ads this changes nothing; for inactive ads it cuts off those that ended earlier.

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

Keep only ads that have been running this long: ads that survive for weeks are usually the ones that pay off. 0 = no filter. Younger ads are left out before they are read, so they cost nothing.

## `maxAdsPerSource` (type: `integer`):

How many ads to collect from each link, keyword, page and country. 0 = as many as the library gives. Also read as resultsLimit and limitPerSource.

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

Stop after this many ads in total. 0 = no total limit. Also read as count, maxAds, maxResults and limit. The run never spends more than the cost limit set in Apify either.

## `countOnly` (type: `boolean`):

One free row per source with the number of ads the library holds for it, and no ads. A quick way to see what a run would cost before running it.

## `deepSearch` (type: `boolean`):

The library shows a visitor only the top ads of each day. On: when a keyword source needs more than that, the Actor also reads every advertiser it finds for the keyword on its own, which reaches most of what the screens hide. It makes more requests, not more charges: you still pay per ad. Off: only the date walk.

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

Opens the details of every ad: the payer and the beneficiary, EU and UK reach with a breakdown by country, age and gender, the targeting the advertiser chose, and the advertiser's page with its Instagram account and follower count. Reach exists only for ads delivered in the EU or UK. One more request per ad, at no extra price.

## `includePageInfo` (type: `boolean`):

Reads the page of every advertiser once per run and adds what its card shows a visitor: categories, website, phone, email, address with coordinates, rating, price range, the confirmed owner, the linked Instagram account and the follower count. Also read as includeAboutPage. Pages that show no card to visitors come with the reason.

## `saveMedia` (type: `string`):

Meta's links to images and videos expire in a few days. This copies the files into the key-value store of the run and adds their lasting links to every row (<code>mediaFiles</code>). Videos can be large.

## `mediaStoreName` (type: `string`):

Optional. A named key-value store keeps the files for as long as you want; without a name they go to the run's own store and live as long as the run's data does on your plan. Letters, digits and hyphens.

## `longRunningDays` (type: `integer`):

Every row carries <code>isLongRunning</code>: whether the ad has run at least this many days. 0 turns the flag off. The <code>winnerScore</code> field (0–100) is always there.

## `includeRaw` (type: `boolean`):

Adds a "raw" field with the ad exactly as the library returns it, next to the flat fields.

## `runTag` (type: `string`):

Any text; it is copied into the runTag field of every row, to tell runs apart after merging datasets.

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

The Actor remembers the ads of this input and delivers only new ones on the next runs.

## `monitoringName` (type: `string`):

Optional. By default each set of sources and filters has its own memory. A name lets several schedules share one memory, or start a fresh one on purpose.

## `resetMonitoring` (type: `boolean`):

Clears the monitoring memory of this input before the run, so everything is delivered again.

## `trackChanges` (type: `boolean`):

Every run delivers what is different from the run before: new ads, ads whose text, landing page or number of versions changed, and ads that stopped. Each row says which in <code>change</code> and <code>changedFields</code>. Ads that did not change are skipped for free.

## `webhookUrl` (type: `string`):

A Slack or Discord incoming-webhook link gets a short message with the first ads; any other address gets a JSON body with the counts per source, the first ads and the dataset link. Also read as slackWebhookUrl.

## `notifyWhen` (type: `string`):

With monitoring on, "only when the run delivered ads" means: only when there is something new.

## `diagnosticRows` (type: `boolean`):

A free row for every source that gave nothing or less than asked, with the reason: no ads, page not found, ad removed, or only part of the ads reachable. Off: the reasons stay in the log and in the SUMMARY record.

## `deduplicate` (type: `boolean`):

On: an ad found by several sources of one run (two keywords, or the same page in two countries) is delivered and charged once, under the source that found it first. Off: every source delivers all of its ads, so the same ad can come several times with different inputs.

## `pageNameMatch` (type: `string`):

A brand name can belong to several pages: the brand itself, its regional pages, or unrelated businesses. Either way the SUMMARY record lists every page that carried the name.

## `strictInput` (type: `boolean`):

On: an input field the Actor does not know stops the run before the first request, so a mistyped limit cannot lead to an unexpected bill; the run ends with a free notice row that names the field. Off: unknown fields are ignored with a warning.

## `failIfAllInputsFail` (type: `boolean`):

Off: a source that gives nothing comes as a free notice row with the reason (no ads, page not found, ad removed, a link or date that cannot be read, an unknown input field, or the library not readable after all retries), and the run finishes as succeeded even when every source ended that way, so an automation that sends one page per run keeps going. On: the run fails when nothing was collected and every source ended with a notice, so an automation can catch it by the run status.

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

Leave empty: the Actor uses Apify datacenter proxies and switches a failing request to residential ones by itself. Set your own only if you need to.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&q=dental%20clinic&search_type=keyword_unordered&media_type=all"
    }
  ],
  "country": "ALL",
  "activeStatus": "active",
  "mediaType": "all",
  "adType": "all",
  "exactPhrase": false,
  "sortBy": "default",
  "minDaysRunning": 0,
  "maxAdsPerSource": 30,
  "maxItems": 0,
  "countOnly": false,
  "deepSearch": true,
  "includeAdDetails": false,
  "includePageInfo": false,
  "saveMedia": "none",
  "longRunningDays": 30,
  "includeRaw": false,
  "onlyNew": false,
  "resetMonitoring": false,
  "trackChanges": false,
  "notifyWhen": "newAds",
  "diagnosticRows": true,
  "deduplicate": true,
  "pageNameMatch": "top",
  "strictInput": true,
  "failIfAllInputsFail": false
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "startUrls": [
        {
            "url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&q=dental%20clinic&search_type=keyword_unordered&media_type=all"
        }
    ],
    "maxAdsPerSource": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("anuarbakty/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 = {
    "startUrls": [{ "url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&q=dental%20clinic&search_type=keyword_unordered&media_type=all" }],
    "maxAdsPerSource": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("anuarbakty/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 '{
  "startUrls": [
    {
      "url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&q=dental%20clinic&search_type=keyword_unordered&media_type=all"
    }
  ],
  "maxAdsPerSource": 30
}' |
apify call anuarbakty/facebook-ads-library-scraper --silent --output-dataset

```

## MCP server setup

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