Facebook Ads Library Scraper — EU Reach, Agency & Contacts
Pricing
from $0.50 / 1,000 ads
Facebook Ads Library Scraper — EU Reach, Agency & Contacts
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.
Pricing
from $0.50 / 1,000 ads
Rating
0.0
(0)
Developer
Berkan Kaplan
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 hours ago
Last modified
Categories
Share
Get the ads advertisers run on Facebook, Instagram, Messenger, Threads and Audience Network from the public Meta Ad 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)
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.isCatalogAdis 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 (runqjykPtwuaaxe3MzQ3). - 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 (run5z4yjpHVA45iesCWM). 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).
detailsStatussays 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 (runT8HBbX5sCTkAzwS9y), 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):
{"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,phonesandcontactPageUrl;legalOwnerName,adminCountriesandpageCreatedAt(with the "Advertiser info" option);beneficiaries,payersandthirdPartyPayers(EU);adsFound,earliestAdStartDate,latestAdStartDate,publisherPlatformsandlandingDomains;contactStatus:found,none-found,unreachableorno-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):
{ "advertisers": ["365604620536"], "country": "DE", "maxAdsPerQuery": 100, "includeAdvertiserInfo": true }
Dentists advertising in Germany, with their contacts (one advertiser row each):
{ "searchQueries": ["zahnarzt"], "country": "DE", "maxAdsPerQuery": 300, "includeAdvertiserContacts": true }
A daily monitor of a competitor: new ads only:
{ "advertisers": ["https://www.facebook.com/nike"], "country": "US", "onlyNewAds": true, "monitorName": "nike-us" }
US political ads about voting, with spend and audience:
{ "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
onlyNewAdsto 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 thenlandingDomainis 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 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_REPORTin 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-failedormax-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.
Built by foXLabs.