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

Facebook and Instagram ads from the public Meta Ad Library, no login. Search terms, advertisers or Ad Library links; any country incl. GCC. Ad text, CTA, landing URL, images, videos, dates, platforms, political spend. Only-new alerts.

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

## Pricing

from $1.50 / 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.

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 Ad Library Scraper — Facebook & Instagram Ads

Get ads from the public Meta Ad Library (facebook.com/ads/library) as clean, flat rows. Search by keyword, by advertiser, or paste Ad Library links. Works for any country, including Saudi Arabia, UAE, Qatar, Kuwait, Bahrain, Oman, Egypt and Jordan. No Facebook login, no cookies, no API token.

### Who it is for

- Marketers and agencies who track what competitors run on Facebook, Instagram, Messenger and Audience Network.
- E-commerce teams who look for winning creatives, offers and landing pages.
- Researchers and journalists who follow political and issue ads, with spend and impressions.
- Anyone who wants an alert when an advertiser starts a new ad.

### What you get for each ad

| Field | What it is |
|---|---|
| `adArchiveId`, `adUrl` | Ad Library ID and a link to the ad |
| `pageId`, `pageName`, `pageUrl`, `pageLikeCount`, `pageCategories` | The advertiser |
| `isActive`, `startDate`, `endDate` | Running now or not, and the run dates |
| `platforms` | facebook, instagram, messenger, audience_network, threads, whatsapp |
| `displayFormat` | IMAGE, VIDEO, CAROUSEL, DCO (dynamic creative), DPA (catalog), MULTI_IMAGES |
| `body`, `title`, `linkDescription`, `caption` | Ad text and headline |
| `ctaText`, `ctaType`, `linkUrl` | Call to action and landing page |
| `imageUrls`, `videoUrls`, `videoPreviewImageUrls` | Media links (HD video when Meta has it) |
| `cards` | Each card of a carousel, catalog or dynamic ad: title, text, link, CTA, image, video |
| `collationCount` | How many versions of this ad Meta groups together |
| `spend`, `currency`, `impressions`, `reachEstimate`, `paidForBy` | Shown by Meta for political and issue ads (and some regulated ads) |
| `sourceInput`, `searchCountry` | Which input and country found the ad |

Catalog and dynamic ads use placeholders such as `{{product.name}}` in their main text. The actor fills `body` and `title` from the first real card instead, so you read real text. Landing links keep the advertiser's own tracking tags.

### Input

You can mix all three ways of choosing ads in one run.

- **Search terms**: keywords such as `noon`, `protein bar` or `عروض`. Turn on **Exact phrase** for phrase matching.
- **Advertisers**: a page ID (`15087023444`), a page link, or a page name (`Vodafone Egypt`). Names are looked up with Meta's own Ad Library search for that country. The run summary shows the page that was picked and other close matches. A page ID is the most precise.
- **Ad Library links**: set filters on facebook.com/ads/library, copy the link, paste it here. A single-ad link (`?id=...`) returns that ad.

Filters: countries (each term and advertiser runs once per country; `ALL` for every country), ad type (all, political and issue, housing, employment, financial), active status, platforms, media type, date range, ad languages, sort (relevance or most impressions), and max ads per input.

Example: two keywords and two advertisers in Saudi Arabia and Egypt.

```json
{
    "searchTerms": ["noon", "protein bar"],
    "advertisers": ["Talabat", "15087023444"],
    "countries": ["SA", "EG"],
    "activeStatus": "active",
    "platforms": ["instagram"],
    "maxAds": 100
}
```

### Output example (real run, Saudi Arabia, search "noon")

```json
{
    "type": "ad",
    "adArchiveId": "1416889517216606",
    "adUrl": "https://www.facebook.com/ads/library/?id=1416889517216606",
    "pageId": "258490630951116",
    "pageName": "Lipton Ice Tea",
    "pageUrl": "https://facebook.com/liptoniceteaGCC",
    "pageLikeCount": 5756873,
    "isActive": true,
    "startDate": "2026-10-13T07:00:00.000Z",
    "endDate": "2026-10-26T07:00:00.000Z",
    "platforms": ["facebook", "instagram"],
    "displayFormat": "VIDEO",
    "body": "طريقة سهلة بس بتفيدك 😌🥤\nNo scissors? No problem \n#liptonicetea #ليبتون_آيس_تي #Hack #أسهل_طريقة",
    "caption": "minutes.noon.com",
    "ctaText": "Shop now",
    "ctaType": "SHOP_NOW",
    "linkUrl": "https://minutes.noon.com/saudi-en/search/?f%5Btag%5D=ksa_admon_lipton_26",
    "videoUrls": ["https://video.famm3-3.fna.fbcdn.net/o1/v/t2/f2/m367/AQOlOEk9...mp4?..."],
    "videoPreviewImageUrls": ["https://scontent.famm3-1.fna.fbcdn.net/v/t39.35426-6/846670700_...jpg?..."],
    "collationCount": 1,
    "sourceType": "search",
    "sourceInput": "noon",
    "searchCountry": "SA"
}
```

Political ad (real run, United Kingdom, ad type political and issue ads), selected fields:

```json
{
    "adArchiveId": "2182679715640357",
    "pageName": "Reform UK",
    "isActive": false,
    "startDate": "2026-07-23T07:00:00.000Z",
    "endDate": "2026-09-12T07:00:00.000Z",
    "paidForBy": "Reform UK",
    "spend": "£50K - £60K",
    "currency": "GBP",
    "impressions": ">1M",
    "reachEstimate": ">1M",
    "categories": ["POLITICAL"]
}
```

