# Meta & Facebook Ad Library Scraper: Ads, Hooks, Landing Intel (`ntriqpro/meta-ad-library-intel`) Actor

Facebook and Meta Ad Library scraper for competitor ad research: give page URLs, page IDs or keywords and get every ad, creative image and video URLs, hook analysis of the ad copy, landing domain profiles, an advertiser summary and competitor comparisons. Batch input, pay per result.

- **URL**: https://apify.com/ntriqpro/meta-ad-library-intel.md
- **Developed by:** [daehwan kim](https://apify.com/ntriqpro) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.40 / 1,000 advertiser summaries

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

## Meta & Facebook Ad Library Scraper: Ads, Hooks, Landing Intel

**Paste competitor Facebook pages, page IDs or keywords. Get every ad they run, and what it all
adds up to.** One page turns into many rows: the advertiser, one row per ad (text, headline, CTA,
landing URL, start date, days running, platforms), every image and video URL, a hook analysis of each
ad text, a profile of every landing domain the ads point to, and a one-row advertiser summary. Enter
two or more competitors and switch on comparisons to get one row per pair, side by side.

> This Actor is an **independent, unofficial tool**. It is **not affiliated with, endorsed by, or
> sponsored by** Meta Platforms, Facebook, Instagram, rdap.org or any advertiser it reads. It reads the
> **public, logged-out** Meta Ad Library the same way a browser does: no login, no cookies from any
> account, no captcha solving, no account pools. Creative images and videos are **linked, never
> downloaded or re-hosted**. **You are responsible** for using the output for a lawful purpose, for
> complying with Meta's terms of service, and for respecting the rights in the advertisers' pages and
> creatives. Every result row carries this notice in its `responsibility` field.

### Who this is for

Performance marketers and media buyers researching competitor ads, agencies building creative
swipe files and pitch decks, e-commerce brands watching who runs what and for how long, and
copywriters studying which hooks and offers keep running.

### What one run returns

| `rowType` | One row per | Key fields |
|---|---|---|
| `advertiser` | advertiser page | `pageId`, `pageName`, `pageCategories`, `pageLikeCount`, `activeAdCount` (all matching ads in the library), `adsInResults`, `adLibraryUrl` |
| `ad` | ad | `adArchiveId`, `startDate`, `endDate`, `runDays`, `isActive`, `publisherPlatforms`, `displayFormat`, `adText`, `headline`, `linkDescription`, `ctaText`, `ctaType`, `landingUrl`, `landingDomain`, `appStoreLink`, `isDynamicTemplate`, `cardCount`, `assetCount`, `adLibraryUrl` |
| `creative-asset` | image or video URL | `assetType` (image, video), `assetUrl`, `previewImageUrl`, `videoSdUrl`, `fileFormat`, `cardIndex` |
| `hook` | unique ad text of an advertiser | `hookText` (the opening line), `hookTypes`, `bodyHookTypes`, `hookScore` (0–100), `hookWordCount` |
| `landing-domain` | domain the ads link to | `registeredAt`, `domainAgeDays`, `registrar`, `registrantCountry`, `mailProvider`, `hasSpf`, `hasDmarc`, `pageTitle`, `metaDescription`, `techHints`, `socialLinks`, `advertiserNames` |
| `advertiser-summary` | advertiser | `activeAdCount`, `adsStartedLast30Days`, `averageRunDays`, `longestRunningAdId`, `longestRunningAdDays`, `topCtas`, `topHookTypes`, `averageHookScore`, `topLandingDomains`, `displayFormats`, `platformShare`, `uniqueAdTexts` |
| `competitor-comparison` | pair of advertisers you entered | `activeAdCountDiff`, `moreActiveAdvertiser`, `averageRunDaysA/B`, `sharedCtas`, `ctaOverlap`, `sharedHookTypes`, `hookTypesOnlyA/B`, `sharedLandingDomains`, `platformsOnlyA/B`, `formatsOnlyA/B` |
| `notice` | message | `noticeCode`, `message` — never charged |

**Hook types.** `question`, `number`, `contrast`, `promise`, `discount`, `urgency`, `social-proof`,
detected by transparent rules on the opening line (`hookTypes`) and on the whole ad text
(`bodyHookTypes`). The score adds 15 points per device in the opening line, 10 for an opener of 12
words or fewer and 5 for an emoji or a direct "you".

**Long-running ads.** `runDays` counts from the ad's start date to today (active ads) or to its end
date. Ads that keep running for months are usually the ones that make money, so the summary names the
longest-running one.

### Pricing (pay per event)

| Event | Charged per | USD (FREE plan) | Bronze | Silver | Gold+ |
|---|---|---|---|---|---|
| `run-started` | run | 0.005 | 0.004 | 0.00325 | 0.0026 |
| `advertiser-resolved` | advertiser row | 0.02 | 0.016 | 0.013 | 0.0104 |
| `ad-scraped` | ad row | 0.01 | 0.008 | 0.0065 | 0.0052 |
| `creative-asset-captured` | image or video URL row | 0.005 | 0.004 | 0.00325 | 0.0026 |
| `hook-classified` | hook row (unique ad text) | 0.02 | 0.016 | 0.013 | 0.0104 |
| `landing-domain-profiled` | landing domain row | 0.01 | 0.008 | 0.0065 | 0.0052 |
| `advertiser-summary` | advertiser summary row | 0.02 | 0.016 | 0.013 | 0.0104 |
| `competitor-compared` | comparison row (opt-in) | 0.03 | 0.024 | 0.0195 | 0.0156 |

**What is charged and what is not.**

- A row written to the dataset is charged once under its event. The same ad, asset, ad text,
  landing domain or advertiser is never charged twice in one run, even when two entries lead to it.
- Hook rows are per unique ad text of an advertiser: 40 ads that share one text give one hook row.
- A page with no ads matching your filters still gets its `advertiser` row (the lookup was done and
  the page is named), followed by a free `no-ads-active` notice.
- For a search term, every advertiser in the results gets an `advertiser` row; the
  `advertiser-summary` row is added only for advertisers with 2 or more ads in the results. Pages you
  enter directly always get a summary.
- Landing domains where no source answered produce no row and no charge. Up to 60 domains per run.
- Notice rows (`advertiser-not-found`, `no-ads-active`, `region-blocked`, `partial-results`,
  `spending-limit`, `invalid-input`) are messages, never charged.
- When your maximum charge per run is reached the run stops paid work and ends with a notice row.

**Example (measured 2026-09-19):** Nike's page with `maxAdsPerPage` 50 returned 50 ads, 25 unique ad
texts, 286 creative URLs (most ads are catalog carousels with up to 10 product cards each) and 8
landing domains: start $0.005 + advertiser $0.02 + ads $0.50 + assets $1.43 + hooks $0.50 + domains
$0.08 + summary $0.02 = **about $2.56**. With creative assets switched off the same run costs about
$1.13. Lower `maxAdsPerPage` to cap the ad rows.

### Input

```json
{
  "pageUrls": [
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=ALL&view_all_page_id=15087023444&search_type=page&media_type=all",
    "https://www.facebook.com/NordstromRack/",
    "running shoes"
  ],
  "countries": ["ALL"],
  "adStatus": "active",
  "maxAdsPerPage": 50,
  "captureCreativeAssets": true,
  "classifyHooks": true,
  "profileLandingDomains": true,
  "compareAdvertisers": false
}
```

- `pageUrls` — array, up to 50 entries. Each one can be an Ad Library URL (a page view or a search;
  its own country and status are kept), a Facebook page URL, a numeric page ID, or a search term.
- `countries` — delivery countries (`US`, `GB`, `DE`…) or `ALL`.
- `adStatus` — `active`, `inactive` or `all`.
- `maxAdsPerPage` — ad rows per entry, 1–1000.
- `compareAdvertisers` — off by default; with two or more pages entered, adds one comparison row per pair.
- `proxyConfiguration` — fallback only: when the Ad Library refuses or rate-limits the run's own
  network, that search is redone once through this proxy (residential by default).

### Output example

```json
{ "rowType": "advertiser", "pageId": "89516513179", "pageName": "Nordstrom Rack", "pageCategories": ["Clothing (Brand)"], "pageLikeCount": 2518001, "activeAdCount": 147, "adsInResults": 50 }
{ "rowType": "ad", "adArchiveId": "925321173274919", "pageName": "Nordstrom Rack", "startDate": "2026-08-03", "runDays": 47, "displayFormat": "VIDEO", "adText": "Buy online and pick up in store for free! Get up to 70% off Nike, Vince, Madewell, adidas and more.", "ctaText": "Learn more", "landingDomain": "stores.nordstromrack.com", "publisherPlatforms": ["FACEBOOK", "INSTAGRAM", "MESSENGER"] }
{ "rowType": "hook", "adArchiveId": "925321173274919", "hookText": "Buy online and pick up in store for free!", "hookTypes": ["discount"], "bodyHookTypes": ["number", "discount"], "hookScore": 25 }
{ "rowType": "advertiser-summary", "pageName": "Nordstrom Rack", "activeAdCount": 147, "adsStartedLast30Days": 40, "averageRunDays": 23.5, "longestRunningAdDays": 50, "topHookTypes": [{"value": "number", "count": 33}, {"value": "discount", "count": 24}] }
```

### Limits worth knowing

- The Ad Library shows commercial ads without spend or reach figures (Meta publishes those only for
  political and issue ads and for ads delivered in the EU). When the library carries them, `spend`,
  `impressionsText` and `targetedOrReachedCountries` are filled in.
- The library occasionally refuses or rate-limits some networks. The run then redoes the search once
  through your proxy; if that fails too it writes a free `region-blocked` notice (nothing delivered) or
  a free `partial-results` notice (the ads it did get are delivered) and moves on.
- CTA text follows the language the library serves to the run's network; `ctaType` (SHOP\_NOW,
  LEARN\_MORE…) is the same everywhere, so summaries and comparisons count `ctaType`.

