# Facebook Ad Library Scraper - Meta & Instagram Ads (`data_ops_main/metaads-scrapper`) Actor

Scrape Facebook & Instagram ads from the Meta Ad Library, no login. Export creatives, EU reach, targeting & political spend, and monitor competitor ads.

- **URL**: https://apify.com/data_ops_main/metaads-scrapper.md
- **Developed by:** [yourlocalhost](https://apify.com/data_ops_main) (community)
- **Stats:** 9 total users, 2 monthly users, 91.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 ad-scrapeds

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

**Scrape the [Meta Ad Library](https://www.facebook.com/ads/library/) (Facebook & Instagram ads) without login or a Meta API token.** This **Facebook Ad Library scraper** returns every ad as one complete row — creative, copy, CTA, advertiser, dates, placements, political **spend** ranges, and the full **EU DSA transparency** data (total reach, reach by age/gender/country, targeting, payer and beneficiary). Built for **competitor ad research** and recurring ad monitoring, with a **monitor mode** that reports which ads are new, which stopped, and how long each has been running — the change tracking the Ad Library itself doesn't give you.

Runs on fast HTTP (no browser), so it's cheap and reliable, with Apify **scheduling, API access, integrations, and proxy rotation** built in.

- ✅ **Facebook & Instagram ads** from the public Meta Ad Library
- ✅ **No login, no cookies, no Meta Ad Library API token**
- ✅ **EU ad transparency** (reach, targeting, payer) in every row
- ✅ **Monitor mode** — track a competitor's ads over time
- ✅ **One flat row per ad**, stable versioned schema, ready for the API, Google Sheets and AI agents

### What you can do with it

- **Competitor ad research / ad spy** — pull every ad a brand is running on Facebook and Instagram, with full copy, media, CTAs and landing domains.
- **Ad creative inspiration** — browse and export winning creatives by keyword or industry.
- **Political & issue ad transparency** — spend and impression ranges, plus EU reach and targeting under the Digital Services Act (DSA).
- **E-commerce & dropshipping research** — find which products and offers competitors are promoting.
- **Agency & marketing reporting** — scheduled exports to dashboards, Google Sheets, Slack, Make or n8n.
- **AI agents & data pipelines** — a stable, versioned field contract and output schema that work through the Apify API and the Apify MCP server.

It reads only **public, logged-out** Ad Library data. No Facebook account, cookies or tokens are used.

### How to scrape the Facebook Ad Library

1. Enter one or more **search terms** (e.g. `nike`) or **advertisers** (a Facebook page URL, handle, or numeric page ID).
2. Pick **countries** (ISO-2 codes like `US`, `DE`, or `ALL`).
3. Leave **Include ad details** on to get EU transparency and advertiser enrichment.
4. Click **Start**. Results stream to the dataset; a per-query coverage report is saved to the key-value store as `SUMMARY`.

The default input (`nike`, `US`) finishes in under a minute.

### Input

| Field | Type | Description |
|---|---|---|
| `searchTerms` | string\[] | Keywords or brand names. Each term is one query. |
| `searchMatch` | enum | `unordered` (all words, any order — the UI default) or `exact` (exact phrase). |
| `advertisers` | string\[] | Pages to fetch: page URL, handle (`Zalando`), or numeric page ID. Handles/URLs are resolved to page IDs; unresolvable entries are reported in `SUMMARY`, not fatal. |
| `adLibraryUrls` | string\[] | Pasted Ad Library search URLs. Every filter in the URL is parsed; explicit form fields override URL params. |
| `adIds` | string\[] | Ad Library IDs for single-ad lookup. |
| `countries` | string\[] | ISO-2 codes or `ALL`. Each term/advertiser is queried once per country. |
| `activeStatus` | enum | `active`, `inactive`, or `all`. |
| `adType` | enum | `all`, or a regulated category: `political_and_issue_ads`, `housing_ads`, `employment_ads`, `credit_ads`, `financial_products_and_services_ads`. |
| `mediaType` | enum | `all`, `image`, `video`, `meme`, `none`. |
| `platforms` | string\[] | Subset of `facebook`, `instagram`, `messenger`, `audience_network`, `threads`. |
| `languages` | string\[] | ISO-2 content languages. |
| `startDateFrom` / `startDateTo` | date | Filter by ad start date. Meta applies this approximately (see FAQ). |
| `maxAdsPerQuery` | int | Unique ads per query (default 100). |
| `maxAdsTotal` | int | Hard cap for the whole run. |
| `includeDetails` | bool | Fetch EU transparency + advertiser enrichment (default true). |
| `monitor` | object | `{ enabled, monitorId, outputOnlyChanges }`. See Monitor mode. |
| `downloadMedia` | enum | `none`, `images`, `images_and_videos` — store creatives in the key-value store. |
| `maxMediaSizeMb` | int | Skip media files larger than this (default 50). |
| `resolveLandingPages` | bool | Follow each ad's link through redirects; record the final URL and domain. |
| `proxyConfiguration` | object | Apify Proxy by default. |

#### Example inputs

Scrape Facebook & Instagram ads by keyword in two countries:

```json
{ "searchTerms": ["running shoes"], "countries": ["US", "DE"], "maxAdsPerQuery": 200 }
```

Scrape all of a competitor's ads with EU transparency:

```json
{ "advertisers": ["https://www.facebook.com/Zalando"], "countries": ["DE"], "includeDetails": true }
```

Paste a filtered Ad Library URL:

```json
{ "adLibraryUrls": ["https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&q=sale&search_type=keyword_unordered&media_type=video"] }
```

### Output

One dataset item per unique ad. Keys are always present in the same order; unavailable values are `null`, never omitted. `schemaVersion` is `1.0`.

#### Output sample (trimmed, real)

```json
{
  "schemaVersion": "1.0",
  "adArchiveId": "2244115343095346",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=2244115343095346",
  "pageName": "Zalando",
  "pageId": "365604620536",
  "instagramUsername": "zalando",
  "instagramFollowers": 3460695,
  "pageVerification": "BLUE_VERIFIED",
  "isActive": true,
  "startDate": "2026-09-30T07:00:00.000Z",
  "endDate": null,
  "daysActive": 27,
  "publisherPlatforms": ["FACEBOOK", "INSTAGRAM"],
  "displayFormat": "VIDEO",
  "bodyText": "Entdecke die neue Kollektion …",
  "ctaText": "Jetzt shoppen",
  "linkUrl": "https://www.zalando.de/...",
  "landingDomain": "zalando.de",
  "euTotalReach": 2704876,
  "targetLocations": [{ "name": "Germany", "type": "countries", "excluded": false }],
  "targetAges": { "min": 18, "max": 65 },
  "targetGender": "All",
  "payer": "Zalando SE",
  "beneficiary": "Zalando SE",
  "detailsStatus": "ok",
  "query": "zalando",
  "queryType": "search",
  "scrapedAt": "2026-10-27T00:00:06.071Z"
}
```

#### Field reference (groups)

- **Identity** — `adArchiveId`, `collationId`, `collationCount`, `adLibraryUrl`, `schemaVersion`
- **Advertiser** — `pageId`, `pageName`, `pageUrl`, `pageProfilePicUrl`, `pageLikeCount`, `pageCategories`, `pageVerification`, `instagramUsername`, `instagramFollowers`, `byline`, `disclaimer`
- **Status & dates** — `isActive`, `startDate`, `endDate`, `daysActive`, `totalActiveTime`
- **Placement** — `publisherPlatforms`, `searchCountry`, `targetedOrReachedCountries`
- **Creative** — `displayFormat`, `bodyText`, `title`, `linkDescription`, `caption`, `ctaText`, `ctaType`, `linkUrl`, `landingUrl`, `landingDomain`, `utm`, `images`, `videos`, `cards`, `extraTexts`, `containsDigitallyCreatedMedia`, `containsSensitiveContent`
- **Political/issue** — `isPoliticalAd`, `gatedType`, `spend`, `impressions`, `demographicDistribution`, `regionDistribution`
- **EU DSA transparency** — `euTotalReach`, `reachByCountryAgeGender`, `targetLocations`, `targetAges`, `targetGender`, `payer`, `beneficiary`, `isAdTakenDown`, `detailsStatus`
- **Derived** — `creativeHash`, `variantGroupId`, `mediaStoreKeys`
- **Monitor** — `changeType`, `firstSeenAt`, `lastSeenAt`
- **Run context** — `query`, `queryType`, `scrapedAt`

**`detailsStatus`** explains every EU/detail null: `ok` (data present), `not_in_eu` (ad doesn't target the EU), `not_published` (taken down / no transparency record), `failed` (detail fetch errored — the list row is still delivered), `skipped` (`includeDetails` was off).

#### Dataset views

`overview` (key columns incl. `euTotalReach` and `detailsStatus`), `creatives`, `euTransparency`, `monitor`, and `full`.

#### SUMMARY record

Written to the key-value store. Per query: `metaReportedCount` (Meta's own result count), `delivered`, `duplicatesSkipped`, `splitsUsed`, `coveragePct`, `pagesFetched`, `retries`, `blocks`, `durationSec`, plus run totals, charged events, and any unresolved advertisers. Use `coveragePct` to confirm a run is complete.

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

This Actor is **pay-per-event**, so you pay for results, not run time:

| Event | Price | When |
|---|---|---|
| Actor start | $0.005 | Once per run |
| Ad scraped | $0.70 / 1,000 | Per unique ad delivered (EU details included) |
| Media stored | $0.50 / 1,000 | Per creative file saved (only with `downloadMedia`) |
| Landing page resolved | $0.20 / 1,000 | Per resolved URL (only with `resolveLandingPages`) |

You are charged **only for unique ads actually delivered** — never for duplicates, failed rows, monitor "stopped" markers, or ads dropped by `outputOnlyChanges`.

**Worked examples:**

- 1,000 ads with EU details, no media: ~$0.005 + $0.70 = **$0.71**.
- 5,000 ads, images downloaded (~4 files/ad), no landing: ~$0.005 + $3.50 + ($0.50 × 20) = **$13.51**.
- Daily monitor of one advertiser returning ~20 new ads/day: ~$0.005 + $0.014 = **~$0.02/day**.

### Monitor mode: track a competitor's ads over time

Meta's Ad Library has no export and no change history. Monitor mode adds both. State is kept in a named key-value store per `monitorId`.

```json
{
  "advertisers": ["Nike", "Adidas"],
  "countries": ["US"],
  "monitor": { "enabled": true, "monitorId": "sportswear-watch", "outputOnlyChanges": true }
}
```

Each run classifies every ad as `new`, `continuing`, or `stopped` (previously active, now gone), with stable `firstSeenAt`/`lastSeenAt`. With `outputOnlyChanges: true`, only new and stopped ads are pushed. Keep `maxAdsPerQuery` stable across runs on the same `monitorId` so change detection stays accurate.

**Recipe:** In the Apify Console, create a **Schedule** (e.g. daily) for this Actor with your monitor input, then add a **webhook** on "run succeeded" to Slack, Make, n8n, or Google Sheets to be notified of changes. Point the webhook at the dataset's `monitor` view, filtered to `changeType` ≠ `continuing`.

### API, Python & JavaScript

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~meta-ad-library-intelligence/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms":["nike"],"countries":["US"],"maxAdsPerQuery":100}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'APIFY_TOKEN' });
const run = await client.actor('YOUR_USERNAME/meta-ad-library-intelligence').call({
    searchTerms: ['nike'], countries: ['US'], includeDetails: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("YOUR_USERNAME/meta-ad-library-intelligence").call(
    run_input={"searchTerms": ["nike"], "countries": ["US"], "includeDetails": True}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["adArchiveId"], item["euTotalReach"])
```

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

The Actor is exposed through the [Apify MCP server](https://mcp.apify.com). Its versioned field contract and output schema let an agent call it and consume results without scraping-specific glue. Point your MCP client at `https://mcp.apify.com`, authorize, and call this Actor by name.

### How it compares to other Facebook Ad Library scrapers

| | This Actor | Typical alternatives |
|---|---|---|
| Engine | HTTP-only, no browser in the hot path | Often browser-based (2 GB+ memory) |
| EU DSA transparency | In every row, with `detailsStatus` | Hidden behind a separate view, or missing |
| Coverage reporting | `metaReportedCount` vs delivered, per query | Rarely reported |
| Monitor mode | Built in (new/stopped/continuing) | Usually absent |
| Memory | Runs at 256–512 MB | Often needs 1–2 GB |

Facts about other Actors change; check their listings for current details.

### FAQ

#### How do I scrape the Facebook Ad Library?

Enter a search term or a Facebook page, pick your countries, and run the Actor. It calls the public Ad Library the same way the website does and returns structured rows — no login and no Meta API token.

#### Is there a Meta / Facebook Ad Library API?

Meta's official Ad Library API is limited (mainly political/issue ads, with access approval). This Actor is an **API alternative** that covers **all advertisers** and exposes the data as a clean JSON dataset you can call from the Apify API, Python, JavaScript, or an AI agent.

#### Do I need to log in or provide a Facebook account?

No. Only public, logged-out data is read. No account, cookies, or access tokens.

#### Can I track a competitor's Facebook and Instagram ads over time?

Yes — use **monitor mode**. Schedule the Actor and each run reports which ads are new, which stopped, and how long each has been running.

#### Is scraping the Meta Ad Library legal?

The Ad Library is public data Meta publishes for transparency. Output may contain advertisers' personal data (e.g. payer names); **GDPR and other compliance obligations are the user's responsibility.**

#### Why is a field null?

Fields map to `null` when Meta doesn't expose them. For EU/detail fields, read `detailsStatus`.

#### Why is EU reach missing on US ads?

EU DSA transparency exists only for ads targeting the EU. `detailsStatus` will be `not_in_eu`.

#### Why don't my media URLs work later?

Meta's CDN URLs expire after a few days. Use `downloadMedia` to persist creatives in the key-value store.

#### My date filter looks approximate.

Meta applies the start-date filter loosely in the public Ad Library; the Actor sends exactly what the UI sends. Filter precisely downstream on `startDate` if you need a hard cutoff.

#### A page handle wasn't resolved.

Meta's page typeahead is login-gated. Supply the numeric page ID or a page URL for guaranteed resolution; unresolved handles are listed in `SUMMARY`.

### Changelog

See [CHANGELOG.md](CHANGELOG.md).

# Changelog

This Actor's version history is a separate document: https://apify.com/data_ops_main/metaads-scrapper/changelog.md

# Actor input Schema

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

Keywords or brand names to search for, e.g. `nike` or `running shoes`. Each term is one query.

## `searchMatch` (type: `string`):

`unordered` finds ads containing all words in any order (what the Ad Library UI does). `exact` finds the exact phrase only.

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

Facebook pages whose ads to fetch. Accepts a page URL (`https://www.facebook.com/Zalando`), a page handle (`Zalando`), or a numeric page ID (`365604620536`). Handles and URLs are resolved to page IDs automatically; entries that cannot be resolved are reported in the SUMMARY record instead of failing the run.

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

Pasted Ad Library search URLs (`https://www.facebook.com/ads/library/?...`). Every filter in the URL is parsed and used. Filters you set explicitly in this form override the URL's parameters. The effective filters are logged at the start of the run.

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

Ad Library IDs (the `id` parameter of an ad's detail URL) for single-ad lookup, e.g. `1550534860016399`.

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

ISO-2 country codes (e.g. `US`, `DE`) or `ALL`. Each search term or advertiser is queried once per country listed here.

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

Whether to return ads that are currently running, no longer running, or both.

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

Restrict results to a regulated ad category, or search all ads.

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

Restrict results by creative format.

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

Only return ads shown on the selected platforms. Leave empty for all platforms.

## `languages` (type: `array`):

ISO-2 language codes (e.g. `en`, `de`). Only return ads whose content is in one of these languages. Leave empty for all languages.

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

Only return ads that started running on or after this date (`YYYY-MM-DD`).

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

Only return ads that started running on or before this date (`YYYY-MM-DD`).

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

Stop paginating a query after this many unique ads. The SUMMARY record reports Meta's total result count per query so you can see what a higher limit would fetch.

## `maxAdsTotal` (type: `integer`):

Hard cap on unique ads pushed by the whole run, across all queries. Leave empty for no cap.

## `includeDetails` (type: `boolean`):

Fetch each ad's detail record: EU DSA transparency (total EU reach, reach by age/gender/country, target locations, ages and gender, payer and beneficiary) plus advertiser enrichment (Instagram username and followers, page likes, verification). Adds roughly one extra request per ad. Every row has a `detailsStatus` field explaining why detail fields are null when they are.

## `monitor` (type: `object`):

`{ "enabled": true, "monitorId": "my-competitors", "outputOnlyChanges": false }`. `monitorId` names the state store, so use the same value on every scheduled run. With `outputOnlyChanges` true, only new and stopped ads are pushed.

## `downloadMedia` (type: `string`):

Store ad creatives in the run's key-value store. Meta's CDN URLs expire after a few days, so enable this when you need the creatives later. Stored files are billed per file.

## `maxMediaSizeMb` (type: `integer`):

Files larger than this are skipped when downloading media.

## `resolveLandingPages` (type: `boolean`):

Follow each ad's link through redirects (up to 5) and record the final URL and domain. Billed per resolved URL.

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

Proxy to use. The default automatic Apify Proxy starts on the cheapest group that works and escalates to residential only when blocked; every escalation is logged.

## Actor input object example

```json
{
  "searchTerms": [
    "nike"
  ],
  "searchMatch": "unordered",
  "advertisers": [],
  "adLibraryUrls": [],
  "adIds": [],
  "countries": [
    "US"
  ],
  "activeStatus": "active",
  "adType": "all",
  "mediaType": "all",
  "platforms": [],
  "languages": [],
  "maxAdsPerQuery": 50,
  "includeDetails": true,
  "monitor": {
    "enabled": false,
    "monitorId": "",
    "outputOnlyChanges": false
  },
  "downloadMedia": "none",
  "maxMediaSizeMb": 50,
  "resolveLandingPages": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `ads` (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 = {
    "searchTerms": [
        "nike"
    ],
    "advertisers": [],
    "adLibraryUrls": [],
    "adIds": [],
    "countries": [
        "US"
    ],
    "maxAdsPerQuery": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("data_ops_main/metaads-scrapper").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"],
    "advertisers": [],
    "adLibraryUrls": [],
    "adIds": [],
    "countries": ["US"],
    "maxAdsPerQuery": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("data_ops_main/metaads-scrapper").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"
  ],
  "advertisers": [],
  "adLibraryUrls": [],
  "adIds": [],
  "countries": [
    "US"
  ],
  "maxAdsPerQuery": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call data_ops_main/metaads-scrapper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data_ops_main/metaads-scrapper"
        }
    }
}
```

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/mqgNmB8ugn2vd2abe/builds/9eY2R1saoiBZi3apg/openapi.json
