# Facebook Ads Library Scraper — EU Reach, Agency & Contacts (`foxlabs/meta-ad-library-scraper`) Actor

Ads from the Meta Ad Library (Facebook, Instagram, Messenger, Threads) by keyword, advertiser or link: copy, creatives, dates, platforms, landing page. EU reach, age/gender/country split, targeting and payer as columns; agency detection; optional advertiser e-mail/phone and image OCR.

- **URL**: https://apify.com/foxlabs/meta-ad-library-scraper.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 ads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Facebook Ads Library Scraper — EU Reach, Agency & Contacts

Get the ads advertisers run on **Facebook, Instagram, Messenger, Threads and Audience Network** from the public [Meta Ad Library](https://www.facebook.com/ads/library), by search term, advertiser or Ad Library link. Every ad comes back as one row: **ad text, headline, images and videos, start date and days running, platforms, call to action, landing page and domain**.

For ads shown in the EU, the same row carries what Meta publishes under the Digital Services Act: **total EU reach, reach by age, gender and country, the targeted locations, ages and gender, and the payer and beneficiary**. `paidByThirdParty` flags ads paid for by a different organisation, usually an agency.

Optional add-ons:

- **advertiser leads**: one row per advertiser with the e-mails and phone numbers its website publishes;
- **text inside image ads** (OCR).

No Facebook account, none of your cookies, no browser.

### Quick start (API)

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~meta-ad-library-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"advertisers": ["365604620536"], "country": "DE", "maxAdsPerQuery": 20}'
```

### What you get

| Group | Fields |
|---|---|
| Ad | `adArchiveId`, `adLibraryUrl`, `isActive`, `startDate`, `endDate`, `daysRunning`, `publisherPlatforms`, `displayFormat` (IMAGE, VIDEO, CAROUSEL, DPA, DCO…), `collationCount` (versions), `containsAiGeneratedMedia`, `adCategories` |
| Creative | `adText`, `title`, `linkDescription`, `caption` (display URL), `ctaText`, `ctaType`, `imageUrls`, `videoUrls`, `videoPreviewImageUrl`, `cards[]` (carousel and catalog cards: title, text, link, image, video), `cardsCount`, `isCatalogAd` |
| Destination | `linkUrl`, `landingUrl` (Facebook redirects unwrapped), `landingDomain`, `utmParams` |
| Advertiser | `pageId`, `pageName`, `pageUrl`, `pageCategories`, `pageLikes`, `pageProfilePictureUrl`, `igUsername`, `igFollowers`, `pageVerification` (from the EU details; for other ads with the "Advertiser info" option), `legalOwnerName` and `legalOwnerCountry` (the owner Meta confirmed, from the page's About tab; "Advertiser info" option) |
| EU transparency | `euTotalReach`, `euReachCountries`, `euReachByAgeGender[]` (country × age range: male, female, unknown), `targetLocations[]`, `targetAgeMin`, `targetAgeMax`, `targetGender`, `payer`, `beneficiary`, `paidByThirdParty`, `thirdPartyPayer` |
| Political and issue ads | `spendRange` and `spendLower`/`spendUpper`, `impressionsRange` and `impressionsLower`/`impressionsUpper`, `reachEstimate`, `currency`, `paidForBy`, `audienceAgeGender[]`, `audienceRegions[]` |
| Options | `imageText`, `imageTextConfidence`, `imageTextStatus` (OCR) · `isNew` (only-new-ads monitor) |
| Run | `query`, `queryType`, `searchCountry`, `detailsStatus`, `scrapedAt`, `status`, `error` |

Notes on the fields:

- **Placeholders:** catalog ads (Meta's DPA format) and dynamic-creative (DCO) ads show template placeholders such as `{{product.name}}` in Meta's snapshot. Those are never returned; the text of the ad's cards is used instead. `isCatalogAd` is true for DPA ads. A link with tracking macros keeps its address; only the macro parameters are dropped.
- **EU transparency** is published for ads shown in the EU. In an EU country nearly every ad has it: 998 of 1,000 German ads (run `aBP6CPzonHgvgYcQi`). In other countries only ads that were also shown in the EU carry it, for example 1 of 35 Nike ads in a US search (run `qjykPtwuaaxe3MzQ3`).
- **Reach buckets:** Meta's age/gender buckets do not add up to the total reach. In 649 of 998 ads (run `aBP6CPzonHgvgYcQi`) the buckets summed to more than the total EU reach; for example 4,464,330 against 4,280,485 for one Zalando ad (run `5z4yjpHVA45iesCWM`). So they are given as Meta publishes them, and the Actor computes no per-country totals.
- **Ranges:** spend and impressions of political ads are ranges as Meta shows them ("$60K - $70K", ">1M"). The numbers read from them are null where a bound is open ("<100" has no lower bound).
- **`detailsStatus`** says what the transparency fields hold:
  - `ok`: the data Meta publishes for the ad;
  - `not-in-eu`: Meta did not mark the ad for EU transparency, so there are no details. Usually the ad was not shown in the EU; in 1,000 German ads, 2 ads that started in 2021 and 2022 had this status too;
  - `no-eu-data`: an EU ad that came without the data;
  - `no-audience-data`: a political ad with no audience split yet. Among the first 20 US "vote" ads sorted by newest (run `T8HBbX5sCTkAzwS9y`), these were the 14 with fewer than 100 impressions;
  - `failed`: the request for the details failed;
  - `not-requested`: EU transparency is turned off.

#### Sample output

A real ad row (trimmed) from platform run `e3JB5DeZcHtCJTwTO` (Zalando's page, Germany, 2026-10-01, with `includeAdvertiserInfo` on):

```json
{
  "adArchiveId": "1462322829072299",
  "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1462322829072299",
  "isActive": true,
  "startDate": "2026-10-01",
  "daysRunning": 1,
  "pageName": "Zalando",
  "pageLikes": 8917204,
  "igUsername": "zalando",
  "igFollowers": 3371074,
  "legalOwnerName": "Zalando SE",
  "legalOwnerCountry": "Germany",
  "publisherPlatforms": ["INSTAGRAM"],
  "displayFormat": "DCO",
  "adText": "Was zieh ich an?",
  "title": "Finde deinen Style",
  "ctaText": "Learn more",
  "landingDomain": "zalando.de",
  "videoUrls": ["https://video-lga3-2.xx.fbcdn.net/o1/v/t2/f2/m366/…"],
  "euTotalReach": 121422,
  "euReachCountries": ["DE"],
  "euReachByAgeGender": [
    { "country": "DE", "ageRange": "18-24", "male": 31078, "female": null, "unknown": null },
    { "country": "DE", "ageRange": "25-34", "male": 55230, "female": null, "unknown": null }
  ],
  "targetLocations": [{ "name": "Germany", "type": "countries", "excluded": false }],
  "targetAgeMin": 18,
  "targetAgeMax": 54,
  "targetGender": "Men",
  "payer": "Zalando",
  "beneficiary": "Zalando",
  "paidByThirdParty": false,
  "detailsStatus": "ok"
}
```

#### Advertiser rows (option "Advertiser contacts")

One extra row per advertiser:

- `type: "advertiser"` and the advertiser's page;
- `website`, `emails`, `phones` and `contactPageUrl`;
- `legalOwnerName`, `adminCountries` and `pageCreatedAt` (with the "Advertiser info" option);
- `beneficiaries`, `payers` and `thirdPartyPayers` (EU);
- `adsFound`, `earliestAdStartDate`, `latestAdStartDate`, `publisherPlatforms` and `landingDomains`;
- `contactStatus`: `found`, `none-found`, `unreachable` or `no-website`.

How the contacts are found:

- **Website:** taken from the advertiser's ads: landing pages, display URLs (counted twice) and catalog links. Social networks, app stores, hosted funnel, form and booking pages and click trackers are skipped. When the ads lead only to such a page, the advertiser's own site is taken from the display URL or from that page's links.
- **Pages read:** the home page (on its own domain or on www), then the legal notice (Impressum in German-speaking countries) or contact page. A legal notice on another domain is read only when that domain carries the same name.
- **E-mails:** the advertiser's own domain first, then related domains, then mailbox addresses (t-online.de, gmx.de…) that carry its name. Addresses of chambers, supervisory, government and data-protection offices, outside data-protection officers and role mailboxes (data protection, press, compliance) are dropped. Other addresses are kept only when nothing better was found.
- **Limits:** when an advertiser advertises through an agency's landing page, the website and its contacts can be the agency's. Sites behind bot protection or very slow ones can stay unreachable.

### Input

| Field | What it does |
|---|---|
| `searchQueries` | Words to search, as on the website (all words, or `searchType: "keyword_exact_phrase"`) |
| `advertisers` | Facebook page ID, page link (`https://www.facebook.com/nike`) or name. A link or name is matched against the advertisers of the ads that mention it. When no page matches, you get a free row with the reason. The numeric ID always works. |
| `adLibraryUrls` | Links copied from the Ad Library: a search with its filters, an advertiser's page of ads (`view_all_page_id`), or one ad (`?id=`). A link's own filters win. |
| `country` | `ALL` or a country. In EU countries Meta publishes the EU transparency data for nearly every ad. |
| `activeStatus` | `active` (default), `inactive`, `all` |
| `adType` | `all`, `political_and_issue_ads`, `housing_ads`, `employment_ads`, `credit_ads`, `financial_products_and_services_ads` |
| `mediaType`, `platforms`, `languages` | Creative type; Facebook, Instagram, Audience Network, Messenger, Threads; two-letter ad languages |
| `datePreset`, `dateFrom`, `dateTo` | Ads shown in the last 7, 30 or 90 days, or between two dates (the website's "impressions by date" filter) |
| `sortBy` | `relevance` (website default), `impressions` (most impressions first), `newest` (Meta's newest-first order, not a strict date order) |
| `maxAdsPerQuery` | Ads per search, advertiser or link (default 100, up to 10,000; the form starts at 10) |
| `includeEuTransparency` | EU reach, breakdown, targeting, payer and beneficiary; political audience. On by default, included in the ad price. |
| `includeAdvertiserInfo` | From each advertiser's About tab: the legal owner Meta confirmed and its country, plus the Instagram account and page verification for ads outside the EU. Large brands usually have a confirmed owner; none of 65 dental practices had one (run `9tgcE5hX8lW1VRYRb`). With advertiser contacts on, the advertiser rows also get the admin countries and the page's creation date. Off by default because it adds one request per advertiser: 100 dentist ads took 70.8 s with it and 33.8 s without (runs `9tgcE5hX8lW1VRYRb` / `8IV3RawEDUXvmflgH`). Included in the ad price. |
| `includeAdvertiserContacts` | Advertiser rows with website contacts (off by default) |
| `ocrImageAds` | Text inside image ads (off by default; a few seconds per image) |
| `onlyNewAds`, `monitorName` | For scheduled runs: each search delivers only ads that the same search (same term, advertiser or link, same filters) did not deliver in earlier runs of the monitor. A changed filter starts a new list. Known ads are skipped and not charged; the search reads on until it has its maximum of new ads. |

### Examples

**A competitor's ads in Germany with EU reach, targeting and the legal owner** (Zalando's page ID):

```json
{ "advertisers": ["365604620536"], "country": "DE", "maxAdsPerQuery": 100, "includeAdvertiserInfo": true }
```

**Dentists advertising in Germany, with their contacts** (one advertiser row each):

```json
{ "searchQueries": ["zahnarzt"], "country": "DE", "maxAdsPerQuery": 300, "includeAdvertiserContacts": true }
```

**A daily monitor of a competitor: new ads only**:

```json
{ "advertisers": ["https://www.facebook.com/nike"], "country": "US", "onlyNewAds": true, "monitorName": "nike-us" }
```

**US political ads about voting, with spend and audience**:

```json
{ "searchQueries": ["vote"], "country": "US", "adType": "political_and_issue_ads", "maxAdsPerQuery": 50 }
```

### Use cases

- **Competitor research:** every ad a brand runs, how long each has been running and on which platforms.
- **EU reach:** how many people each ad reached in the EU, by age, gender and country, and whom it targeted.
- **Agencies:** which advertisers pay through an agency (`paidByThirdParty`, `thirdPartyPayer`) and which agency it is.
- **Lead generation:** find businesses that advertise on Meta in a niche and country, with the e-mails and phones their websites publish.
- **Monitoring:** schedule a run with `onlyNewAds` to receive only new ads and pay only for those (plus the run-start event).
- **Research:** political and issue ads with spend, impressions and audience.

### Data quality (measured)

Runs on the Apify platform, 2026-10-01, default memory (512 MB). Unless noted, build 0.1.10; the current build 0.1.12 differs only in a shorter page-load timeout.

| Platform run | Input | Result | Fill |
|---|---|---|---|
| `aBP6CPzonHgvgYcQi` | "zahnarzt" (dentist), Germany, 1,000 ads, defaults | 1,000 ads, all unique | EU reach 99.8% · targeting 98.2% · payer and beneficiary 98% · ad text 99.4% · landing domain 95.7% · image or video 99.8% |
| `k0rwymoNRUroj40cH` | the same search, 300 ads, advertiser contacts (the second example above) | 300 ads, 176 advertisers | website 79.5% · e-mail 57.4% · phone 56.3% · e-mail or phone 63.1% |
| `Obxau8a8KFcd8ftyK` | Zalando page, Germany, advertiser info on (the first example) | 62 ads | ad text, landing domain, image or video, EU reach, payer, Instagram, legal owner: 100% (one large brand) |
| `bxfplfDn38qsTamAv` | "vote", US, political and issue ads (the fourth example) | 50 ads | spend, impressions, audience by age/gender and region, "paid for by": 100% |
| `qjykPtwuaaxe3MzQ3`, then `2VtqCz3OhXeInNBKB` (builds 0.1.2, 0.1.3) | Nike page, US, only new ads (the third example), two runs | 35 ads, then none | the second run left one free row: "No new ads since the last run…" |
| `oQxNjPefAKkspniyz` (build 0.1.1) | "zahnarzt", Germany, 15 ads, text inside images | 8 image ads | text read in 5 of the 8 pictures; video ads are not read |

On the contacts run: the 36 advertisers without a website have ads that lead only to Facebook, Instagram, WhatsApp or an app store (24), have no link (7), or lead to a hosted page with no link to an own site (5). None of the 176 rows has an address of a chamber, an authority or a data-protection officer, and no date appears as a phone number.

Checks on these runs:

- **Re-read from the source:** 20 random ads of an earlier run of the contacts search (`JqbJrvuPohDEiQ8Rg`), opened again through their own Ad Library links, matched in all 8 compared fields (ID, page, page name, start date, status, format, platforms, text).
- **Links:** 12 of 12 image and video links returned real JPEG, PNG and MP4 files (partial downloads, run `gGAYPYElbr46PrpNA`). Landing links reached the row's domain. The one exception was an ad that links through a tracking domain, and then `landingDomain` is the tracker.
- **Contact pages:** each of the contact pages checked showed the e-mail or phone on the row (4 of 4).
- **Date filter:** a search for ads shown in March 2024 returned 30 ads, all of which ran during that month (run `flYeu7Q5o8N1bUpDG`).

Speed on the platform (512 MB). Times vary with how fast Meta answers:
| Input | Time |
|---|---|
| 10 ads (the form's first run) | 8–9 s (3 runs, build 0.1.12). A page load that hangs now costs at most 15 s more; earlier builds took up to 58 s then |
| 100 ads | 34 s |
| 1,000 ads | 277–659 s (2 runs, 99 result pages each) |
| 300 ads with advertiser contacts | 227–414 s (6 runs) |
| Text inside images | about 6 s per picture |

### Pricing

Pay per event:

- `ad`: each delivered ad, with EU transparency and advertiser info included.
- `advertiser-contacts`: each advertiser row whose website gave at least one e-mail or phone (contacts option only).
- `image-text`: each ad whose picture gave text (OCR option only).
- Apify's run-start event.

Free:

- status rows (nothing found, an unknown advertiser, an invalid link, a failed search); their message says that no ad was charged;
- advertiser rows without contacts.

Each ad is delivered and charged once per run, whichever search finds it first. Current prices are on the Pricing tab.

### Integrations

Use the [Apify API](https://docs.apify.com/api/v2) or the `apify-client` package for JavaScript and Python. Results connect to Make, Zapier, n8n and Google Sheets through Apify integrations, and AI agents can call the Actor through the Apify MCP server.

### FAQ

**Do I need a Facebook account or cookies?** No. The Actor reads the public Ad Library without logging in and without your cookies.

**Why are the EU fields empty for some ads?** Meta publishes reach, targeting and payer only for ads shown in the EU. In an EU country nearly every ad has them; `detailsStatus: "not-in-eu"` marks the others.

**Can I get stopped ads?** Yes, with `activeStatus: "inactive"` or `"all"`. A US search for "nike" listed more than 50,000 stopped ads, one of them started in 2023 (run `5DP5xNcCceOFr9SVl`). Meta shows result counts above 50,000 as 50,001; we saw it for "nike" (stopped ads) and "sale" (all ads, run `gckTWF2Q2JqeC54s1`) in the US.

**What does `paidByThirdParty` mean?** In the EU disclosure, the payer is a different organisation than the beneficiary. `thirdPartyPayer` gives the payer's name. In 1,000 German dental ads (run `aBP6CPzonHgvgYcQi`), 53 of the 980 ads with a disclosed payer named a different payer. Most were marketing agencies and practice-marketing firms; a few were related companies.

**What does a scheduled "only new ads" run return when nothing is new?** One free status row that says so. No ad is charged; only the run-start event applies (runs `2VtqCz3OhXeInNBKB` and `kRZYfmkCj5vw4O9RQ`).

**How is an advertiser given by name found?** The Actor searches the name, then picks the page among the first results: by the page link's name, then the exact page name, then a page whose name contains the given name (the one with the most ads). If none matches, a free status row explains it. Check `pageName` in the rows; the page ID is exact: open the advertiser in the Ad Library and copy `view_all_page_id` from the address.

**Is "days running" exact?** It counts from the start date to the end date Meta shows. For active ads, that end date is the latest day Meta shows the ad.

**How are contacts found?** From the advertiser's own website: home page, then its legal notice (Impressum) or contact page; see "How the contacts are found" above. Sites that do not answer give `contactStatus: "unreachable"`, and that row is free.

### Troubleshooting

- **A search returns a status row "empty":** Meta shows no ads for that term with these filters. Try another country, `activeStatus: "all"`, or fewer filters.
- **An advertiser name is "unresolved":** use the numeric page ID or the advertiser's Ad Library link.
- **The run is slow with OCR or contacts on:** both read extra pages per ad or advertiser. Turn them off for large runs, or lower `maxAdsPerQuery`.
- **Fewer ads than Meta's count:** Meta's result count is approximate and changes between page loads. `SOURCE_REPORT` in the key-value store shows Meta's count, the ads and pages read, the duplicates, and why each search stopped (`stopReason`: `max-ads-reached`, `no-more-results`, `no-new-ads-on-3-pages`, `page-limit`, `page-failed` or `max-charge-reached`).

### Notes and limits

- **Source:** the public Meta Ad Library website (no API key). Meta changes its pages from time to time. If its query IDs change, the Actor reads them again from the page's scripts. A search that fails leaves a free status row with the reason.
- **Personal data:** the EU payer or beneficiary of a small business can be a person's name, and contacts are business contact details the advertiser's website publishes. Use the data in line with the GDPR and your local law.

### Support

Questions or a field you need: open an issue on the Actor page or write to info@foxlabs.com.tr.

### Changelog

See [CHANGELOG.md](./CHANGELOG.md).

*Built by foXLabs.*

# Changelog

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

# Actor input Schema

## `searchQueries` (type: `array`):

One per line: words to search in the Ad Library, as you would type them on facebook.com/ads/library ("running shoes", "dentist", a brand name). Meta matches the words in the ads. Each search returns up to "Max ads per search".

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

One per line: an advertiser's Facebook page ID (15087023444), page link (https://www.facebook.com/nike) or name ("Nike"). Returns the ads that page runs. A name or page link is matched against the advertisers of the ads that mention it; when no page matches you get a free row with the reason, and the page ID always works.

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

One per line: links copied from facebook.com/ads/library: a search with its filters, an advertiser's page of ads (view\_all\_page\_id) or a single ad (?id=). Filters inside a link win over the filters below.

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

How the search terms are matched.

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

Ads shown in this country. In EU countries Meta publishes the reach, age/gender split, targeting and payer of nearly every ad (998 of 1,000 German ads; see "EU transparency"); elsewhere only for political and issue ads and for ads also shown in the EU. "All countries" searches everywhere.

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

Running now, stopped, or both. Stopped ads are listed in the US too (a "nike" search listed more than 50,000).

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

The Ad Library's ad categories. Political and issue ads carry spend, impressions and the audience split.

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

Creative type of the ads.

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

Only ads that run on these platforms. Empty = all platforms.

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

Two-letter language codes of the ads, such as en, de, fr, tr. Empty = all languages.

## `datePreset` (type: `string`):

Only ads Meta showed in this period (the Ad Library's "impressions by date" filter). "Custom" uses the two dates below.

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

YYYY-MM-DD. Used only when "Shown in period" is Custom.

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

YYYY-MM-DD. Used only when "Shown in period" is Custom.

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

Order of the results, as on the website. "Most impressions" puts the ads with the most impressions first. "Newest" is Meta's newest-first order, which is not a strict date order.

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

Stop each search, advertiser or link after this many ads (up to 10000). The form starts at 10 for a quick first run; an API call without this field gets 100. You pay per ad delivered.

## `includeEuTransparency` (type: `boolean`):

For ads shown in the EU: total EU reach, reach by country, age and gender, targeted locations, ages and gender, and the payer and beneficiary (with "paidByThirdParty" when an agency or other company paid). For political and issue ads: the audience split. One extra request per such ad; included in the ad price.

## `includeAdvertiserInfo` (type: `boolean`):

Per advertiser, from the page's About tab: the legal owner Meta confirmed and its country (large brands usually have one; none of 65 dental practices did), and the Instagram account and page verification for ads outside the EU (EU ads already carry them). With "Advertiser contacts" on, the advertiser rows also get the page admins' countries, the page's creation date and name changes. One extra request per advertiser: a broad search took about twice as long with it. Included in the ad price.

## `includeAdvertiserContacts` (type: `boolean`):

Adds one row per advertiser with its website (from its ads' landing pages) and the e-mails and phone numbers the website publishes (home page, then its legal notice / Impressum or contact page). Charged per advertiser only when at least one e-mail or phone is found.

## `ocrImageAds` (type: `boolean`):

Reads the text written in image ads (headlines, prices, offers in the picture) into "imageText". Slower: a few seconds per image. Charged per ad only when text is found.

## `onlyNewAds` (type: `boolean`):

For scheduled runs: remembers the ads each search delivered (in a key-value store named by "Monitor name" in your account) and delivers only ads that search has not delivered before. The memory is per search: the same term, advertiser or link with the same filters; a changed filter starts a new list. Known ads are skipped and not charged, and the search reads on until it has "Max ads per search" new ads (at most about Max/10 + 30 result pages).

## `monitorName` (type: `string`):

Name of the key-value store that keeps the delivered ad IDs (letters, digits, dashes). Use one name per monitored set of searches. Empty = meta-ad-library-monitor.

## Actor input object example

```json
{
  "searchQueries": [
    "zalando"
  ],
  "searchType": "keyword_unordered",
  "country": "DE",
  "activeStatus": "active",
  "adType": "all",
  "mediaType": "all",
  "datePreset": "any",
  "sortBy": "relevance",
  "maxAdsPerQuery": 10,
  "includeEuTransparency": true,
  "includeAdvertiserInfo": false,
  "includeAdvertiserContacts": false,
  "ocrImageAds": false,
  "onlyNewAds": false
}
```

# Actor output Schema

## `dataset` (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 = {
    "searchQueries": [
        "zalando"
    ],
    "country": "DE",
    "maxAdsPerQuery": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/meta-ad-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 = {
    "searchQueries": ["zalando"],
    "country": "DE",
    "maxAdsPerQuery": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/meta-ad-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 '{
  "searchQueries": [
    "zalando"
  ],
  "country": "DE",
  "maxAdsPerQuery": 10
}' |
apify call foxlabs/meta-ad-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/meta-ad-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/05kRCSjl5k16I49bi/builds/XbuvZXTNFRWu5cnyX/openapi.json
