# Meta Ads Library Scraper - Facebook & Instagram, All Filters (`automation_craft/meta-ads-library-scraper`) Actor

Scrape the Meta Ad Library (Facebook, Instagram, Messenger, Audience Network, WhatsApp, Threads ads) without login: keywords, advertiser pages, ad IDs or Ad Library URLs, every filter, EU and political transparency details. Exact caps, free total count, cross-run dedup, pay once per ad.

- **URL**: https://apify.com/automation\_craft/meta-ads-library-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** Social media, Marketing, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Meta Ads Library Scraper - Facebook & Instagram, All Filters

Scrape the **Meta Ad Library** (Facebook, Instagram, Messenger, Audience Network, WhatsApp and Threads ads) as clean JSON, CSV or Excel. Search by keyword, advertiser page, ad ID or a pasted Ad Library URL, apply **every filter the Ad Library website has**, set a cap that is actually a cap, and pay only for the ads you receive. No login, no cookies, no API keys, no code.

Built around the complaints buyers leave on other Ad Library scrapers:

- **Every Ad Library filter, applied on Meta's side.** Country (all 250 plus "All countries"), ad category (all, political, housing, employment, financial), active status, media type, platform, ad language, impressions date window, sort order, "targeted at country", and the political-only filters: paid-for-by, delivery region and estimated audience size. Filters that Meta applies are filters you do not pay for.
- **A cap that is honoured, billing that matches it.** `maxAds` and `maxAdsPerQuery` are hard caps. You are charged exactly once per delivered ad; duplicates, filtered-out ads, total counts, summaries and empty runs are free. There is no start fee.
- **A free total count before you pay.** Tick "Only count matching ads" and you get the Ad Library's own total per query at no cost, so you can size a job first.
- **Honest coverage.** Every query ends with `totalCount`, `delivered`, `coverage` (`complete`, `partial`, `empty`) and `stopReason` (`cap`, `end_of_results`, `thin_pages`, `blocked`). You are told when the data is incomplete instead of guessing.
- **EU and political transparency, parsed into columns.** Political ads carry spend, impressions and audience-size ranges as numbers; "Fetch ad details" adds EU (DSA) reach by country, age and gender, political age / gender and region distributions, payer / beneficiary data and the advertiser profile (page likes, Instagram followers, lifetime spend by disclaimer).
- **Free cross-run memory.** Name a memory and later runs return only ads you have not received yet. Schedule it and you have a change feed of a competitor's new ads.
- **Derived "winning ad" filters, free.** Minimum days running, minimum creative reuse (collation count), creative format, text must / must not contain, exclude pages.

Filter by placement (Facebook, Instagram, Messenger, Audience Network, WhatsApp, Threads) or leave it empty for every Meta surface.

### 30-second start

1. Type one or more **Keywords** (one per line), or paste **advertiser pages** (facebook.com/nike, a page ID, an Instagram profile), **advertiser names**, **Ad Library URLs** (every parameter in the URL is applied) or **Ad IDs**.
2. Pick the **Countries** and any Ad Library filters you would use on the website.
3. Set **Max ads**. Start. Download JSON, CSV or Excel from the Dataset tab, or read it through the API.

Prices: **$0.50 per 1,000 ads delivered.** Optional add-ons: ad details $0.25 per 1,000 ads, landing-page resolution $0.20 per 1,000 pages. A full record with details costs **$0.75 per 1,000 ads**. Worked examples: 500 ads for three keywords = **$0.25**; 2,000 competitor ads with details = **$1.50**; a daily re-run under a memory name that finds 8 new ads among 300 known ones = **$0.004**; a run that finds nothing = **$0.00**.

### What you get