### Status rows and run summary

Every input ends with a clear result. Inputs that return nothing get a free status row (`type: "status"`) that says why: `no_results`, `no_new_ads`, `not_found` (advertiser name not found), `invalid_input`, `blocked` or `api_error`. Status rows are never charged.

The `RUN_SUMMARY` record in the key-value store lists every input with its status, the Ad Library link that reproduces it, the page picked for each advertiser name, and the number of requests.

An ad found by two inputs for the same country is delivered and charged once.

### Only new ads (alerts)

Turn on **Only new ads** and schedule the actor. The first run delivers everything and saves a baseline. Later runs deliver only ads not seen before for each advertiser, search term or link, and the run summary counts new ads per advertiser. Use a different **Alert state name** for each watchlist. You pay only for new ads.

### How to schedule

In Apify Console, open the actor, save your input as a task, then add a schedule (for example every morning at 08:00). Connect the task to email, Slack, Google Sheets or a webhook through Apify integrations.

### Pricing

$2.00 per 1,000 ads. Platform usage is included in the price. Status rows are free. A run stops charging when it reaches your maximum cost per run.

### Limits

- Data is what Meta's public Ad Library shows to a logged-out visitor. Spend, impressions and reach are shown by Meta for political and issue ads; for most commercial ads Meta shows none.
- Meta returns about 10 ads per request. In our tests one run fetched about 1,000 ads in 90 seconds.
- European audience details (age, gender and reach per country for EU ads) and page "About" details are not included.
- Meta can rate-limit busy addresses. The actor then starts a fresh session and, if needed, moves to Apify datacenter and then residential proxy. If Meta still refuses, the input gets a `blocked` status row and nothing is charged for it.
- Media links are Meta's own CDN links. They expire after some days, so download files you want to keep.

# Actor input Schema

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

Keywords to search in the Meta Ad Library, such as a brand, product or topic: nike, protein bar, عروض رمضان. Each term runs once per country below.

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

Match the search terms as an exact phrase. Off = Meta's normal keyword match (all words, any order).

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

Pages whose ads you want. Use a page ID (15087023444), a page link (https://www.facebook.com/profile.php?id=61588018263996) or a name (Vodafone Egypt). Names are looked up with Meta's own Ad Library search for the first country below; the chosen page and other close matches are shown in the run summary. Page IDs are the most precise.

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

Links copied from facebook.com/ads/library after you set filters there. The filters in the link (country, status, ad type, media, platforms, language, dates) are used as they are. A single-ad link (?id=...) returns that one ad.

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

Two-letter country codes where the ads are shown. Examples: US, GB, SA (Saudi Arabia), AE (UAE), QA, KW, BH, OM, EG, JO. Use ALL for every country. Each search term and advertiser runs once per country.

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

All ads, or only one of Meta's special categories. Political and issue ads include spend, impressions and paid-for-by.

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

Ads running now, ads that stopped, or both.

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

Only ads shown on these Meta platforms. Leave empty for all.

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

Only ads with this kind of creative.

## `dateFrom` (type: `string`):

Optional. Meta's date filter: ads that delivered impressions on or after this date. A date (2026-09-01) or a relative time (30 days, 2 weeks).

## `dateTo` (type: `string`):

Optional. Ads that delivered impressions on or before this date (2026-10-25).

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

Optional. Two-letter language codes of the ad text, such as ar, en, fr. Leave empty for all languages.

## `sortBy` (type: `string`):

Meta's order of results. Most impressions puts the ads seen most first.

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

Cap for each search term, advertiser or link in each country. Meta returns about 10 ads per request.

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

Remembers the ads each advertiser, search term or link returned before and delivers only ads not seen yet. Schedule the actor to get new-ad alerts per advertiser. The first run delivers everything and saves the baseline. You pay only for new ads.

## `stateName` (type: `string`):

Name of the saved state for Only new ads. Use a different name for each watchlist or schedule.

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

Leave empty for the automatic ladder: no proxy first, then Apify datacenter, then residential only if Meta limits us. Set a proxy here to force it.

## `autoProxyFallback` (type: `boolean`):

Move to a proxy when Meta limits direct requests.

## `allowResidentialFallback` (type: `boolean`):

Residential proxy uses more platform credit (included in the price). Turn off to stop at datacenter.

## `maxRetries` (type: `integer`):

Retries per request, each on a fresh session, when Meta answers with a rate limit or an error page.

## Actor input object example

```json
{
  "searchTerms": [
    "coffee"
  ],
  "exactPhrase": false,
  "countries": [
    "US"
  ],
  "adType": "all",
  "activeStatus": "active",
  "mediaType": "all",
  "sortBy": "relevance",
  "maxAds": 20,
  "onlyNew": false,
  "stateName": "default",
  "autoProxyFallback": true,
  "allowResidentialFallback": true,
  "maxRetries": 4
}
```

# Actor output Schema

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

All rows. Filter by the type field.

## `runSummary` (type: `string`):

Status per input, matched pages for advertiser names, new ads per advertiser (alert mode), requests, bytes and proxy tier used.

# 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": [
        "coffee"
    ],
    "countries": [
        "US"
    ],
    "maxAds": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("dottti/meta-ad-library").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": ["coffee"],
    "countries": ["US"],
    "maxAds": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("dottti/meta-ad-library").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": [
    "coffee"
  ],
  "countries": [
    "US"
  ],
  "maxAds": 20
}' |
apify call dottti/meta-ad-library --silent --output-dataset

```

## MCP server setup

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

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/E85RAjZbPDb2c6Mco/builds/y5leIERp8pbGDreOh/openapi.json
