# Meta Ads Library Scraper (Facebook Ads) (`sol-arkis/meta-ads-library-scraper`) Actor

For creative strategists, copywriters, media buyers, marketers, and agencies. Scrape the Meta Ad Library by exact phrase, keyword, page, or Library URL. Control country, status, media type, platforms, dates, and impressions sort. $0.60 per 1,000 ads. No Facebook token.

- **URL**: https://apify.com/sol-arkis/meta-ads-library-scraper.md
- **Developed by:** [Arkis](https://apify.com/sol-arkis) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 ad results

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

This Actor is for **creative strategists, copywriters, media buyers, marketers, agencies**, and anyone watching competitors in the [Meta Ad Library](https://www.facebook.com/ads/library) (Facebook Ad Library, covering Facebook, Instagram, Messenger, Audience Network and Threads).

When the job is Meta ads, this is the scraper you reach for. You search by keyword (any word or **exact phrase**), paste an Ad Library URL, or point it at a Facebook page. You pick the country, keep **active** ads or include inactive ones, filter by media type and publisher platform, bound a delivery window, and sort by **impressions** or recency. Turn on ad details when you need payer, beneficiary, and EU reach. The inputs stay stable enough to schedule as a monitor.

You get the ad copy, headline, CTA, landing page, image and video URLs, carousel cards, platforms, start and end dates, days running, and, where Meta discloses them, payer, beneficiary and EU reach. Output is clean JSON, CSV or Excel, with no Facebook login, access token or app review.

It works as a **Facebook Ad Library API alternative**. The official Ad Library API only covers political/issue ads and EU-delivered ads, and it needs identity verification. This Actor reads the same public pages you see at facebook.com/ads/library.

### What can it do?

- 🔎 **Keyword search**: any-word or exact-phrase, in one country or `ALL` countries.
- 🔗 **Paste Ad Library URLs** straight from your browser. Their filters (country, status, media type, sort, dates, language, platform) are kept.
- 🏷️ **All ads from a page**: Facebook page URL, `profile.php?id=…`, numeric page ID or `view_all_page_id=` URL.
- 🎯 **Single ad** by `…/ads/library/?id=<adArchiveId>`.
- 📅 **Delivery window and platforms**: first-class `startDateMin` / `startDateMax` (Meta's delivered-impressions window) and `publisherPlatforms` (Facebook, Instagram, Messenger, Audience Network, Threads).
- 🧾 **Ad details** (on by default): payer, beneficiary, EU total reach and EU audience (age, gender, locations), Instagram handle and followers, page verification.
- 🖼️ **Creatives**: image URLs, HD and SD video URLs, video thumbnails, and every carousel / dynamic-product card in `snapshot.cards`.
- 💾 **Durable media save** (`saveMedia`, off by default): download images and videos into the run key-value store and keep Apify URLs that outlast signed Meta CDN links.
- 📡 **Monitor mode**: schedule the same query and only write ads not seen for that fingerprint. Optional `emitAdChanges` writes `itemType: "ad-change"` when copy, CTA, landing URL, media, status, platforms or dates shift.
- ♻️ **Deduplicated** by `adArchiveId` across all your queries. You never pay twice for the same ad in a run.
- 🛟 **Resilient**: solves Meta's JS challenge, retries with exponential backoff, rotates proxy sessions on blocks, and resumes after migrations. Every run writes a summary to the `OUTPUT` record.

### How to scrape the Meta Ad Library

See the input tab for every field. Four starting points:

#### 1. Competitor page watch

Paste the competitor's Facebook page (or an Ad Library `view_all_page_id=` URL). Sort by most recent, keep ads active, and schedule the run. Use `mode: "monitor"` if you only want ads that were not seen for this query before.

```json
{
  "urls": ["https://www.facebook.com/BachaCoffeeOfficial/"],
  "country": "SG",
  "activeStatus": "active",
  "sortBy": "most_recent",
  "maxAds": 100,
  "scrapeAdDetails": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### 2. Keyword exact-phrase winners

Lock the phrase so you are not matching every word in any order. Sort by impressions to see what actually ran at scale.

```json
{
  "keywords": ["best running shoes"],
  "searchType": "keyword_exact_phrase",
  "country": "US",
  "activeStatus": "active",
  "sortBy": "impressions",
  "mediaType": "all",
  "maxAds": 150,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### 3. Creative strategist swipe file

Pull video (or image) ads for a theme, optionally on Instagram only, and keep details on so you can see who paid when Meta discloses it.

```json
{
  "keywords": ["skincare routine"],
  "searchType": "keyword_exact_phrase",
  "country": "GB",
  "mediaType": "video",
  "publisherPlatforms": ["instagram"],
  "sortBy": "impressions",
  "activeStatus": "active",
  "maxAds": 80,
  "scrapeAdDetails": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### 4. Agency multi-country

Set `country` to `ALL`, or paste one Ad Library URL per market so each URL keeps its own country filter. Bound a delivery window if you only care about a flight.

```json
{
  "urls": [
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=US&view_all_page_id=22437985072&search_type=page",
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=GB&view_all_page_id=22437985072&search_type=page",
    "https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=DE&view_all_page_id=22437985072&search_type=page"
  ],
  "startDateMin": "2026-01-01",
  "maxAds": 200,
  "scrapeAdDetails": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

A pasted Library URL already carrying `country`, `start_date`, or `publisher_platforms` keeps those values. The fields above fill in anything the URL leaves blank.

#### 5. Scheduled monitor with durable creatives

```json
{
  "urls": ["https://www.facebook.com/BachaCoffeeOfficial/"],
  "country": "SG",
  "activeStatus": "active",
  "sortBy": "most_recent",
  "maxAds": 80,
  "mode": "monitor",
  "emitAdChanges": true,
  "saveMedia": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

The first monitor run saves matching ads (charged as `ad`) and records their ids. Later runs only write unseen ads. With `emitAdChanges`, later runs also write `itemType: "ad-change"` when snapshotted fields move. `saveMedia` stores files in the run key-value store and adds `savedImageUrls` / `savedVideoUrls`.

### Pricing

Pay per result. Two add-ons stay off unless you turn them on.

| Event | Default | Price | When it charges |
|---|---|---|---|
| `ad` | always | $0.0006 ($0.60 / 1,000) | Each unique ad written to the dataset |
| `media-save` | off (`saveMedia`) | $0.0015 ($1.50 / 1,000) | Each ad that had at least one image or video saved to the run key-value store |
| `ad-change` | off (`mode: "monitor"` and `emitAdChanges`) | $0.0008 ($0.80 / 1,000) | Each previously seen ad whose snapshotted fields changed |

Failed downloads, duplicate ads, and unchanged previously seen ads are free. The first monitor run with `emitAdChanges` only stores snapshots; it does not charge `ad-change`. Basic `imageUrls` / `videoUrls` stay in the `ad` item even when `saveMedia` is off.

You also pay Apify's standard Actor start event. Set a **maximum cost per run** in Apify Console and the Actor stops cleanly when the limit is reached.

| Ads | `ad` only | All 1,000 ads also saved media |
|---|---|---|
| 100 | $0.06 | $0.21 |
| 1,000 | $0.60 | $2.10 |
| 10,000 | $6.00 | $21.00 |

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `keywords` | string\[] | `["coffee"]` (prefill) | One query per keyword. Pair with `searchType`. |
| `urls` | string\[] | – | Ad Library URLs, Facebook page URLs, page IDs or single-ad URLs. |
| `country` | string | `SG` | ISO-2 code (`US`, `GB`, `DE`, …) or `ALL`. |
| `activeStatus` | `active` / `inactive` / `all` | `active` | |
| `mediaType` | `all` / `image` / `video` / `meme` / `none` | `all` | |
| `maxAds` | integer | `50` | **Per query** (each keyword / URL). |
| `sortBy` | `impressions` / `most_recent` | `impressions` | Same as the site's sort. |
| `searchType` | `keyword_unordered` / `keyword_exact_phrase` | `keyword_unordered` | Exact phrase for tight copy/angle hunts. |
| `startDateMin` | YYYY-MM-DD | – | Ads that delivered on or after this date. |
| `startDateMax` | YYYY-MM-DD | – | Ads that delivered on or before this date. |
| `publisherPlatforms` | string\[] | – | `facebook`, `instagram`, `messenger`, `audience_network`, `threads`. Empty = all. |
| `mode` | `search` / `monitor` | `search` | `monitor` only writes ads not stored for this query fingerprint (named store `meta-ads-library-monitor`, cap 20,000 ids). |
| `emitAdChanges` | boolean | `false` | Monitor only. Emit `itemType: "ad-change"` when snapshotted creative fields change. First run stores snapshots only. |
| `saveMedia` | boolean | `false` | Download creatives into the run key-value store and add durable Apify URLs. Charged as `media-save` once per ad with at least one saved file. |
| `scrapeAdDetails` | boolean | `true` | One extra small request per ad for payer/beneficiary/EU fields. Fails soft. |
| `includeRaw` | boolean | `false` | Adds the unmodified Meta objects as `raw`. |
| `paginationFallback` | boolean | `true` | See [Limitations](#limitations). |
| `proxyConfiguration` | proxy | `{ "useApifyProxy": true }` | **Use the `RESIDENTIAL` group when `maxAds` > 30.** |

### Output

One dataset item per unique ad (and, if you asked for them, `ad-change` rows). The dataset has views for **Overview**, **Creatives & media**, **Payer, beneficiary & EU reach**, and **Ad changes**.

| Field | Description | Availability |
|---|---|---|
| `adArchiveId`, `adLibraryUrl` | Ad Library ID and public link | always |
| `pageId`, `pageName`, `pageProfileUrl`, `pageProfilePictureUrl`, `pageLikeCount`, `pageCategories` | Advertiser page | always (likes may be null) |
| `primaryText`, `headline`, `description`, `ctaText`, `ctaType`, `linkUrl`, `linkCaption` | Ad copy & destination. For dynamic product ads (`{{product.name}}` templates) the first card's real text is used | when the ad has them |
| `displayFormat` | `IMAGE`, `VIDEO`, `CAROUSEL`, `DPA`, `DCO`, `MULTI_IMAGES`, … | always |
| `imageUrls`, `videoUrls` (HD + SD), `videoPreviewImageUrls` | Media of the ad and all its cards | when present. fbcdn URLs are signed and expire after about 1-4 days |
| `savedMedia`, `savedImageUrls`, `savedVideoUrls`, `mediaStoreId` | Durable copies in this run's key-value store (`media/{adArchiveId}/…`) | only with `saveMedia`, and only for files that downloaded |
| `itemType`, `changes`, `before` | `ad` or `ad-change`, plus the field diffs | `itemType`/`changes`/`before` when `emitAdChanges` is on |
| `publisherPlatforms` | `FACEBOOK`, `INSTAGRAM`, `AUDIENCE_NETWORK`, `MESSENGER`, `THREADS` | always |
| `startDate`, `endDate` | ISO dates. For active ads `endDate` is the latest delivery date (≈ today) | always |
| `isActive`, `daysRunning` | Days from start to today (active) or to end date | always |
| `collationId`, `collationCount` | Group of near-identical versions ("N ads use this creative") | usually |
| `impressionsText`, `impressionsIndex`, `spend`, `currency`, `reachEstimate` | **Only disclosed by Meta for political/issue ads** (and some regions); `null` for normal commercial ads | rarely |
| `payer`, `beneficiary`, `payerBeneficiary` | Who paid / who benefits | with `scrapeAdDetails`, for ads that reached the EU or fall under regional disclosure rules |
| `targetsEu`, `euTotalReach`, `euAudience` | EU DSA transparency: total EU reach, gender, age range, locations | with `scrapeAdDetails`, EU-delivered ads |
| `instagramUsername`, `instagramFollowers`, `pageVerification` | From the details endpoint | with `scrapeAdDetails` |
| `country`, `keyword`, `sourceUrl`, `foundVia`, `scrapedAt` | Query context. `foundVia` = `ssr` / `pagination` / `fallback` | always |
| `snapshot` | Cleaned creative: body, title, images, videos, **`cards[]` for carousels / DPA**, extra texts/links | always |
| `raw` | Unmodified Meta data | only with `includeRaw` |

#### Sample item

Real output from a run with `keywords: ["coffee"]`, `country: "SG"` (long URLs shortened):

```json
{
  "adArchiveId": "27086586400970202",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=27086586400970202",
  "pageId": "114040321562599",
  "pageName": "Muse App",
  "pageLikeCount": 965,
  "pageProfileUrl": "https://www.facebook.com/100088641745631/",
  "pageCategories": ["Photo & Video"],
  "primaryText": "Easy Reels  Maker! ✨ Try it now!",
  "headline": "Muse: Best Reels Maker",
  "description": null,
  "ctaText": "Install now",
  "ctaType": "INSTALL_MOBILE_APP",
  "displayFormat": "VIDEO",
  "linkUrl": "http://itunes.apple.com/app/id1638320348",
  "linkCaption": "itunes.apple.com",
  "imageUrls": [],
  "videoUrls": [
    "https://video-iad3-1.xx.fbcdn.net/o1/v/t2/f2/m366/AQMVY5oW3u…",
    "https://video-iad3-1.xx.fbcdn.net/o1/v/t2/f2/m412/AQO2-5UGzc…"
  ],
  "publisherPlatforms": ["FACEBOOK", "INSTAGRAM", "AUDIENCE_NETWORK", "MESSENGER", "THREADS"],
  "startDate": "2026-06-13T07:00:00.000Z",
  "endDate": "2026-11-05T08:00:00.000Z",
  "isActive": true,
  "daysRunning": 146,
  "collationId": "1306591561637611",
  "collationCount": 1,
  "impressionsText": null,
  "impressionsIndex": -1,
  "spend": null,
  "currency": null,
  "payer": "BIGROWTH LTD",
  "beneficiary": "BIGROWTH LTD",
  "targetsEu": true,
  "euTotalReach": 739152,
  "euAudience": {
    "gender": "All", "ageMin": 20, "ageMax": 65,
    "locations": [
      { "name": "Worldwide", "type": "country_groups", "excluded": false },
      { "name": "Africa", "type": "country_groups", "excluded": true }
    ]
  },
  "instagramUsername": "muse.templates",
  "instagramFollowers": 433982,
  "pageVerification": "NOT_VERIFIED",
  "detailsFetched": true,
  "country": "SG",
  "keyword": "coffee",
  "foundVia": "ssr",
  "scrapedAt": "2026-11-05T08:50:06.405Z",
  "snapshot": {
    "displayFormat": "VIDEO",
    "body": "Easy Reels  Maker! ✨ Try it now!",
    "title": "Muse: Best Reels Maker",
    "videos": [{ "videoHdUrl": "https://video-iad3-1…", "videoSdUrl": "https://video-iad3-1…", "videoPreviewImageUrl": "https://scontent-iad6-1…" }],
    "cards": []
  }
}
```

#### Run summary (`OUTPUT` key-value record)

Each run writes:

- ads saved per query, and Meta's reported total for the query
- pages fetched, and whether pagination was blocked
- how many ads came from each source (`foundVia`)
- per-query errors
- detail-fetch stats
- media-save stats (files saved / failed) when `saveMedia` is on
- monitor fingerprint, previously seen count, and change stats when `mode` is `monitor`
- HTTP stats: requests, retries, blocks by type, session rotations
- the GraphQL `doc_id`s used

### Limitations

Read these before running large jobs:

- **Meta throttles pagination.** The first ~30 results of every search come embedded in the page and are reliable. Further pages need Meta's `AdLibrarySearchPaginationQuery`, which Meta often answers with `Rate limit exceeded` for logged-out traffic from datacenter IPs. The Actor backs off and rotates proxy sessions; **RESIDENTIAL proxy gives the best chance**.
- **Pagination fallback.** If pagination stays blocked and `paginationFallback` is on, the Actor re-runs the first results page over narrower slices of your search. A slice can be a content language, media type, platform, past month, or the other sort order. In tests this raised a no-proxy run from 30 to 300 unique ads for "coffee" in Singapore. All ads still match your filters, but:
  - the extra ads are not in your chosen sort order;
  - the fallback cannot reach every ad of a query with tens of thousands of results;
  - `foundVia: "fallback"` marks these ads.
- **Spend, impressions and reach are mostly `null`.** Meta only publishes them for political/issue ads, plus EU reach for EU-delivered ads (needs `scrapeAdDetails`).
- **Media URLs expire.** Signed Meta CDN links last about 1-4 days. Turn on `saveMedia` if you need copies in the run key-value store (charged as `media-save`). Those files last as long as the run storage does.
- **Monitor baseline is what this Actor fetched**, not the entire Ad Library. The first `mode: "monitor"` run records every ad it actually saw (up to pagination / fallback). Later runs emit ads that were not in that set. Ids are stored in `meta-ads-library-monitor`, keyed by a hash of keywords, urls, country, status, media type, search type, dates and platforms, and capped at 20,000.
- **Political & issue ad type** (`ad_type=political_and_issue_ads`) may need a logged-in session on some countries; this Actor does not log in.
- **Page URLs by vanity name** (facebook.com/SomeBrand) are resolved to a page ID on a best-effort basis. If that fails, use an Ad Library URL with `view_all_page_id=`.
- Meta changes its internal web API without notice. The `doc_id`s are discovered from the live page and fall back to known values. Expect occasional breakage; please report issues.

### Is it legal?

The Actor only collects data that Meta publishes openly in its Ad Library for transparency, and it never logs in. You are responsible for how you use the data, including any personal data, under GDPR and other laws, and for complying with Meta's terms.

### Coming later

These already work if you paste an Ad Library URL that sets them. They are not first-class input fields yet:

- **Content language** (`content_languages` on a Library URL)
- **Ad type**, including political and issue ads (`ad_type=` on a Library URL). Some countries may need a logged-in session for that filter; this Actor does not log in.
- **A list of countries in one keyword search**, without using `ALL` or one URL per market

### FAQ

**Do I need a Facebook account or API token?** No.

**Why did I get only ~30 ads?** Pagination was blocked. Switch the proxy to `RESIDENTIAL` and keep `paginationFallback` on. The `OUTPUT` record shows `paginationBlocked` per query.

**Can I monitor competitors?** Yes. Set `mode: "monitor"` on a scheduled run with the same keywords or page URLs. The Actor only writes ads it has not stored for that query fingerprint. Turn on `emitAdChanges` if you also want rows when an already-seen ad changes copy, CTA, landing URL, media, status, platforms or dates.

**How do I keep creatives after the CDN links expire?** Set `saveMedia` to `true`. Files land in the run key-value store under `media/{adArchiveId}/…` and the dataset item gets `savedMedia` plus `savedImageUrls` / `savedVideoUrls`. You pay `media-save` once per ad that stored at least one file.

# Actor input Schema

## `keywords` (type: `array`):

Keywords to search in the Ad Library, e.g. "coffee" or a brand name. Each keyword is one query. Use "Search type" to switch between any-word and exact-phrase matching.

## `urls` (type: `array`):

Optional. Ad Library search URLs copied from your browser (facebook.com/ads/library/?...). Their filters (country, q, search_type, media_type, active_status, sort, start_date, view_all_page_id, id) are respected; missing filters fall back to the fields below. Also accepts Facebook page URLs (facebook.com/YourPage, profile.php?id=…), bare numeric page IDs (all ads by that page) and single-ad URLs (…/ads/library/?id=<adArchiveId>).

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

2-letter ISO country code the ads were shown in (e.g. SG, US, GB, DE, AU) or ALL for every country.

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

Active ads only, inactive ads only, or both.

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

Filter by creative type.

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

Maximum number of unique ads to save for each keyword / URL. You pay per saved ad. Above 30 ads Meta needs to paginate — use a RESIDENTIAL proxy for that.

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

Order of results as on the Ad Library site.

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

keyword_unordered matches ads containing all the words in any order; keyword_exact_phrase matches the exact phrase.

## `startDateMin` (type: `string`):

Optional. Only include ads that delivered impressions on or after this date (YYYY-MM-DD). This is Meta's delivery window, not the ad creation date. Pasted Ad Library URLs that already set start_date\[min] keep their own value.

## `startDateMax` (type: `string`):

Optional. Only include ads that delivered impressions on or before this date (YYYY-MM-DD). Use with Delivered after to bound a window. Pasted Ad Library URLs that already set start_date\[max] keep their own value.

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

Optional. Limit results to ads that ran on these surfaces. Leave empty for every platform Meta returns (Facebook, Instagram, Messenger, Audience Network, Threads). Pasted Ad Library URLs that already set publisher_platforms keep theirs.

## `mode` (type: `string`):

search (default) saves every matching ad up to maxAds. monitor only writes ads whose adArchiveId was not stored for this query fingerprint (keywords, urls, country, status, media type, search type, delivery dates, platforms) in the named key-value store meta-ads-library-monitor. Schedule the same input to watch a page or keyword. The first run is the baseline: every ad it fetched is recorded as seen.

## `emitAdChanges` (type: `boolean`):

Only used when Mode is monitor. Snapshot primary text, headline, CTA, landing URL, media URLs, active status, platforms, and start/end dates. When a previously seen ad comes back with a different snapshot, write an itemType: ad-change row (charged as ad-change). The first run only stores snapshots; it does not emit or charge changes. New ads are still charged as ad.

## `saveMedia` (type: `boolean`):

Optional paid add-on, off by default. Download each ad's images and videos (HD preferred) plus video preview stills into this run's key-value store, and add savedMedia, savedImageUrls and savedVideoUrls with durable Apify URLs. Original Meta CDN URLs stay on the item; they expire in about 1-4 days. Charged once per ad that had at least one file saved (media-save). Failed downloads are skipped and do not fail the run.

## `scrapeAdDetails` (type: `boolean`):

Makes one extra request per ad to Meta's ad-details endpoint to add payer, beneficiary, EU total reach / audience (age, gender, locations), Instagram account and page verification. Fails soft: if Meta refuses, ads are still saved without these fields. Turn off for faster runs.

## `includeRaw` (type: `boolean`):

Adds a "raw" field with the unmodified Ad Library objects (search result + details). Makes items much larger.

## `paginationFallback` (type: `boolean`):

If Meta throttles cursor pagination ("Rate limit exceeded"), keep collecting ads by re-running the first results page over narrower slices of your search (content language, media type, platform, past months, alternate sort). All ads still match your filters, but the extra ones are not in the selected sort order and coverage of very large result sets is partial.

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

Apify Proxy is on by default. Choose the RESIDENTIAL group when maxAds is above 30 — Meta rate-limits Ad Library pagination from datacenter IPs and may show a login wall.

## Actor input object example

```json
{
  "keywords": [
    "coffee"
  ],
  "country": "SG",
  "activeStatus": "active",
  "mediaType": "all",
  "maxAds": 50,
  "sortBy": "impressions",
  "searchType": "keyword_unordered",
  "mode": "search",
  "emitAdChanges": false,
  "saveMedia": false,
  "scrapeAdDetails": true,
  "includeRaw": false,
  "paginationFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped ads: copy, headline, CTA, format, image/video URLs, carousel cards (snapshot.cards), platforms, dates, days running, collation, impressions/spend where disclosed, payer, beneficiary and EU reach (with scrapeAdDetails). When saveMedia is on, also savedMedia / savedImageUrls / savedVideoUrls pointing at this run's key-value store.

## `adsOverview` (type: `string`):

Table: page, primary text, headline, CTA, format, platforms, start date, active, days running, payer, EU reach, link.

## `adsMedia` (type: `string`):

Image and video URLs, landing page and headline per ad. Includes durable savedImageUrls / savedVideoUrls when saveMedia is on.

## `adsTransparency` (type: `string`):

Transparency fields from the per-ad details endpoint.

## `adsChanges` (type: `string`):

Monitor rows with itemType ad-change (body, CTA, landing URL, media, status, platforms, dates).

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

OUTPUT record: ads saved per query, Meta's reported total, pagination/fallback stats, blocks, session rotations, detail fetch stats, media-save stats, monitor fingerprint/stats, doc_ids used and errors.

# 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 = {
    "keywords": [
        "coffee"
    ],
    "country": "SG",
    "maxAds": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("sol-arkis/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 = {
    "keywords": ["coffee"],
    "country": "SG",
    "maxAds": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("sol-arkis/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 '{
  "keywords": [
    "coffee"
  ],
  "country": "SG",
  "maxAds": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call sol-arkis/meta-ads-library-scraper --silent --output-dataset

```

## MCP server setup

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