# Meta Ad Library Scraper | Export & Changes (`danthedataman/meta-ad-library-change-feed`) Actor

Export Meta Ad Library creatives by advertiser or keyword. Compare full exports for new, changed and absent ads, with coverage safeguards and source media links.

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

## Pricing

from $0.44 / 1,000 results

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 you can export

Use this **Meta and Facebook Ad Library scraper** for creative research and ad archives. Supply an advertiser page ID, exact page name or keywords with country contexts to export available creative text, media links, activity flags and source links.

Compare a compatible full export to emit only new, changed and observed-absent ads. An ended row means disappearance from a completely read query; it is not independent confirmation that a campaign stopped.

### Try the shipped prefill

Use the input form's example values for a first run. Prefills are examples, not API defaults; check the billing and limits before starting.

```json
{
  "advertisers": [
    "182162001806727"
  ],
  "searchTerms": [],
  "countries": [
    "US"
  ],
  "activeStatus": "all",
  "maxAdsPerQuery": 40
}
```

### Input

| Field | Type | Prefill | What it is |
| --- | --- | --- | --- |
| `advertisers` | array of strings | `["182162001806727"]` | Numeric page IDs or exact advertiser page names. Names must resolve to one case-insensitive exact name match in public search results; otherwise supply a page ID. |
| `searchTerms` | array of strings | `[]` | Keyword searches, which can return several advertiser pages. |
| `countries` | array of strings | `["US"]` | Two-letter country codes, including `US` and `DE`. Each country has its own query. |
| `activeStatus` | string | `"all"` | `all`, `active` or `inactive`; omitted or null uses `all`. |
| `maxAdsPerQuery` | integer | `40` | Maximum distinct ads scanned per query and country, including unchanged ads. Omitted or null uses 100; 0 requests no work. |
| `baselineDatasetId` | string | — | Optional read-only resource picker for a previous full export dataset; selecting it switches to change mode. |

Prefills populate the form; they are not API defaults. `{}`, empty advertisers plus empty search terms, missing/empty countries, or a zero maximum exits successfully with guidance and no ad rows. An Actor start charge may still apply. Invalid inputs fail with a concise explanation, without dataset error rows.

Numeric page IDs give exact advertiser selection. Name resolution checks case-insensitive exact names among the initial public keyword results; a name absent from that sample or matching multiple pages requires a page ID. Keyword results are never presented as an exact advertiser match.

### Output

Each row represents an ad in one query and country. The same ad can appear in several query contexts. Within a query, duplicates are removed using `adArchiveId`.

```json
{
  "adArchiveId": "1234567890",
  "pageId": "182162001806727",
  "pageName": "adidas",
  "startDate": 1759708800,
  "endDate": 1760054400,
  "isActive": true,
  "platforms": ["FACEBOOK", "INSTAGRAM"],
  "creativeText": ["Example creative text"],
  "creativeTitle": ["Example title"],
  "cta": ["Shop now"],
  "landingUrls": ["https://example.com/product"],
  "mediaUrls": ["https://example.com/image.jpg"],
  "country": "US",
  "sourceUrl": "https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=US&search_type=page&view_all_page_id=182162001806727",
  "scrapedAt": "2026-10-05T17:00:00Z",
  "changeStatus": "changed",
  "changedFields": ["creativeText"]
}
```

The example is illustrative. Export rows omit `changeStatus` and `changedFields`. Start and end dates are the library's Unix timestamps in seconds, or null when absent. An active ad can have an end date; `isActive` is the source activity flag. Creative text, titles, CTA text, landing URLs and media URLs are distinct string arrays, including supplied carousel/card values. Missing values produce empty arrays. Landing URLs can include tracking links; the Actor does not follow them. `country` is search context, not an independently verified targeting claim. `sourceUrl` links to the query; `scrapedAt` is this run's UTC observation time.

Media URLs expire. Reacquire them on later runs and dedupe ads using the archive ID, never a media URL. Change comparisons use host-independent media paths and ignore query strings, fragments and array ordering; raw media URLs are exported unchanged, so CDN host rotation alone does not create changed ads. Only explicitly selected advertiser and creative fields are exported; page administrators, commenters and other personal data are excluded.

### Change feed

Select a previous **full export** in `baselineDatasetId` and use the same advertisers/search terms, country contexts and active-status filter. The Actor reads that dataset and never writes to it. An empty baseline makes all current ads new. Malformed rows, duplicate ad/query identities, other query contexts and change-feed datasets are refused.

