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

Scrape public Facebook and Instagram ads by keyword or exact advertiser. Export creatives, stable IDs and dates with pagination reports and only-new monitoring.

- **URL**: https://apify.com/benthepythondev/meta-ad-library-scraper.md
- **Developed by:** [Ben](https://apify.com/benthepythondev) (community)
- **Categories:** Marketing, Social media, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Meta Ad Library Scraper

Export public Facebook and Instagram advertising creatives from Meta Ad Library. Search by keyword or specify exact advertiser Page IDs, then inspect stable ad IDs, text, source links, dates and media URLs. Export to JSON, CSV or Excel, run on a schedule, call via API, or connect to Make, Zapier or n8n.

**Check all charge events before a large search:** on the Free plan, results cost **$2 per 1,000 ads**, a search costs **$0.08**, and each additional search page costs **$0.03**, plus the start event. `onlyNew` skips known ad results but still incurs search costs. This is creative research, not an ad-performance or spend report.

### Quick start

Start with a tested example for an advertiser creative feed:

- [Nike ads in Germany with verified advertiser IDs](https://apify.com/benthepythondev/meta-ad-library-scraper/examples/nike-germany-verified-ad-creatives)

Open the example and choose **Try for free** to copy its input into your account. Check the input and set a maximum run charge before starting; Actor fees still follow the pricing below. When the run finishes, open the run's dataset and select the **Overview** view and download JSON for nested fields or CSV/Excel for a table.

| Your goal | Input to use |
|---|---|
| Research a known advertiser | `pageIds` with a country |
| Explore keyword matches | Omit Page IDs and supply `keyword` |
| Observe newly seen ad IDs | `onlyNew` with a stable `monitorId` |

The [real output example](#example-output) shows the record shape. The [Python workflow](#python-api-workflow) saves your own export to a local JSON file with a $0.30 maximum Actor charge.

### What does this scraper extract?

This Actor uses the public Ad Library search in a browser, without supplying a Facebook login or purchasing data from another Actor. It reads search results and subsequent result pages, checks advertiser identity in Page-ID mode and removes repeated ad IDs. A keyword search can return multiple advertisers; those results retain their actual Page names and IDs.

Meta sometimes groups several ads or creative variants in one result card. The Actor exports distinct ad archive IDs, preserves the creative variants exposed in each snapshot and reports the number of unique source ads observed. A displayed group count is not treated as a count of exportable ads.

Use it to monitor a known advertiser's new creatives, collect advertising examples for research, compare public messaging in a market, or build a repeatable feed of newly observed ads. Facebook, Instagram and other publisher platforms are reported exactly as the source supplies them; an ad can run on several platforms.

### Input

| Input | Default | Behavior |
|---|---|---|
| `keyword` | "Nike" | Meta keyword search. Explicit Page IDs take precedence, including over the default keyword. |
| `pageIds` | "optional" | Up to ten numeric Page ID strings. Every returned ad must match the requested advertiser. Overrides keyword. |
| `country` | "DE" | Two-letter country code such as DE, US or GB; ALL searches all available countries. |
| `activeStatus` | "active" | Select active, inactive or all ads available in the public library. |
| `maxResults` | 10 | Cap on matching ads inspected across all searches, including already-seen ads in monitor mode. Not a guarantee of this many new results. |
| `maxPages` | 5 | Bounded source pages. SUMMARY reports the actual stop reason and incomplete coverage. |
| `strictKeyword` | false | Checks every whitespace-separated keyword word locally. Ignored in exact advertiser mode. |
| `startDateFrom` | "optional" | Optional inclusive YYYY-MM-DD UTC date. Filters the scanned window, not the entire library. |
| `startDateTo` | "optional" | Optional inclusive YYYY-MM-DD UTC date. Ads with missing dates are excluded when a date bound is used. |
| `onlyNew` | false | Persist seen IDs in your account and export new ones on later runs of this monitor. |
| `monitorId` | "optional" | Optional stable monitor name. Use a distinct value for each search; otherwise derived from search filters. |
| `firstRunMode` | "emitAll" | Seed-only establishes a baseline; later runs export only new IDs within the scanned window. |
| `resetState` | false | Reset only this monitor state. Switch off again for subsequent scheduled runs. |
| `proxyConfiguration` | German residential proxy | Verified cloud route; public static bundles load directly. |

#### Example input

```json
{
  "pageIds": [
    "15087023444"
  ],
  "country": "DE",
  "activeStatus": "active",
  "maxResults": 10,
  "maxPages": 3,
  "onlyNew": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DE"
  }
}
```

This requests ads for Nike's Page ID in Germany. Page IDs override the keyword, so the default `Nike` keyword cannot contaminate an exact-advertiser request. You can obtain a public Page ID from Ad Library or the [Facebook Pages Actor](https://apify.com/benthepythondev/facebook-pages-scraper). Supplying a personal profile ID does not turn it into an advertiser.

For keyword research, omit Page IDs. Enable `strictKeyword` if every keyword word must appear in the returned advertiser or creative. This is a local word check, not a semantic relevance score. Date bounds use the ad's start date in UTC and include both boundary dates. They filter the scanned window; they do not guarantee discovery of every historical match.

### Example output

One complete record from a verified September 9, 2026 cloud run. Values and public media URLs can change or expire; this is a dated sample.

```json
{
  "ad_id": "28093431340342783",
  "ad_url": "https://www.facebook.com/ads/library/?id=28093431340342783",
  "page_id": "15087023444",
  "page_name": "Nike",
  "page_url": "https://www.facebook.com/nike/",
  "creative_page_id": "15087023444",
  "creative_page_name": "Nike",
  "creative_page_url": "https://www.facebook.com/nike/",
  "is_partnership_ad": false,
  "is_active": true,
  "start_date": "2026-08-10T07:00:00+00:00",
  "source_end_date": "2026-09-09T07:00:00+00:00",
  "body": "Vom ersten Training bis zum Schlusspfiff: Entdecke Basketball-Essentials für einen starken Auftritt bei jeder Partie.",
  "title": "Finde deinen Flow",
  "caption": "fb.com",
  "link_url": "https://fb.com/canvas_doc/1678531143227828",
  "link_description": null,
  "cta_text": "Shop now",
  "display_format": "VIDEO",
  "creative_variants": [],
  "image_urls": [],
  "video_urls": [
    "https://video-fra3-1.xx.fbcdn.net/o1/v/t2/f2/m366/AQPSADM-Enxo3uSlcLMidGPDSWBtXcDQ1m2ppKuyoQj2i17yj7QKhxvkoRjy5rsJjs8sVtLCxTyNUCDFou2m4q4F5Lbcijd7waYLnBYikH8cOg.mp4?_nc_cat=103&_nc_sid=b66105&_nc_ht=video-fra3-1.xx.fbcdn.net&_nc_ohc=5KW-9OtPbW8Q7kNvwHNOdnS&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5WSV9VU0VDQVNFX1BST0RVQ1RfVFlQRS4uQzMuNzIwLmRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHAiLCJ4cHZfYXNzZXRfaWQiOjI3OTg0MjM0NjI3ODU5NjQ2LCJhc3NldF9hZ2VfZGF5cyI6MzQsInZpX3VzZWNhc2VfaWQiOjEwMTM5LCJkdXJhdGlvbl9zIjo2LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&vs=d02e3e3efcf778d8&_nc_vs=HBksFQIYRWZiX2VwaGVtZXJhbC85ODQ4OTlBQTUxRkIyNjY5QzVFNjE1M0FEN0UyQjc5RF9tdF8xX3ZpZGVvX2Rhc2hpbml0Lm1wNBUAAsgBEgAVAhhAZmJfcGVybWFuZW50L0NBNDEzNzcwOEE1RTY0RTdBOUFBOEExMEQ1RUJGM0I4X2F1ZGlvX2Rhc2hpbml0Lm1wNBUCAsgBEgAoABgAGwKIB3VzZV9vaWwBMRJwcm9ncmVzc2l2ZV9yZWNpcGUBMRUAACb8kvDE6-C1YxUCKAJDMywXQBgAAAAAAAAYGWRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHARAHUAZbaeAQA&_nc_gid=wMFTuOq8MfMOcXGgH5Apsg&_nc_ss=7f289&_nc_zt=28&oh=00_AQK9EbJJvxfMkw19hox9IvRZuUc0cMDv0IGvpcA0lZa7rQ&oe=6AA77655"
  ],
  "publisher_platforms": [
    "FACEBOOK",
    "INSTAGRAM",
    "AUDIENCE_NETWORK",
    "MESSENGER",
    "THREADS"
  ],
  "spend": null,
  "reach_estimate": null,
  "currency": null,
  "targeted_or_reached_countries": [],
  "spend_reach_scope": "Search response only; null means not supplied, not zero.",
  "search_url": "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=DE&media_type=all&view_all_page_id=15087023444&search_type=page",
  "country": "DE",
  "scraped_at": "2026-09-09T19:38:39.979239+00:00",
  "is_new": false,
  "monitor_id": null
}
```

Partnership ads keep the advertiser in `page_id`/`page_name` and the creative owner in `creative_page_id`/`creative_page_name`/`creative_page_url`. A different creative owner is accepted only when Meta explicitly attributes branded content to the requested advertiser. `is_partnership_ad` marks this case.

Each dataset row also includes the Page URL; creative title, caption, call to action and destination URL; image/video URLs; creative text variants; publisher platforms; search URL and country; and UTC extraction timestamp. `spend`, `reach_estimate`, `currency` and targeted/reached countries contain only values present in the public search response. Null means unavailable, never zero. This release does not open an ad's separate detail panel or infer spend/reach for unsupported regions.

`source_end_date` preserves Meta's source timestamp. For an active ad, that value can be the source observation boundary rather than the date an advertiser stopped the ad. Use `is_active` to interpret it. Media links can expire, and this Actor exports URLs rather than downloading media files. Dynamic creatives may keep template text in the main body while exposing text variants separately in `creative_variants`.

The Output tab links to the default dataset and the `SUMMARY` key-value record. Summary fields include per-search pages, unique source IDs, rejected rows and stop reasons, plus matching/exported totals, skipped known ads and `coverage_complete`. Result/page limits or the run's time bound produce an explicitly incomplete scan. A missing source connection or stalled pagination marks the run failed while preserving any exported rows and diagnostic summary. Inspect this before treating a partial export as a complete archive.

### Only-new monitoring

Enable `onlyNew` and reuse a stable `monitorId` on an Apify schedule. The first run normally exports matching ads; choose `seedOnly` to establish a baseline without dataset rows. Later runs omit previously observed IDs within the scanned window. Seen IDs live in a named key-value store in the caller's account, isolated by monitor key.

The monitor retains up to 5,000 IDs observed within 90 days. It detects new IDs, not edits to existing creatives or ad shutdowns. A changed search with the same explicit monitor ID intentionally shares state; use separate IDs for separate searches. `maxResults` counts matching ads inspected, including known IDs. Increase the scan bounds if old ads fill the first pages. A schedule is not an exhaustive historical archive.

The Actor marks an ID after successful dataset storage and the result charge, so a spending-limit stop leaves unexported ads eligible for a later run. No result events are created for known ads or a seed-only baseline. If storage fails, retry and reconcile by `ad_id`; an interrupted run can leave a partial dataset. Use `resetState` only for an intentional fresh baseline, then turn it off again before scheduling.

### Pricing

This Actor charges for scanning and for exported ads. On Free, a keyword search or advertiser costs **$0.08 to initialize**, **$0.03 per parsed source page**, and **$0.002 per exported ad ID**. A start event adds $0.00005 per GB, minimum one; default 2 GB adds $0.00010. A single-page search exporting ten ads is therefore **$0.13010** on Free. A repeated one-page monitor with no new ads is **$0.11010**.

Search, page and ad events are 10% lower on Bronze, 15% lower on Silver and 20% lower on Gold. The currently saved Platinum and Diamond rates equal Gold. The start event also has plan discounts: $0.000045 per GB on Bronze, $0.000040 on Silver, and $0.000035 on Gold, Platinum and Diamond. The Store pricing panel is authoritative. Exported ads alone are $2/1,000 on Free; scan and start charges are additional. Do not treat this as the total price of an arbitrary search.

A search initialization event is charged only after the first public result connection loads. Each supplied advertiser is a separate search. Seed-only runs, date-filtered pages and pages containing already-known IDs still consume resources and incur scan charges. They create no result events unless an ad is exported. A source attempt that never reaches a result connection creates no search/page events.

The verified cloud route uses residential proxies. Public static bundles load directly to reduce proxy traffic. There is no paid upstream API key. Use result/page caps and Apify's maximum charge control; a small charge cap may stop after scan fees with no exported ads. The run summary reports that stop. This is a maintained browser and monitoring workflow, with different costs from a bulk-only ad feed.

### Python API workflow

Copy the first input JSON above into `input.json` and save this code as `export.py`. In a Python virtual environment, install `apify-client` with `python -m pip install apify-client`. Set `APIFY_TOKEN` to your own Apify token in your environment, then run `python export.py`. Keep the token out of source files and shared screenshots.

```python
import json
import os
from decimal import Decimal
from pathlib import Path

from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("benthepythondev/meta-ad-library-scraper").call(
    run_input=json.loads(Path("input.json").read_text(encoding="utf-8")),
    max_total_charge_usd=Decimal("0.30"),
    memory_mbytes=2048,
    timeout_secs=300,
)
if not run or run["status"] != "SUCCEEDED":
    raise RuntimeError(f"Inspect the run before retrying: {run and run['id']}")

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
Path("nike-ad-creatives.json").write_text(
    json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(f"Saved {len(rows)} records from run {run['id']}")
```

This starts a run in your account, waits for completion and saves `nike-ad-creatives.json`. The $0.30 limit bounds Actor charges; it is not a promised bill or result count. Check the printed run ID and server log if the result is shorter than expected. If your client loses its connection, inspect the existing run in Console before starting another one. See the [official Python client](https://docs.apify.com/api/client/python/) for other run options.

Import by `ad_id` and retain advertiser and creative-owner IDs separately. The example exports a snapshot. To monitor new IDs, use your own saved Task and the only-new settings below. Search/page charges still apply when no new ad is exported; an empty repeat is not evidence that a campaign stopped.

For recurring runs without writing a scheduler, copy a tested public Task into your account and attach an Apify Schedule to that copy. Keep its maximum-charge limit and avoid overlapping runs when using monitor state. The Python example above is also suitable for an existing scheduler; run it from the directory containing `input.json` and provide the token through that scheduler's secret settings.

### FAQ

**Does it need a Facebook account?** No account or session cookie is supplied. Access still depends on Meta making the public search available from the run environment.

**Does it cover Instagram ads too?** Yes, when Ad Library returns them. Check `publisher_platforms`; this is not an Instagram profile or organic-post scraper.

**Why are there fewer ads than Meta displays?** Displayed groups, visibility restrictions, filtering and scan limits can all differ from exported unique IDs. Read `SUMMARY` rather than assuming complete coverage.

**Can I get advertiser spend?** Only if the search response supplies it. This release does not fetch separate transparency-detail records and does not estimate missing values.

**Can I retrieve every deleted or inactive ad?** No. Country and category retention rules control source availability. Selecting inactive or all cannot recover records the public source does not expose.

**Will a zero-result monitor run mean the advertiser stopped advertising?** No. It can mean all scanned IDs were already known or no matching record appeared in the bounded search. It is not evidence of a campaign shutdown.

**Can I use it from n8n or an API?** Yes. Run a saved Task, read its dataset and retain `ad_id` as your downstream key. Seed-only and repeated monitor runs may intentionally return an empty dataset.

**How should I use the data?** Follow applicable law and platform terms. Public ad visibility does not grant permission to reuse copyrighted creative assets for unrelated campaigns.

### Related Actors

[Facebook Pages](https://apify.com/benthepythondev/facebook-pages-scraper) supplies organizational Page metadata and public contacts. [LinkedIn Company Scraper](https://apify.com/benthepythondev/linkedin-company-scraper) exports verified public company snapshots.

**Keywords:** Meta Ad Library scraper, Facebook ads scraper, Instagram ads, advertiser monitoring, ad archive ID, creative research, new ad alerts, Page ID search, advertising data, ad start dates, creative variants, marketing research, JSON export, scheduled ads feed.

# Actor input Schema

## `keyword` (type: `string`):

Meta keyword search. Explicit Page IDs take precedence, including over the default keyword.

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

Up to ten numeric Page ID strings. Every returned ad must match the requested advertiser. Overrides keyword.

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

Two-letter country code such as DE, US or GB; ALL searches all available countries.

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

Select active, inactive or all ads available in the public library.

## `maxResults` (type: `integer`):

Cap on matching ads inspected across all searches, including already-seen ads in monitor mode. Not a guarantee of this many new results.

## `maxPages` (type: `integer`):

Bounded source pages. SUMMARY reports the actual stop reason and incomplete coverage.

## `strictKeyword` (type: `boolean`):

Checks every whitespace-separated keyword word locally. Ignored in exact advertiser mode.

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

Optional inclusive YYYY-MM-DD UTC date. Filters the scanned window, not the entire library.

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

Optional inclusive YYYY-MM-DD UTC date. Ads with missing dates are excluded when a date bound is used.

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

Persist seen IDs in your account and export new ones on later runs of this monitor.

## `monitorId` (type: `string`):

Optional stable monitor name. Use a distinct value for each search; otherwise derived from search filters.

## `firstRunMode` (type: `string`):

Seed-only establishes a baseline; later runs export only new IDs within the scanned window.

## `resetState` (type: `boolean`):

Reset only this monitor state. Switch off again for subsequent scheduled runs.

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

Residential proxy required by the verified Apify route. Select the country appropriate for your search. Public static bundles load directly to reduce proxy traffic.

## Actor input object example

```json
{
  "keyword": "Nike",
  "country": "DE",
  "activeStatus": "active",
  "maxResults": 10,
  "maxPages": 5,
  "strictKeyword": false,
  "onlyNew": false,
  "firstRunMode": "emitAll",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DE"
  }
}
```

# 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 = {
    "keyword": "Nike"
};

// Run the Actor and wait for it to finish
const run = await client.actor("benthepythondev/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 = { "keyword": "Nike" }

# Run the Actor and wait for it to finish
run = client.actor("benthepythondev/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 '{
  "keyword": "Nike"
}' |
apify call benthepythondev/meta-ad-library-scraper --silent --output-dataset

```

## MCP server setup

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