### Runs on the Apify platform

Schedule it to watch competitors weekly, call it from the API, or plug it into a workflow. It needs
no browser; 2 GB of memory is plenty.

# Actor input Schema

## `pageUrls` (type: `array`):

One per line, up to 50. Each value is detected automatically: a Meta Ad Library URL (a page view or a search), a Facebook page URL (facebook.com/nike), a numeric page ID, or a search term (every advertiser whose ads match it). Duplicates are checked once. You are responsible for a lawful purpose, for complying with Meta's terms and for how you use the advertisers' pages and creatives.

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

Two-letter country codes where the ads were delivered (US, GB, DE...), or ALL. Up to 10. A country in an Ad Library URL overrides this list for that URL.

## `adStatus` (type: `string`):

Which ads to collect. A status in an Ad Library URL overrides this for that URL.

## `maxAdsPerPage` (type: `integer`):

Upper limit of ad rows for each entry in the list (1-1000). The advertiser row still reports the total number of matching ads.

## `captureCreativeAssets` (type: `boolean`):

One row per image or video URL of each ad, carousel cards included (creative-asset-captured). URLs only: files are not downloaded or stored.

## `classifyHooks` (type: `boolean`):

One row per unique ad text with its opening line, hook types and a 0-100 hook score (hook-classified).