- `new`: the archive ID was absent from that query's baseline.
- `changed`: a page, date, activity, platform, creative, CTA, landing or media field differs. `changedFields` lists the changed output field names. Observation time is excluded.
- `ended`: a baseline ad was absent after that query's pagination reached its end. The row retains its last observed fields and refreshes `scrapedAt`. This means absent from the selected query/filter, not independent confirmation that the advertiser stopped the ad. With an active-only filter, disappearance can reflect inactivity; with other filters, it can reflect library removal or query membership changes.

An interrupted scan emits no ended rows. A charge limit during delivery after a completed scan can truncate its ended rows; coverage stays complete, the stop reason becomes `charge-limit`, and a warning counts withheld ended rows. A current row the limit refuses is warned separately. A source-provided terminal page must be fully processed, including all collated ads, before coverage is complete. Unchanged ads emit no change rows but count toward the scan cap. Use a fresh full export as the next baseline; a change dataset is not a complete inventory.

Baseline access follows Apify's [Actor permissions](https://docs.apify.com/platform/actors/development/permissions#access-user-provided-storages): "they can access storages that users explicitly provide via the Actor input" and "the run token is expanded to allow only the requested operations on the selected storages". Its [input-schema resource contract](https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1#resource-type) defines a string `dataset` resource with `resourcePicker` and `["READ"]`.

### Coverage and billing

The `COVERAGE` key-value record lists each query's `complete` or `partial` coverage, distinct ads scanned and stop reason (`end`, `cap`, `source-error`, or `charge-limit`). The `ERRORS` record holds handled failures, never billable rows. Every output row uses the pay-per-event dataset push; the Actor stops fetching when the customer's charge limit binds. Unchanged ads incur no dataset-row event. See the Store pricing panel for current prices.

Queries use the public anonymous Ad Library website, without signing in or using the official identity-confirmed API. Document challenges are handled in the same anonymous session; source non-answers retry on fresh sessions, with a bounded three-attempt budget. Each attempt allows at most `maxAdsPerQuery` continuation requests; fresh cursors containing only duplicates cannot fetch indefinitely. Platform runs use residential egress because the measured datacenter continuation returned a rate-limit error. Source operations can change, so complete coverage is a run observation rather than a guarantee of every ad Meta holds. Large advertisers may need a higher cap and more time. Facebook's robots policy restricts anonymous crawling on these surfaces.

Download the dataset as JSON, CSV or Excel, or read its items through the Apify API. For change tracking, retain the complete full-export dataset you selected as the baseline.

# Changelog

This Actor's version history is a separate document: https://apify.com/danthedataman/meta-ad-library-change-feed/changelog.md

# Actor input Schema

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

Numeric Meta page IDs or case-insensitive exact page names. Names must resolve unambiguously from public results; page IDs give exact selection. No advertiser is filled in for API callers.

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

Keyword queries across advertiser pages. Results are not limited to one advertiser. An empty list requests no keyword queries.

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

Two-letter country context, such as US or DE. Each country is a separate query. Omitted, null or empty means no queries.

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

Library activity filter. Omitted or null uses all; isActive comes from the source, not its end date.

## `maxAdsPerQuery` (type: `integer`):

Maximum distinct ads scanned for each advertiser/keyword and country, including unchanged ads. Omitted or null uses 100; 0 requests no work. A cap can prevent complete coverage and ended rows.

## `baselineDatasetId` (type: `string`):

Optional read-only full export dataset from this Actor with the same query URLs, countries and active status. Selecting a dataset switches to new/changed/ended mode. Never filled automatically. Change-feed rows, duplicate identities and malformed rows are refused.

## Actor input object example

```json
{
  "advertisers": [
    "182162001806727"
  ],
  "searchTerms": [],
  "countries": [
    "US"
  ],
  "activeStatus": "all",
  "maxAdsPerQuery": 40
}
```

# Actor output Schema

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

No description

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

No description

## `coverage` (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 = {
    "advertisers": [
        "182162001806727"
    ],
    "searchTerms": [],
    "countries": [
        "US"
    ],
    "activeStatus": "all",
    "maxAdsPerQuery": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("danthedataman/meta-ad-library-change-feed").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 = {
    "advertisers": ["182162001806727"],
    "searchTerms": [],
    "countries": ["US"],
    "activeStatus": "all",
    "maxAdsPerQuery": 40,
}

# Run the Actor and wait for it to finish
run = client.actor("danthedataman/meta-ad-library-change-feed").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 '{
  "advertisers": [
    "182162001806727"
  ],
  "searchTerms": [],
  "countries": [
    "US"
  ],
  "activeStatus": "all",
  "maxAdsPerQuery": 40
}' |
apify call danthedataman/meta-ad-library-change-feed --silent --output-dataset

```

## MCP server setup

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

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/vtUmqj5d81HkVrPSD/builds/7fsAjzetv4GZiQirw/openapi.json
