# Meta Ads Library Scraper (`m3web/meta-ads-library-scraper`) Actor

Scrape Facebook & Instagram ads from the Meta Ads Library by website domain. Exact-domain matching. Returns structured JSON: ad text, headline, landing page, dates, platforms, images, videos, product cards. Filter by country, status, platform, media type. API and MCP ready.

- **URL**: https://apify.com/m3web/meta-ads-library-scraper.md
- **Developed by:** [M3Web](https://apify.com/m3web) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Meta Ads Library Scraper do?

**Meta Ads Library Scraper** finds the **Facebook and Instagram ads that a website is running**. Enter one or more **domains** (for example `nike.com`) and the Actor returns the matching ads from the public [Meta Ads Library](https://www.facebook.com/ads/library/) as clean, structured data: ad text, headline, landing page, start and end dates, platforms, images, videos and product cards.

It matches the **exact domain**, so you get the ads for that website, not a loose keyword search. Because it runs on Apify, you also get API access, scheduling, webhooks, integrations and an MCP tool for AI agents.

### Why use Meta Ads Library Scraper?

- **Competitor research:** see what your competitors advertise, which products they push and how their ads change.
- **Lead generation and sales:** find companies that actively spend on Meta ads.
- **Agency reporting:** pull a client's live ads, or a whole list of brands, in one run.
- **AI agents:** answer "what ads is example.com running in Germany?" through the Apify MCP server.

#### Why the results are more precise

The Actor searches Meta's Ads Library for the **exact domain phrase**. Scrapers that use a broader keyword search often return ads from other advertisers that only mention similar words. With exact-domain matching, the result list stays close to the ads that belong to the website you asked about. Meta shows an approximate total (for example "~280 results") and the Actor reports it in the run summary so you can compare.

#### Which ads do you get first?

Ads are **sorted by total impressions, highest first** (the same ranking as Meta's own "most impressions" sort). If a domain has 280 ads and you set `maxAdsPerDomain` to 50, you get the **50 ads with the most impressions**, not the 50 newest.

### How to scrape Meta ads by domain

1. Open the **Input** tab and enter one or more domains, for example `nike.com`.
2. Optionally choose a country, ad status, platforms and media type.
3. Click **Start**. A single domain with up to 30 ads usually finishes within seconds.
4. Open the **Output** tab and download the ads as JSON, CSV, Excel or HTML, or read them through the API.

You can paste domains in any form. The Actor removes `https://`, `www.`, paths and query strings, and combines duplicates, so each unique domain is checked **once** and charged **once**, even if it appears several times in your list.

Only **website domains** are searched. Entries that are not valid domains, such as a brand name or a keyword (`nike`, `test`), are skipped and **not charged**, so you never pay for a result that is not about a website.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `domains` | array of strings | required | 1 to 100 domains, such as `["nike.com"]`. Full URLs are cleaned to the domain. Use domains, not brand names or keywords; other entries are skipped and not charged. |
| `maxAdsPerDomain` | integer | `30` | 1 to 500 ads per domain, highest total impressions first. |
| `country` | string | `ALL` | 2-letter country code (for example `DE`, `US`) or `ALL`. |
| `activeStatus` | string | `active` | `active` for ads running now, `all` to include past ads. |
| `publisherPlatforms` | array | all | Any of `facebook`, `instagram`, `audience_network`, `messenger`, `whatsapp`, `threads`. |
| `mediaType` | string | `all` | `all`, `image`, `meme`, `image_and_meme`, `video`, `none`. |

No proxy setup is needed. For larger requests the Actor handles it automatically, and the cost is included in the price.

#### Example input

```json
{
  "domains": ["nike.com", "adidas.com"],
  "maxAdsPerDomain": 50,
  "country": "US",
  "activeStatus": "active"
}
```

### Output

The dataset has one row per ad, and **every domain you enter appears in the results**:

- **Ad rows** have `hasAds: true` and all ad fields.
- A domain with **no ads** gets one row with `hasAds: false`. Only `domain`, `hasAds` and `scrapedAt` are filled, all other values are `null`.
- A domain that **could not be checked** (for example when Meta did not respond) gets one row with `hasAds: null`. It is not charged.
- An entry that is **not a valid domain** also gets one row with `hasAds: null` and is not charged. The summary marks it `invalid`.
- A domain that was **skipped** because the run reached its time limit or your maximum cost gets one row with `hasAds: null` and is not charged. The summary marks it `skipped`.

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

```json
{
  "domain": "alternate.de",
  "hasAds": true,
  "adArchiveId": "1793908174954881",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1793908174954881",
  "pageName": "ALTERNATE",
  "headline": "ASUS PRO WS W790-ACE, Mainboard",
  "bodyText": "Warum noch zögern? Dein Setup verdient mehr! ...",
  "landingPageUrl": "https://www.alternate.de/listing.xhtml?b=1220&bc=11",
  "ctaText": "Shop now",
  "isActive": true,
  "startDate": "2026-07-27T07:00:00.000Z",
  "platforms": ["FACEBOOK", "INSTAGRAM", "AUDIENCE_NETWORK", "THREADS"],
  "displayFormat": "DPA",
  "cards": [
    { "title": "ASUS PRO WS W790-ACE, Mainboard", "linkUrl": "https://www.alternate.de/...", "imageUrl": "https://..." }
  ]
}
```

#### Data fields

| Field | Description |
|---|---|
| `domain` | The domain from your input this row belongs to |
| `hasAds` | `true` on ad rows, `false` for a domain without ads, `null` if it could not be checked or is not a valid domain |
| `adArchiveId`, `adLibraryUrl` | Meta's ad ID and a link to the ad in the Ads Library |
| `pageName`, `pageId`, `pageProfileUrl`, `pageLikeCount` | The advertiser's Facebook page |
| `headline`, `bodyText`, `linkDescription` | Ad headline and texts |
| `landingPageUrl`, `ctaText` | Where the ad sends people and its button text |
| `isActive`, `startDate`, `endDate` | Status and dates (ISO 8601, UTC) |
| `platforms`, `displayFormat` | Where the ad runs and its format (IMAGE, VIDEO, CAROUSEL, DPA) |
| `imageUrls`, `videoUrls` | All creative URLs of the ad |
| `cards` | Slides of carousel and catalog ads: product title, link and image |

Every field is described in the Actor's dataset schema.

#### Run summary

Each run also saves a `SUMMARY` record in the key-value store with one entry per domain: its status (`ok`, `no_ads`, `partial`, `skipped`, `invalid`, `error`, `no_ads_read`), the number of ads returned, Meta's approximate total, and a note. It is not part of the dataset and not charged. Check it when a domain returns fewer ads than you expected.

If Meta's Ads Library is not responding and no domain could be checked, the run **fails** with a clear message, so you (or your agent) know to try again later. That is different from a domain that simply has no ads. If none of the entries is a valid domain, the run also fails right away with a message saying so.

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

You pay per event, and **platform and proxy costs are included**, so there are no extra usage charges and no start fee:

- **Domain checked:** $0.005 per unique domain, on every plan. It applies whether the domain has ads or not. Domains that could not be checked are not charged.
- **Ad scraped:** a price per ad that depends on your Apify plan. It applies to every ad saved.

| Apify plan | Domain checked | Ad scraped | Per 1,000 ads |
|---|---|---|---|
| Free | $0.005 | $0.001 | $1.00 |
| Starter | $0.005 | $0.0006 | $0.60 |
| Scale | $0.005 | $0.00055 | $0.55 |
| Business | $0.005 | $0.0005 | $0.50 |

Examples for one domain:

| Result | Free | Starter | Scale | Business |
|---|---|---|---|---|
| No ads found | $0.005 | $0.005 | $0.005 | $0.005 |
| 30 ads | $0.035 | $0.023 | $0.0215 | $0.020 |
| 100 ads | $0.105 | $0.065 | $0.060 | $0.055 |

You can set a maximum cost per run in the Apify Console. The Actor stops when it is reached and marks the affected domains in the summary.

### Tips for faster and cheaper runs

- **Keep `maxAdsPerDomain` at 30** when you only need a sample of the most-seen ads. Up to about 30 ads is the fastest.
- **Set a country** to match the market you care about. Results are filtered by the ads shown there.
- **Use `activeStatus: active`** to skip old ads.
- **Group domains in one run** instead of starting many runs. Several domains are processed in parallel.
- **Screening a long list?** If you only need to know which domains advertise at all, my companion Actor [Meta Ads Checker](https://apify.com/m3web/meta-ads-checker) is a cheaper yes/no check. Then scrape just the domains with ads here.

### Use Meta Ads Library Scraper with AI agents (MCP)

Add this Actor as a tool to the Apify MCP server:

```
https://mcp.apify.com/?tools=m3web/meta-ads-library-scraper
```

Example prompts for your agent:

- "What ads is nike.com running on Instagram in the US right now?"
- "List the products adidas.com is advertising in Germany."
- "Compare how many active ads alternate.de and mediamarkt.de run."

The Actor accepts plain domains or URLs and checks each unique domain once. It returns flat, documented fields, and every domain gets at least one row, so an agent can tell `hasAds: true`, `false` (no ads) and `null` (could not be checked or not a valid domain) apart without guessing. The per-domain summary adds the exact status and a note.

### FAQ, disclaimers and support

**Why did I get fewer ads than `maxAdsPerDomain`?**
The domain may have fewer ads, or not all of them could be loaded in this run. Check the `SUMMARY` record for the status and a note. Meta's total is approximate and can differ slightly from the number of rows.

**Which ads come first if I set a limit?**
The ads with the most total impressions. Sorting by date is not available yet.

**Can I search by brand name, keyword or Facebook page?**
No. The Actor searches for the exact website domain, so a brand name or keyword is skipped and not charged. Use the brand's website domain.

**Can I get impressions, spend or audience data?**
`impressionsText` is filled only when Meta publishes it, mainly for political and social-issue ads. This Actor does not collect audience breakdowns.

**Do image and video links keep working?**
They are signed by Meta and can stop working after a few days. Download files you need to keep.

**Is it legal to scrape the Meta Ads Library?**
The Actor reads publicly available pages of Meta's Ads Library. You are responsible for using the data in line with applicable laws, Meta's terms and data protection rules. Do not use it to collect personal data.

**Found a problem or need another field?**
Open an issue in the **Issues** tab of this Actor. Custom solutions are available on request.

# Actor input Schema

## `domains` (type: `array`):

Website domains to look up in the Meta Ads Library, for example `nike.com`. Returns the ads that match that exact domain. Use domains, not brand names or search terms: entries that are not valid domains are skipped and not charged. Full URLs are accepted and cleaned to the domain. Between 1 and 100 entries.

## `maxAdsPerDomain` (type: `integer`):

Maximum number of ads to return for each domain (1-500). Ads are sorted by total impressions, highest first, so a limit returns the most-seen ads. Up to about 30 ads is the fastest; larger requests take longer.

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

Only ads shown in this country, as a 2-letter country code such as `DE` or `US`. Use `ALL` to search every country.

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

`active` returns only ads running now. `all` also includes inactive ads the advertiser has run in the past.

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

Limit results to ads shown on these platforms. Leave empty for all platforms.

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

Filter by ad creative type. `all` returns every type.

## Actor input object example

```json
{
  "domains": [
    "nike.com"
  ],
  "maxAdsPerDomain": 30,
  "country": "ALL",
  "activeStatus": "active",
  "publisherPlatforms": [],
  "mediaType": "all"
}
```

# Actor output Schema

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

One row per ad.

## `summary` (type: `string`):

Per-domain status: ads returned, approximate total ads, and any notes (for example when only the first ads could be collected).

# 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 = {
    "domains": [
        "nike.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("m3web/meta-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 = { "domains": ["nike.com"] }

# Run the Actor and wait for it to finish
run = client.actor("m3web/meta-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 '{
  "domains": [
    "nike.com"
  ]
}' |
apify call m3web/meta-ads-library-scraper --silent --output-dataset

```

## MCP server setup

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