## `profileLandingDomains` (type: `boolean`):

One row per landing domain the ads link to: registration date and age, registrar, mail provider, homepage title, tech hints and social links (landing-domain-profiled). Up to 60 domains per run.

## `compareAdvertisers` (type: `boolean`):

When you enter two or more pages, add one comparison row per pair of advertisers (competitor-compared). Off by default.

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

Used only when the Ad Library refuses or rate-limits the run's own network: the run then redoes that search once through this proxy. Residential proxies work best for the Ad Library.

## Actor input object example

```json
{
  "pageUrls": [
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=ALL&view_all_page_id=15087023444&search_type=page&media_type=all",
    "https://www.facebook.com/NordstromRack/",
    "running shoes"
  ],
  "countries": [
    "ALL"
  ],
  "adStatus": "active",
  "maxAdsPerPage": 50,
  "captureCreativeAssets": true,
  "classifyHooks": true,
  "profileLandingDomains": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "pageUrls": [
        "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=ALL&view_all_page_id=15087023444&search_type=page&media_type=all",
        "https://www.facebook.com/NordstromRack/",
        "running shoes"
    ],
    "countries": [
        "ALL"
    ],
    "adStatus": "active",
    "maxAdsPerPage": 50,
    "captureCreativeAssets": true,
    "classifyHooks": true,
    "profileLandingDomains": true,
    "compareAdvertisers": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ntriqpro/meta-ad-library-intel").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 = {
    "pageUrls": [
        "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=ALL&view_all_page_id=15087023444&search_type=page&media_type=all",
        "https://www.facebook.com/NordstromRack/",
        "running shoes",
    ],
    "countries": ["ALL"],
    "adStatus": "active",
    "maxAdsPerPage": 50,
    "captureCreativeAssets": True,
    "classifyHooks": True,
    "profileLandingDomains": True,
    "compareAdvertisers": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ntriqpro/meta-ad-library-intel").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 '{
  "pageUrls": [
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=ALL&view_all_page_id=15087023444&search_type=page&media_type=all",
    "https://www.facebook.com/NordstromRack/",
    "running shoes"
  ],
  "countries": [
    "ALL"
  ],
  "adStatus": "active",
  "maxAdsPerPage": 50,
  "captureCreativeAssets": true,
  "classifyHooks": true,
  "profileLandingDomains": true,
  "compareAdvertisers": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call ntriqpro/meta-ad-library-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ntriqpro/meta-ad-library-intel"
        }
    }
}
```

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/9St1LLPVLhl8RUPml/builds/PKquQdQSsXMLTEGVH/openapi.json
