# Meta Ad Library Scraper – Facebook & Instagram Ads (`dougmanoel/meta-ad-library-scraper`) Actor

Scrape the public Meta Ad Library (Facebook, Instagram, Messenger) by keyword, advertiser page or filtered URL: ad text, dates, platforms, call to action and destination domain. No login. Residential proxy by default.

- **URL**: https://apify.com/dougmanoel/meta-ad-library-scraper.md
- **Developed by:** [Douglas Gouvea Manoel](https://apify.com/dougmanoel) (community)
- **Categories:** Social media
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 – Facebook & Instagram Ads by Keyword or Page

![Meta Ad Library Scraper](https://errandcode.com/meta-ad-library-scraper-cover.png)

[![Errand Code – Meta Ad Library Scraper](https://img.youtube.com/vi/lJhZ60G_6OU/maxresdefault.jpg)](https://youtu.be/lJhZ60G_6OU)

Watch the demo: https://youtu.be/lJhZ60G_6OU

Part of Errand Code: https://errandcode.com/meta-ad-library-scraper

Scrape the public **Meta Ad Library** (ads on Facebook, Instagram, Messenger and Audience Network) as clean dataset rows, without a login or a Facebook account. Search by **keyword**, by **advertiser page** (numeric page ID), or paste an **Ad Library URL** with your filters already applied.

Example, input `{"pages": ["15087023444"], "country": "BR", "activeStatus": "all", "maxAds": 200}` returned (real run, 9 Nov 2026) 186 unique ads of the Nike page in 23 requests, 0 retries and 39 s. One of them: ad `1702938977100376`, active since 2025-04-07 on Facebook, Instagram, Audience Network and Messenger, text "Bring sports more fully into your day with the Nike App.", call to action "Install now", destination domain `itunes.apple.com`.

### What you get (one row per ad)

`adArchiveId`, `adUrl`, `pageId`, `pageName`, `pageProfileUrl`, `pageLikeCount`, `isActive`, `startDate`, `endDate`, `platforms`, `displayFormat`, `text`, `title`, `linkDescription`, `ctaText`, `linkUrl`, `linkDomain`, `imageUrls`, `videoUrls`, `cards` (carousel items), `variations` (versions of the same ad that Meta groups together), `isBrandedContent`, `searchCountry`, `searchQuery`, `sourceUrl`, `scrapedAt`.

If a search is blocked, finds nothing or an input cannot be used, you get a free status row (`blocked`, `no_results`, `error`, `invalid_input`) with a `reason` instead of ads.

### Input

| Field | Default | Notes |
|---|---|---|
| `searchTerms` | `nike` | Keywords or brand names, one search each |
| `pages` | – | Numeric Facebook page IDs, `profile.php?id=` links or Ad Library links with `view_all_page_id` |
| `libraryUrls` | – | Links copied from facebook.com/ads/library, filters included |
| `country` | `US` | `US`, `BR`, `GB`… or `ALL` |
| `activeStatus` | `active` | `active`, `inactive`, `all` |
| `mediaType` | `all` | `all`, `image`, `video`, `meme`, `none` |
| `exactPhrase` | `false` | Match the keyword as an exact phrase |
| `maxAds` | `50` | Ads per search. Volumes above 200 per search are not tested |
| `proxyConfiguration` | `Residential (Apify Proxy)` | Residential by default. See *Why Residential proxy* below |

Page names such as `facebook.com/nike` cannot be resolved without a login: use the numeric page ID (it is in the Ad Library link of that advertiser).

### Why Residential proxy is the default

The first page of results (about 27–30 ads) comes with the Ad Library page itself. The next pages come from Meta's own paging request, which Meta rate-limits (`Rate limit exceeded`, code 1675004). Without a proxy and with datacenter IPs that paging failed in every attempt of our comparison, so only the first page came back. On a rate limit the Actor waits, retries and switches to a new exit IP.

Our tests on 9 Nov 2026:

| Proxy | Result |
|---|---|
| None (Apify datacenter IP) | 3 of 3 searches returned ads, only the first page (27–30 each, 50 requested) |
| Datacenter (Apify Proxy) | 3 of 3 returned ads, only the first page (27–30 each) |
| **Residential (default)** | **10 of 10** searches returned ads; 8 of 10 reached the 50 requested, 2 stopped around 30 |

Volume test on Residential: "running shoes", US, active ads, `maxAds` 200: **200 of 200** unique ads, 21 requests, 0 retries, 41 s. Nike page, BR, all statuses, `maxAds` 200: **186** unique ads, 23 requests, 0 retries, 39 s; the run ended without a status row, probably because that is the page's total for the filter (not confirmed). Residential traffic was about 1 MB per 200 ads and is part of your Apify platform usage.

These are small samples, not a guarantee.

### Pricing

**Early access: no Actor fee yet.** You pay only standard Apify platform usage, including the Residential proxy (in our tests on 9 Nov 2026: about US$ 0.05–0.08 per 1,000 ads). Pay-per-result pricing may be introduced later; Apify notifies users 14 days in advance.

### Pairs with Google Ads Transparency

The [Google Ads Transparency Scraper](https://errandcode.com/google-ads-transparency-scraper) covers Google's ad library. This Actor returns `pageName` and `linkDomain`; the Google one returns the advertiser name and, for search ads, the display URL. You can line up the same advertiser in both libraries yourself, for example by brand name or by a domain such as `nike.com`. There is no automatic cross-matching and no monitor mode in this version.

### Limits

- Not affiliated with Meta. Independent tool.
- Public Ad Library data only, no login. Respect Meta's terms of use and check what you may do with the data.
- Meta rate-limits the paging: some searches stop around 30 ads even on Residential. Volumes above 200 ads per search were not tested.
- It depends on undocumented Ad Library internals, including a query ID that Meta can change. When that happens, runs fail with a clear status and the Actor needs maintenance.
- Partnership (branded content) ads appear under the advertiser's `pageId` with the creator's name in `pageName`; `isBrandedContent` marks them.
- Meta does not show spend or reach for non-political ads, so those figures are not collected.
- Information only, not legal or advertising advice.

### Develop

```
uv venv --python 3.13 .venv && uv pip install --python .venv/bin/python -r requirements.txt -r requirements-dev.txt
.venv/bin/python -m pytest -q
uvx ruff check .
.venv/bin/python scripts/live_local.py "nike" page:15087023444   # real request from your machine, no proxy
```

How it works: the page's own HTML carries the first ~30 ads as JSON; the following pages come from the same GraphQL endpoint the website uses. No browser is needed.

### More from Errand Code

This Actor is built and maintained by [Errand Code](https://errandcode.com/meta-ad-library-scraper). See the [other tools](https://errandcode.com/): an [Americanas Product Scraper](https://errandcode.com/americanas-product-scraper), a [Buscapé Price Scraper](https://errandcode.com/buscape-price-scraper), a [Google Ads Transparency Scraper](https://errandcode.com/google-ads-transparency-scraper), a [Google Trends Scraper](https://errandcode.com/google-trends-scraper), [YouTube Transcripts](https://errandcode.com/youtube-transcripts), a [CNPJ Due Diligence](https://errandcode.com/cnpj-due-diligence) Actor, an AI visibility check ([GEO audit](https://errandcode.com/geo)), a Google Sheets add-on and MCP servers for AI agents. Not affiliated with Meta.

# Actor input Schema

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

Words or brand names to search in ad text and advertiser names, one search per entry (e.g. `nike`, `tênis de corrida`).

## `pages` (type: `array`):

Numeric Facebook page ids, `profile.php?id=` URLs or Ad Library links with `view_all_page_id=`. Returns every ad of that advertiser.

## `libraryUrls` (type: `array`):

Links copied from facebook.com/ads/library with your filters already applied (country, status, media type, keyword or page). Filters in the URL win over the fields below.

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

Two-letter country code where the ads ran (US, BR, GB...) or ALL.

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

Which ads to return: those running now, those that stopped, or both.

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

Only ads that contain this kind of media.

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

Match the keyword as an exact phrase instead of any of its words.

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

Stop each search after this many ads (one ad = one row; variations of the same ad are grouped, see `variations`).

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

Residential Apify Proxy by default. Without a proxy or with datacenter IPs only the first page of results (~27–30 ads) comes back, because Meta rate-limits the pagination. Residential traffic is billed as Apify platform usage.

## Actor input object example

```json
{
  "searchTerms": [
    "nike"
  ],
  "country": "US",
  "activeStatus": "active",
  "mediaType": "all",
  "exactPhrase": false,
  "maxAds": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "nike"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dougmanoel/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 = {
    "searchTerms": ["nike"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("dougmanoel/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 '{
  "searchTerms": [
    "nike"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call dougmanoel/meta-ad-library-scraper --silent --output-dataset

```

## MCP server setup

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