One item per ad. Nested objects (`images`, `videos`, `cards`, `details`) for developers, flat columns (`imageUrls`, `spendLower`, `landingDomain`, ...) for spreadsheets. IDs are strings (they exceed JavaScript's number range).

| Field group | Fields |
|---|---|
| Identity | `adArchiveId`, `adLibraryUrl`, `adId`, `collationId`, `collationCount` (how many ads share this creative), `pageId`, `pageName`, `pageProfileUrl`, `pageProfilePictureUrl`, `pageLikeCount`, `pageCategories` |
| Status and timing | `isActive`, `startDate`, `endDate`, `daysRunning`, `totalActiveTimeSeconds`, `publisherPlatforms` |
| Creative | `displayFormat` (IMAGE, VIDEO, CAROUSEL, MULTI\_IMAGES, DCO, DPA, TEXT), `adText`, `title`, `caption`, `linkDescription`, `ctaText`, `ctaType`, `images[]` / `imageUrls`, `videos[]` (HD, SD, preview) / `videoUrls`, `cards[]` (carousel items with their own text, link and media), `extraTexts`, `extraLinks`, `extraImageUrls`, `extraVideoUrls`, `allText` |
| Destination | `linkUrl` (as served), `landingUrl` (l.facebook.com wrappers unwrapped), `landingDomain`, `landingUtm`, `linkIsTemplate` (catalogue placeholders such as `{{product.name}}`), `landingResolved` when resolution is on |
| Transparency (political) | `isPolitical`, `adCategories`, `byline` (paid for by), `disclaimerLabel`, `spendText` / `spendLower` / `spendUpper`, `currency`, `impressionsText` / `impressionsLower` / `impressionsUpper`, `audienceSizeText` / `audienceSizeLower` / `audienceSizeUpper`, `transparencyScope` (`POLITICAL`, `EU_DSA`, `NONE`) |
| Flags | `containsDigitalCreatedMedia` (AI disclosure), `containsSensitiveContent`, `isAaaEligible`, `gatedType`, `hideDataStatus`, `stateMediaRunLabel`, `hasUserReported`, `regionalRegulation`, `brandedContent`, `ecCertificates`, `brazilTaxId` |
| Details (optional) | `details.advertiser` (page alias, category, likes, verification, Instagram handle and followers, lifetime and weekly spend by disclaimer, entity type), `details.eu` (`targetsEu`, `totalReach`, `reachByCountry[]`, `reachBreakdown[]` by country x age x gender, age / gender / location targeting), `details.political` (`ageGender[]`, `regions[]` with share, `singleCountry`), `details.payerBeneficiary[]`, `details.isAdTakenDown`, `details.violationTypes` |
| Provenance | `searchTerm`, `searchCountry`, `searchAdType`, `queryKey`, `scrapedAt`, `changeType` (NEW, UPDATED, DUPLICATE), `isKnown`, `firstSeenAt`, `lastSeenAt`, `contentHash`, `charged` |

Which transparency numbers exist depends on the ad, not on this Actor: Meta publishes spend, impressions and audience size **only for ads in the "Issues, elections or politics" category**, and EU reach breakdowns **only for ads that were shown in the EU**. For every other ad those fields are `null` and `transparencyScope` says why. Measured fill rates from live runs at build time:

| Field | Commercial ads (US, keyword) | Political ads (US) |
|---|:-:|:-:|
| `adText` | 98% | 100% |
| `title` | 79% | 82% |
| `linkUrl` | 93% | 78% |
| `landingDomain` | 92% | 78% |
| `ctaType` | 93% | 77% |
| `imageUrls` | 40% | 10% |
| `videoUrls` | 25% | 55% |
| `pageLikeCount` | 98% | 97% |
| `spendText` | 2% | 100% |
| `impressionsText` | 2% | 100% |
| `audienceSizeText` | 2% | 98% |
| `byline` | 2% | 98% |
| `collationCount` | 80% | 93% |
| `endDate` | 100% | 100% |

### Input reference

| Input | What it does |
|---|---|
| Keywords, Keyword matching | The Ad Library search box. "Exact phrase" is the quoted search. |
| Advertiser pages, Advertiser names | Page IDs, page URLs, Instagram profiles or plain names (resolved through the Ad Library's own advertiser lookup; the run summary lists the candidates). Returns the page's "See all ads" view. |
| Ad Library URLs | Paste a URL from your browser; keyword, page, country, ad type, status, media type, platforms, languages, dates, sort and political filters are all read from it. `?id=` URLs fetch that single ad. |
| Ad IDs | Library IDs to fetch directly (current status, dates, creative, political numbers). Unknown IDs are reported and free. |
| Countries | Any of the 250 countries or All countries. Several countries = one query each (fair share of the cap). `EU` expands to the 27 member states (API). |
| Ad category | All, political, housing, employment, financial (the regulated categories). Political ads are the ones with spend and impressions. |
| Active status, Media type, Platforms, Ad languages | Exactly the website's filters. |
| Impressions within / from / to | The website's "Impressions by date": ads that were shown in the window (an ad that started earlier and is still running matches). |
| Sort | Default relevance, impressions high to low / low to high, most recent (grouped by launch month), as the website offers. |
| Only ads targeted at the country | The `is_targeted_country` switch. |
| Paid for by, Delivery regions, Audience size | Political-only filters. Region names as the website shows them ("California"). Audience buckets: 100-1K, 1K-5K, 5K-10K, 10K-50K, 50K-100K, 100K-500K, 500K-1M, above 1M. |
| Minimum days running, Minimum creative reuse, Creative formats, text filters, Exclude pages | Free post-filters. |
| Max ads, Max ads per query | Hard caps on delivered, charged ads. `maxItems: 0` through the API means no cap. |
| Only count matching ads | Free preflight: one `count` row per query, no ads, no charge. |
| Fetch ad details, Resolve landing pages | Paid add-ons, charged only when data came back. |
| Memory name, Also return already-known ads, Reset memory | Cross-run dedup (named key-value store `ads-library-memory-<name>` in your account). Known ads are never charged. |
| Proxy, Parallel queries, IP retries, Stop after N empty pages | Advanced. Leave the defaults. |

### Output

`type: "ad"` items, `type: "count"` rows in count mode, `type: "status"` rows when something could not be done (never charged), and one `type: "run-summary"` item at the end with per-query `totalCount`, `delivered`, `pages`, `coverage`, `stopReason`, the resolved advertisers, dedup stats, exact `charges`, proxy statistics and fill rates.

Sample item from a live run:

```json
{
  "type": "ad",
  "adArchiveId": "1785179045778580",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1785179045778580",
  "adId": null,
  "collationId": "974017695598335",
  "collationCount": 1,
  "isActive": false,
  "startDate": "2026-05-07T07:00:00.000Z",
  "endDate": "2026-06-29T07:00:00.000Z",
  "daysRunning": 53,
  "totalActiveTimeSeconds": null,
  "pageId": "721404351056614",
  "pageName": "IControl: Easy Widgets Themes",
  "pageProfileUrl": "https://www.facebook.com/61578892468353/",
  "pageProfilePictureUrl": "https://scontent-lga3-2.xx.fbcdn.net/v/t39.35426-6/695177587_1511487320476777_920753808075...",
  "pageLikeCount": 4501,
  "pageCategories": [
    "Topic"
  ],
  "pageIsDeleted": false,
  "publisherPlatforms": [
    "FACEBOOK",
    "INSTAGRAM",
    "AUDIENCE_NETWORK",
    "MESSENGER",
    "THREADS"
  ],
  "displayFormat": "IMAGE",
  "adText": "❤️Aesthetic presets for your phone✨",
  "title": "🥰a FREE and personalized APP for you",
  "caption": "play.google.com",
  "linkDescription": null,
  "linkUrl": "http://play.google.com/store/apps/details?id=com.icontrol.easy.widgets.themes",
  "landingUrl": "http://play.google.com/store/apps/details?id=com.icontrol.easy.widgets.themes",
  "landingDomain": "play.google.com",
  "landingUtm": null,
  "linkIsTemplate": false,
  "ctaText": "Install now",
  "ctaType": "INSTALL_MOBILE_APP",
  "images": [
    {
      "url": "https://scontent-lga3-3.xx.fbcdn.net/v/t39.35426-6/689490050_1634784394244050_406178336346...",
      "resizedUrl": "https://scontent-lga3-2.xx.fbcdn.net/v/t39.35426-6/691720374_1271927545153646_343053186879..."
    }
  ],
  "imageUrls": [
    "https://scontent-lga3-3.xx.fbcdn.net/v/t39.35426-6/689490050_1634784394244050_406178336346..."
  ],
  "videos": [],
  "videoUrls": [],
  "videoPreviewImageUrls": [],
  "cards": [],
  "cardCount": 0,
  "extraTexts": [],
  "extraLinks": [],
  "extraImageUrls": [],
  "extraVideoUrls": [],
  "allText": "❤️Aesthetic presets for your phone✨\n🥰a FREE and personalized APP for you\nplay.google.com",
  "adCategories": [
    "UNKNOWN"
  ],
  "isPolitical": false,
  "byline": null,
  "disclaimerLabel": null,
  "spendText": null,
  "spendLower": null,
  "spendUpper": null,
  "currency": null,
  "impressionsText": null,
  "impressionsLower": null,
  "impressionsUpper": null,
  "impressionsIndex": -1,
  "audienceSizeText": null,
  "audienceSizeLower": null,
  "audienceSizeUpper": null,
  "transparencyScope": "NONE",
  "targetedOrReachedCountries": [],
  "countryIsoCode": null,
  "gatedType": "ELIGIBLE",
  "isAaaEligible": true,
  "containsDigitalCreatedMedia": false,
  "containsSensitiveContent": false,
  "hideDataStatus": "NONE",
  "stateMediaRunLabel": null,
  "hasUserReported": false,
  "reportCount": null,
  "isReshared": false,
  "brandedContent": null,
  "event": null,
  "brazilTaxId": null,
  "additionalInfo": null,
  "ecCertificates": [],
  "regionalRegulation": {
    "finserv": {
      "is_deemed_finserv": false,
      "is_limited_delivery": false
    },
    "tw_anti_scam": {
      "is_limited_delivery": false
    }
  },
  "searchTerm": "nike",
  "searchCountry": "US",
  "searchAdType": "ALL",
  "queryKey": "term:nike:US",
  "scrapedAt": "2026-09-01T18:30:38.204Z",
  "changeType": "NEW",
  "isKnown": false,
  "firstSeenAt": "2026-09-01T18:30:38.204Z",
  "lastSeenAt": "2026-09-01T18:30:38.204Z",
  "charged": true
}
```

### Using the API

Same input as the form.

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~meta-ads-library-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "searchTerms": ["adidas"],
    "countries": ["US"],
    "activeStatus": "active",
    "mediaType": "video",
    "publisherPlatforms": ["instagram"],
    "deliveredWithinDays": "30",
    "maxAds": 100
  }'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation_craft/meta-ads-library-scraper').call({
    advertiserNames: ['Nike'], countries: ['ALL'], fetchAdDetails: true, maxAds: 200, memoryName: 'nike-watch',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.type === 'ad').length, 'ads');
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation_craft/meta-ads-library-scraper").call(run_input={
    "searchTerms": ["vote"], "countries": ["US"], "adType": "political_and_issue_ads", "bylines": ["VOTEVETS"], "fetchAdDetails": True, "maxAds": 50,
})
ads = [i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if i["type"] == "ad"]
```

Input keys are tolerant: country names or codes, enum labels or ids, lowercase or uppercase, strings or arrays, and the common aliases other scrapers use (`keyword`, `maxItems`, `startUrls`, `period`, `startDateMin`) are accepted.

### Recipes

- **Competitor monitoring:** advertiser pages + `memoryName`, scheduled daily. Only new ads arrive; `changeType: UPDATED` (with "Also return already-known ads") flags ads whose status, dates or creative changed.
- **Winning ads:** keyword + `activeStatus: active` + `minDaysRunning: 30` + `minCollationCount: 3` + `sortBy: total_impressions_desc`.
- **Political spend research:** `adType: political_and_issue_ads` + `bylines` or `regions` + `fetchAdDetails: true` for age / gender / region distributions and lifetime spend by disclaimer.
- **EU DSA audits:** an EU country + `fetchAdDetails: true` for reach by country, age and gender and the declared targeting.
- **Sizing a job:** the same input with `onlyTotalCount: true` first (free).

### What this Actor does NOT do

- It does not log in and never asks for cookies or tokens, so it only sees what the public Ad Library shows. Spend, impressions and audience size exist for political ads only; EU reach data only for ads shown in the EU. Meta's ranges (">$1M", "100K-500K") are ranges, not exact numbers.
- Very broad keywords: the Ad Library's own result pages thin out after a few hundred ads (`stopReason: thin_pages`, `coverage: partial`). Split the job by country, platform, date window or advertiser page for complete coverage.
- It does not download media files; it delivers the CDN URLs (which expire after some days, so download what you need).
- It does not read the aggregate "Ad Library report" spend tables, branded-content listings or the collation "summary" panel; open an issue if you need them.
- Meta rate-limits roughly half of all residential IPs. The Actor rotates IPs automatically and the run summary shows `proxy.blockedIps`; a query that cannot get a clean IP is reported as `coverage: blocked` and costs nothing.

### Sibling listings (same engine)

- [Facebook Ads Library Scraper](https://apify.com/automation_craft/facebook-ads-library-scraper)
- [Instagram Ads Library Scraper](https://apify.com/automation_craft/instagram-ads-library-scraper)

### Changelog

- 1.0.0: first release. Keyword, page, advertiser-name, URL and ad-ID modes; every Ad Library filter; exact caps with fair share; free total count; post-filters; cross-run memory; ad details (EU, political, advertiser); landing-page resolution; per-query coverage reporting.

# Actor input Schema

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

One search per line, exactly like typing in the Ad Library search box. Each keyword is searched in every selected country. Leave empty when you search by page, advertiser, URL or ad ID.

## `searchType` (type: `string`):

Unordered (default): ads containing the words in any order. Exact phrase: the words together, as the Ad Library does when you put a phrase in quotes.

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

Facebook page IDs, page URLs (facebook.com/nike, facebook.com/profile.php?id=...), Instagram profile URLs or Ad Library "view all page" URLs. Page names / handles are resolved to IDs automatically. Returns every ad the page runs (the "See all ads" view).

## `advertiserNames` (type: `array`):

Advertiser names as you would type them in the Ad Library search box, e.g. "Nike" or "VoteVets". The best matching page is used; the run summary lists the candidates so you can pin the ID next time.

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

Paste any facebook.com/ads/library URL. Every parameter in it (keyword, page, country, ad type, status, media type, platforms, languages, date range, sort, political filters) is applied to that query. URLs with ?id= fetch that single ad.

## `adArchiveIds` (type: `array`):

Ad Library IDs to fetch directly, e.g. 161966936869658, or ?id= URLs. Each returns the current record of that ad (status, dates, creative, political spend). Unknown IDs are reported and free.

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

Where the ads were shown. "All countries" (ALL) works for every ad type. Select several to run one query per country; "EU" (via the API) expands to the 27 member states. Names and ISO codes are both accepted through the API.

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

The Ad Library category. "All ads" covers commercial ads. Political ads carry spend, impressions, audience size and paid-for-by data; housing, employment and financial categories are the regulated US categories.

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

Active (running now), inactive (ended) or both.

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

Images, memes (images with text), images and memes, videos, or ads with no image or video (text only).

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

Keep ads that ran on at least one of the selected placements. Empty = any platform.

## `contentLanguages` (type: `array`):

Language of the ad text (ISO 639-1). Empty = any language. Through the API any two-letter code or English language name is accepted.

## `deliveredWithinDays` (type: `string`):

The Ad Library "Impressions by date" filter: ads that were shown in this window (not ads that started in it). Any number of days works through the API.

## `deliveredAfter` (type: `string`):

Start of the impressions window (YYYY-MM-DD). Overrides the relative window.

## `deliveredBefore` (type: `string`):

End of the impressions window (YYYY-MM-DD).

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

Order of results, exactly as the Ad Library offers it. Impressions sorting is most useful for political ads (where impressions are published).

## `isTargetedCountry` (type: `boolean`):

The Ad Library "is\_targeted\_country" switch: keep only ads whose advertiser targeted the selected country, rather than ads that merely reached people there.

## `bylines` (type: `array`):

Exact disclaimer labels, e.g. "VOTEVETS". Political ads only.

## `regions` (type: `array`):

Region names as the Ad Library shows them, e.g. "California", "Texas". Political ads only.

## `audienceSizeMin` (type: `integer`):

Lower bound of the Ad Library audience-size buckets (100, 1000, 5000, 10000, 50000, 100000, 500000, 1000000). Political ads only.

## `audienceSizeMax` (type: `integer`):

Upper bound (1000, 5000, 10000, 50000, 100000, 500000, 1000000; leave empty for no upper bound). Political ads only.

## `minDaysRunning` (type: `integer`):

Keep only ads that have run at least this many days (inactive ads: their total run length). A quick "proven winner" filter. Free.

## `minCollationCount` (type: `integer`):

Keep only ads whose creative is used by at least this many ads (the "N ads use this creative and text" number). Free.

## `displayFormats` (type: `array`):

Keep only these creative formats. DCO = dynamic creative, DPA = catalogue (product) ads.

## `mustIncludeText` (type: `array`):

Keep ads whose text, title, caption, page name or link contains at least one of these (case-insensitive).

## `mustExcludeText` (type: `array`):

Drop ads containing any of these.

## `excludePageIds` (type: `array`):

Page IDs or page URLs whose ads should be dropped.

## `onlyActiveNow` (type: `boolean`):

Client-side safety net on top of the Active status filter.

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

Hard cap on delivered, charged ads for the whole run. Honoured exactly: you never receive or pay for more. Through the API, 0 means no cap.

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

Cap per keyword x country (or page x country). Defaults to the total cap.

## `onlyTotalCount` (type: `boolean`):

Do not fetch ads: return one row per query with the Ad Library total (">50,000" shows as 50001). Free, so you can size a job before paying.

## `fetchAdDetails` (type: `boolean`):

Adds the "See ad details" data per ad: advertiser profile (page likes, category, verification, Instagram handle and followers, lifetime spend by disclaimer), EU (DSA) transparency (reach by country, age and gender, targeting), political insights (age / gender and region distribution) and payer / beneficiary. $0.25 per 1,000 ads that got details.

## `resolveLandingPages` (type: `boolean`):

Follows the ad link through redirects (l.facebook.com wrappers, shorteners, trackers) to the final URL and domain. $0.20 per 1,000 pages that answered. Facebook / Instagram destinations and catalogue templates are skipped.

## `memoryName` (type: `string`):

Name this search, e.g. "competitor-watch". Ads delivered under that name are remembered in your account (key-value store "ads-library-memory-<name>") and never delivered or charged again. Schedule the run and you have a free change feed.

## `emitKnownAds` (type: `boolean`):

Re-send ads the memory already knows, marked isKnown = true with changeType DUPLICATE or UPDATED (status, dates, spend or creative changed). Never charged.

## `resetMemory` (type: `boolean`):

Forget everything stored under the memory name before running.

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

Leave empty: the Actor uses Apify residential proxies for the Ad Library API calls (Meta blocks datacenter IPs) and a datacenter proxy for page loads. Set your own only if you know why.

## `maxConcurrency` (type: `integer`):

How many queries (keyword x country) run at the same time, each on its own IP. 3 is a good default; 6-8 for big multi-country jobs.

## `maxIpRetries` (type: `integer`):

Roughly half of residential IPs are rate-limited by the Ad Library; the Actor rotates to a fresh IP automatically. 6 tries gives above 99 % success.

## `maxEmptyPages` (type: `integer`):

Deep result pages get thin; after this many consecutive empty pages the query is marked partial and stops.

## Actor input object example

```json
{
  "searchTerms": [
    "adidas"
  ],
  "searchType": "keyword_unordered",
  "countries": [
    "US"
  ],
  "adType": "all",
  "activeStatus": "all",
  "mediaType": "all",
  "deliveredWithinDays": "any",
  "sortBy": "default",
  "isTargetedCountry": false,
  "onlyActiveNow": false,
  "maxAds": 20,
  "onlyTotalCount": false,
  "emitKnownAds": false,
  "resetMemory": false,
  "maxConcurrency": 3,
  "maxIpRetries": 6,
  "maxEmptyPages": 3
}
```

# Actor output Schema

## `ads` (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": [
        "adidas"
    ],
    "countries": [
        "US"
    ],
    "maxAds": 20,
    "fetchAdDetails": false,
    "resolveLandingPages": false,
    "memoryName": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/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 = {
    "searchTerms": ["adidas"],
    "countries": ["US"],
    "maxAds": 20,
    "fetchAdDetails": False,
    "resolveLandingPages": False,
    "memoryName": "",
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/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 '{
  "searchTerms": [
    "adidas"
  ],
  "countries": [
    "US"
  ],
  "maxAds": 20,
  "fetchAdDetails": false,
  "resolveLandingPages": false,
  "memoryName": ""
}' |
apify call automation_craft/meta-ads-library-scraper --silent --output-dataset

```

## MCP server setup

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