# Facebook Ads Library Scraper | $0.50/1K Starter | Monitor (`x_guru/facebook-ads-library-monitor`) Actor

Scrape Facebook and Instagram ads from Meta Ad Library by keyword, advertiser ID or search URL. Export ad copy and image/video links to CSV or JSON. Resume without duplicates, collect across searches and monitor competitor ad changes. $0.50/1K ads on Starter.

- **URL**: https://apify.com/x_guru/facebook-ads-library-monitor.md
- **Developed by:** [Hundevmode Labs](https://apify.com/x_guru) (community)
- **Security:** Limited permissions
- **Stats:** 3 total users, 1 monthly users, 87.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 ad saveds

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

## Facebook Ads Library Scraper & Competitor Ad Monitor

Collect public Facebook and Instagram ads from **Meta Ad Library** using keywords, advertiser page IDs or Ad Library search URLs. Export ad copy, image/video links and creative variants to CSV or JSON, continue an interrupted collection without repeating saved IDs, and monitor competitor ad changes.

**$0.50 per 1,000 saved ads on Starter.** Returned creative links, continuation and monitoring are included. Startup is $0.001 at the default memory; other account-plan rates are listed below.

For agencies, ecommerce teams and researchers who need current ad creatives, advertiser comparisons or structured data for their workflows. No Facebook login or separate ad-data subscription is required.

### What you get

- **Ad creative research:** copy, headlines, CTA, landing links, images, videos and returned creative cards.
- **Multiple searches in one run:** one total result cap, global deduplication and collection across targets in turn.
- **A report for each search:** delivered ads, duplicates, completion status and the reason a search stopped.
- **Saved continuation:** unfinished searches resume; completed searches and already saved ad IDs are skipped.
- **Competitor monitoring:** compare current observations with the last complete snapshot and export changes.

Media are returned as links, not permanently hosted files. Meta can limit available results or restrict access; use the per-search report to check coverage.

### Quick start: scrape Facebook ads

1. In **Keywords, advertiser IDs or Ad Library URLs**, enter your targets, one per line. Replace the example.
2. Choose a country and **Maximum ads in this run**. The limit is shared across all targets.
3. Start the Actor. Open **Ad results (JSON)** or **Download flat CSV**, then check **Results and issues by search**.

A keyword search:

```json
{
  "searchTerms": ["running shoes"],
  "country": "US",
  "activeStatus": "active",
  "maxResults": 100
}
```

To collect a specific advertiser, paste its numeric Facebook page ID or a search URL containing `view_all_page_id`. For example:

```json
{
  "searchTerms": [
    "15087023444",
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&view_all_page_id=115939511520844"
  ],
  "country": "US",
  "maxResults": 100,
  "collectionStrategy": "round_robin"
}
```

A numeric value is an **advertiser page ID**, not an individual ad archive ID. Keywords can match unrelated advertisers. A company name or domain is not automatically a verified advertiser match.

### Facebook and Instagram ad data

The dataset contains one row per unique ad ID. Missing scalar source fields are `null`.

| Data | Fields |
| --- | --- |
| Ad and advertiser | `adId`, `adUrl`, `pageId`, `pageName`, `pageUrl` |
| Creative copy | `adText`, `headline`, `description`, `callToAction`, `displayFormat` |
| Destinations | `landingPageUrl`, `landingPageDomain` |
| Images and videos | `imageUrl`, `videoUrl`, `imageUrls`, `videoUrls`, `imageCount`, `videoCount` |
| Creative variants | `creativeVariants`, `creativeVariantCount`, `primaryCreativeVariantIndex` |
| Source status and dates | `isActive`, `startDate`, `endDate`, `daysSinceStart` |
| Platforms and search context | `publisherPlatforms`, `publisherPlatformsText`, `requestedCountry`, `searchTerm`, `searchTargetIndex`, `observedAt` |
| When published by Meta | `currency`, `spend`, `reachEstimate`, `targetedOrReachedCountries` |
| In monitoring mode | `changeType`, `firstSeenAt` |

A single ad may contain several creative variants. JSON preserves returned cards and image/video lists; the flat CSV includes primary media links and scalar columns for spreadsheets and CRM imports. If top-level copy is missing or templated, primary fields use the first returned creative card. `primaryCreativeVariantIndex: 0` identifies that fallback. Variant counts describe returned cards, not all variants in existence.

`searchTerm` and `searchTargetIndex` identify the first target that delivered an ad. Overlapping searches do not create extra dataset rows for that ID. A landing domain describes the ad's destination; it does not verify business ownership.

`requestedCountry` is the requested search country, not proven audience targeting. The source's combined targeting/reach country list does not prove actual reach. `daysSinceStart` is calendar time since the source date, not active running time or a performance score. Spend and reach may be absent; the Actor does not infer commercial impressions, ROAS or conversions. A source end date does not override the source active-status flag.

### Input options and filters

| Input | How to use it |
| --- | --- |
| `searchTerms` | Main field: keywords, numeric advertiser page IDs or Ad Library search URLs. Up to 50 unique targets in total. |
| `country` | A two-letter country code such as US, GB or DE; `ALL` requests all available countries. |
| `maxResults` | One total result cap per run, shared across all targets. It is not a cap per keyword or a promise to reach the count. |
| `collectionStrategy` | `round_robin` (default): one page per unfinished target in turn. `sequential`: finish searches in input order. |
| `activeStatus` | Active, all available or inactive ads. Historical availability varies by country and category. |
| `mediaType` | All, images and memes, video, or no media, following the source filters. |
| `adType` | All ads or supported political/issue, housing, employment and credit categories. |
| `exactPhrase` | Request an exact keyword phrase instead of an unordered keyword search. |
| `collectionMode` | `collect` for current results; `monitor` for snapshot comparison. |
| `monitorKey` | Stable monitoring name for manual/API runs. An Apify Task supplies its identity automatically. |
| `resumeFromRunId` | Continue a finished run using the input provided in `PAGINATION.nextInput`. |
| `pageIds`, `startUrls` | Optional additional targets for existing integrations, under the same total cap. |
| `advanced` | Bounded request, time, transfer and no-progress safeguards. |

The default one-page turns spread collection across searches; page sizes and overlapping IDs differ, so counts need not be equal. A small budget or result cap may stop before every target is visited. **In input order** gives earlier searches priority, so the first broad search can use the whole cap.

Pasted search URLs can override country, status, media, category and keyword/page filters for that target. Standard total-impressions sorting with `sort_data[mode]=total_impressions` and `sort_data[direction]=asc` or `desc` is supported; order comes from Meta. Single-ad URLs, custom requests, remote URL lists, date/language/platform URL filters and other sorting modes are rejected rather than silently ignored. The image filter includes memes; there is no separate meme classifier.

### Results and coverage for every search

Open **Results and issues by search** in Output. Its `TARGET_REPORT` record lists each target's filters, status, stop reason, pages fetched, returned records, delivered unique ads, delivered ads in this run, skipped duplicates and outstanding buffer count. The same list is included in `RUN_SUMMARY.targets`.

Statuses are `complete`, `partial`, `paused` and `not_started`. The one-based `targetIndex` matches the dataset's `searchTargetIndex`. Counts accumulate across continuation. `adsReturned` counts records from fetched pages, including repeated IDs; it is not an estimate of every available matching ad.

A source error or a series of pages without new unique ads pauses only the affected target. Other unfinished searches continue. The run is marked failed if source issues remain, while saved results, the per-target report and continuation remain available. Budget, result, request, time and traffic limits stop the entire run.

`coverageComplete` means Meta reported no more pages for every requested target. It does not prove the source exposed every ad in existence or that a collection is an atomic snapshot. A successful empty search differs from a failed search. Reports, errors and summaries are stored separately and are not charged as saved ads.

### Pagination: continue an interrupted collection

1. Open **Continue collecting: next input** in Output.
2. Copy `PAGINATION.nextInput` into a new run of this Actor.
3. Adjust the result cap, run safeguards or **Maximum cost per run** if needed. Keep target searches and filters unchanged.

The next run resumes unfinished searches and skips completed ones. Each target retains its own cursor, search session and partly consumed page. Global ad-ID deduplication spans the continuation chain. New-run datasets contain only the new batch; combine datasets to obtain the full collection.

You may change `maxResults`, `collectionStrategy` and safeguards during continuation. If a result cap ended mid-page, the remaining rows are delivered before moving to the next target. A source-paused target is attempted again in the next run, not repeatedly scheduled within the same run. If a page has an unusable format or missing ad ID, continuation requests it again while preserving already saved IDs.

Default safeguards allow 120 requests, 240 seconds and 25 MB per run. A target pauses after 10 consecutive pages without new unique IDs; `advanced.maxNoProgressPages` can change this threshold from 1 to 50. The Apify run timeout must also allow the requested collection time. Raising the threshold can spend more time on empty or duplicate pages.

`hasMore` means unfinished work remains to attempt, not proof that more ads will be returned. Meta may keep refusing requests, and cursors can expire. A fresh collection starts a new deduplication scope. Older saved checkpoints upgrade automatically; their previously unrecorded per-target history is marked `statisticsCoverFullChain: false`, while global saved-ID counts are retained.

### Monitor competitor ads and creative changes

Save an Apify Task with `collectionMode: "monitor"` and schedule it in Apify. The Task supplies a stable monitor identity. For manual or API runs, set a stable `monitorKey`, for example `competitor-us`.

Monitoring is scoped to your account, Actor, monitor identity and exact searches. It compares advertiser identity, source active status/dates, copy, headlines, destinations, CTA, format, platforms and returned creatives. Spend and reach are exported when present but are not change triggers.

| Change type | Meaning |
| --- | --- |
| `BASELINE` | First complete collection establishes the reference snapshot. |
| `NEW` | Ad was not present in the previous complete snapshot. |
| `UPDATED` | A tracked field differs from the previous snapshot. |
| `UNCHANGED` | Tracked fields match the previous snapshot. |
| `NOT_OBSERVED` | Ad was absent from this completed search; this does not prove it ended. |

Current observations are exported in the dataset; **Monitoring changes** opens the separate `CHANGES` record. Only a complete collection replaces the baseline. A capped or source-partial scan keeps the previous snapshot, and continuation retains observations across batches. Expiring media signatures and regional CDN-host changes alone do not count as creative changes.

Avoid overlapping runs with the same monitoring name. Baseline changes are checked before committing, but this is not a transactional lock. Keep collection limits high enough to finish the monitoring scope. Each fresh monitoring run bills delivered observations, including unchanged ads, at the same rate as collection.

### Pricing: saved ads and included features

| Apify account plan | Per 1,000 saved ad observations |
| --- | ---: |
| Free | $0.75 |
| Starter | $0.50 |
| Scale | $0.40 |
| Business | $0.30 |
| Platinum / Diamond discount tiers | $0.30 |

**Startup:** $0.001 per GB of selected run memory, minimum one event. At the default 256 MB, startup is $0.001, including empty searches. Platform usage is included in the configured PPE policy.

**Included:** returned creative links/cards, CSV/JSON output, per-search reports, deduplication, saved continuation and monitoring. No separate creative-link or monitoring surcharge. Media files are not downloaded, and optional EU demographic-detail pages are not fetched.

For example, 100 saved ads on Starter at default memory cost `100 × $0.00050 + $0.001 = $0.051`. A continuation is a new run with its own startup fee. Each newly delivered unique ad ID is one observation; a later fresh monitoring run that delivers that ad again creates another observation. Skipped duplicates, errors, summaries and standalone change records are not separately billed.

Free accounts have a ceiling of 100 unique ads across a continuation chain, with at most 30 requests, 120 seconds and 10 MB per run. “Free” is the Apify account plan; saved observations still use its PPE rate. The Actor uses the platform account-plan flag and SDK spending limits. **Maximum cost per run** can stop collection before the result cap. Check the live Pricing tab before running; these are the configured pre-publication rates.

### API, n8n and scheduled workflows

Use the Actor's **API** tab to start runs and retrieve datasets from your application, n8n, Make or other HTTP-capable tools. Apify generates authenticated request examples for your account. For integrations, the stable Actor ID is `6J4u2Rtng2DV9Nivj`; the technical name is `x_guru/facebook-ads-library-monitor`.

Use the dataset for current ad observations, `TARGET_REPORT` for per-search completion, `PAGINATION.nextInput` for the next batch, and `CHANGES` for monitoring changes. An API workflow should inspect completion instead of treating every finished run as full coverage.

### Frequently asked questions

#### Can I scrape Instagram ads too?

Yes, when Meta includes them in the Ad Library results. Returned `publisherPlatforms` identify the platforms. This Actor collects Ad Library ads; it does not scrape Instagram profiles or posts, and it does not offer an Instagram-only platform filter.

#### Can I download Facebook ad videos and images?

The Actor returns direct image/video links and returned creative variants. Open or save them in your workflow while they remain available. Signed media URLs can expire; the Actor does not provide permanent media hosting or a downloaded-file archive.

#### Do I need a Facebook account or an official Meta API token?

No Facebook login or Meta API token is required. The source is the public Ad Library web interface. Your Apify API token is needed only when invoking the Actor through Apify's API. This is an Apify scraping API, not Meta's official Marketing API or access to private campaign metrics.

#### Can I enter a company website to get its verified advertising page?

Use an advertiser page ID or an Ad Library search URL for a specific page. A company name can be searched as a keyword but may match unrelated ads. Verified domain-to-advertiser matching is not provided.

#### Why did collection stop below my result limit?

The source may be exhausted or restricted, or the run may reach its money, time, request, transfer or no-progress safeguard. Check **Results and issues by search** and `RUN_SUMMARY.stopReason`. Continue using the supplied next input where available. A high requested count is not a completeness or throughput guarantee.

#### Why are spend, reach or historical ads missing?

Meta's available fields and historical coverage vary by country and ad category. Missing source values remain `null`; the Actor does not estimate private campaign performance or recover information the source does not expose.

### Attribution

Includes the MIT-licensed MetaAdsCollector package by Yossef. See `NOTICE.md` and the included license. This is an independent tool, not affiliated with Meta or Facebook.

# Actor input Schema

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

Add one target per line: a keyword (running shoes), a numeric advertiser Facebook page ID, or a full Facebook Ad Library search URL. Replace the example with your targets. A numeric value is a page ID, not an ad ID. URL filters override the form for that target. Results are deduplicated across targets. Standard total-impressions sorting in pasted URLs is supported.

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

Two-letter country code, such as US, GB or DE. This is the search scope, not proof that an ad reached that country. Use ALL for all countries; availability depends on Meta.

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

One total cap across all targets. By default, the Actor takes one page per target in turn. A small cap or budget may stop before every target is visited. Limits on time or requests can stop earlier. Copy PAGINATION.nextInput to collect the next batch. Free accounts: up to 100 ads per continuation chain.

## `collectionStrategy` (type: `string`):

Recommended: collect one page from each unfinished target in turn. This spreads collection across your searches; it does not guarantee equal result counts. Choose input order to finish each target before moving on. A source error pauses only that target; other targets continue. Budget, request and time limits apply to the whole run. You can change this choice when continuing.

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

Select the status requested from Meta. Historical commercial ads may not be available in every country.

## `collectionMode` (type: `string`):

Collect ads for export, or compare with the previous complete snapshot. Monitor runs return current observations at the same per-ad price, including unchanged ads. Changes are also saved separately in CHANGES.

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

Requested Meta web media filter. The web interface combines images and memes; an image-only or meme-only classifier is not provided.

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

Special-category historical availability depends on Meta and country. This filter does not classify ads independently.

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

Request exact-phrase keyword matching. Advertiser page-ID searches do not use keywords.

## `monitorKey` (type: `string`):

For scheduled Apify Tasks this can be left empty: the Task ID is used automatically. Otherwise enter a stable name, e.g. competitor-us. Keep filters unchanged and avoid overlapping runs with the same name. Only complete scans update the baseline.

## `resumeFromRunId` (type: `string`):

Optional. The safest way is to copy PAGINATION.nextInput from the previous finished run. Saved ads are skipped and a partly consumed page is resumed. Raise the run budget if it stopped on budget. Source restrictions may persist; restarting does not guarantee access. Free accounts have a 100-ad ceiling across a continuation chain.

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

Optional additional numeric advertiser page IDs. Added after the main targets under the same total cap. An ad archive ID is not a page ID.

## `startUrls` (type: `array`):

Optional additional Ad Library search URLs for existing integrations. Added after keywords and page IDs. Direct URLs only; custom requests and remote lists are unsupported. Supported URL filters override form filters. Total-impressions URL sorting is supported; date, language and other sort modes are rejected.

## `advanced` (type: `object`):

Optional safeguards. Default: 120 requests, 240 seconds, 25 MB transferred. Free accounts use lower caps. Hitting a safeguard saves continuation. Increase the Apify run timeout in Run options if you raise the collection time. Also pauses after 10 consecutive pages without new ads.

## Actor input object example

```json
{
  "searchTerms": [
    "running shoes"
  ],
  "country": "US",
  "maxResults": 100,
  "collectionStrategy": "round_robin",
  "activeStatus": "active",
  "collectionMode": "collect",
  "mediaType": "all",
  "adType": "all",
  "exactPhrase": false
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `run_summary` (type: `string`):

No description

## `pagination` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `target_report` (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": [
        "running shoes"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("x_guru/facebook-ads-library-monitor").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": ["running shoes"] }

# Run the Actor and wait for it to finish
run = client.actor("x_guru/facebook-ads-library-monitor").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": [
    "running shoes"
  ]
}' |
apify call x_guru/facebook-ads-library-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,x_guru/facebook-ads-library-monitor"
        }
    }
}
```

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/6J4u2Rtng2DV9Nivj/builds/As72C4avUcjSzPt6Y/openapi.json
