Facebook Ad Library Scraper avatar

Facebook Ad Library Scraper

Pricing

from $0.49 / 1,000 ads

Go to Apify Store
Facebook Ad Library Scraper

Facebook Ad Library Scraper

Scrape Facebook Ad Library ads by keyword, page URL or domain and find the competitor ads that keep running. Get days running, a 0-100 winner score, variant count, landing domain and UTMs, EU reach, political spend and new or stopped ad alerts.

Pricing

from $0.49 / 1,000 ads

Rating

0.0

(0)

Developer

Cliqto Media

Cliqto Media

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Facebook Ad Library Scraper collects ads from the public Meta Ad Library. Add keywords, website domains, advertiser pages or ad IDs, pick one or more countries, and get one row per ad: text, creative links, landing page with UTM tags, start date, days running, a 0–100 winner score, variant count and, for EU and political ads, reach and spend ranges.

No Facebook login, cookies or API token. The prefilled run (apify, US, up to 10 ads) takes about 10 seconds and costs about $0.007 on the Free plan.

Facebook Ad Library Scraper: search terms, pages and countries in; ads with days running, winner score, landing domain, EU reach and new or stopped status out

Table of contents

What it does

The Meta Ad Library lists ads that run on Facebook, Instagram, Messenger, Threads, WhatsApp and the Audience Network. This Actor reads the same public pages for you and saves each ad as a clean Dataset row.

For every ad you get:

  • Who: page name, page ID, page link, likes and categories.
  • What: ad text, headline, call-to-action, format (IMAGE, VIDEO, CAROUSEL, DCO, DPA…), image and video links, detected language.
  • Where it leads: landing link without the Facebook redirect, landingDomain, and utmSource, utmMedium, utmCampaign, utmContent, utmTerm.
  • How long and how strong: startDate, endDate, daysRunning, variantCount (how many versions Facebook groups together) and winnerScore (0–100).
  • Political and EU data: spend and impression ranges, "Paid for by" text, and with Add EU reach, demographics and payer details on: EU and UK reach, age, gender, locations, payer and beneficiary.
  • Change tracking: changeStatus new or seen and a list of ads that stopped since the last run.

Good fit

  • Find a competitor's long-running ads, the ones they keep paying for.
  • Collect ad copy, hooks and calls-to-action for a keyword or a niche.
  • Watch a list of advertisers every day and get only the new ads.
  • Check political and issue ads: who paid, spend range and impressions.
  • Compare 2–3 advertisers side by side with the advertiser summary.

👉 Try it now: open the input form

How it works

  1. The Actor opens the public Ad Library search for each search term or page in each country you chose.
  2. It reads the result pages until it has Max ads per search ads or the list ends. It removes repeats, works out days running, landing domain, UTM tags, language and winner score, and keeps only the media type you chose.
  3. If you asked for it, it opens each ad's detail panel (EU reach, payer), saves the first image, and compares the ads with the last run.
  4. It saves one row per ad in the Dataset and writes RUN_SUMMARY (status and reason for every search). With tracking on it also writes CHANGES, and with the summary on it writes ADVERTISER_SUMMARY.

👉 Try it now: open the input form

Quick start

Input form: box 1 is the search terms field, box 2 the countries, box 3 Max ads per search

  1. Open the Input tab. Search terms, domains or Ad Library links has apify in it (box 1). Replace it with your keyword, a domain such as nike.com, or leave it.

  2. To list every ad of one advertiser, add the page link (for example https://www.facebook.com/nike) in Advertiser pages or ad IDs instead.

  3. Keep Countries at US and Max ads per search at 10 (boxes 2 and 3), or choose your own. Use ALL for every country.

  4. Start the run with the button at the bottom of the page.

  5. Open the Output tab. The Ads view (box 1) shows page, days running, winner score, variant count (box 2), ad text and landing domain. Export (box 3) downloads CSV, JSON or Excel.

    Output table: box 1 the Ads, Winner analysis, EU reach & political and All fields views, box 2 days running, winner score and variant count, box 3 Export

Expected result: up to 10 ad rows for apify in the US. Facebook's "about N results" counts every ad version, so the row count can be smaller than that number.

👉 Try it now: open the input form

Use cases

Buyer jobInput pathOutput entityKey fieldsNext step
Find winning ads in a nichesearchTerms, sortBy = winnerScoreadwinnerScore, daysRunning, variantCount, bodyWinner analysis view, export to CSV
Spy on a competitorpageUrls, includeAdvertiserSummaryad + advertiser summarylandingDomain, utmCampaign, ctaText, topAdsCompare 2–3 pages in one run
Daily new-ad alertspageUrls, onlyNew, schedulenew adchangeStatus, firstSeenAt, adLibraryUrlSlack or Notion delivery
Political ads and EU reachadType = political_and_issue_ads, or includeAdDetails with an EU countryadpaidForBy, spendText, euTotalReach, payersEU reach & political view
Keep creativessaveMediaad + image filemediaKeys, mediaUrlsDownload from the key-value store

👉 Try it now: open the input form

Input

Add at least one entry in searchTerms or pageUrls. Each entry runs once in each country, so 3 terms × 2 countries = 6 searches. Repeated entries run once. An entry that is not an Ad Library link, page or ad ID is skipped and listed in RUN_SUMMARY.invalidSources; the other entries still run.

Input fields

FieldPlain nameTypeDefault / prefillAllowed valuesEffect
searchTermsSearch terms, domains or Ad Library linksarrayprefill ["apify"]up to 50; keyword, domain or Ad Library search linkOne keyword search per country. A pasted link keeps its own filters.
pageUrlsAdvertiser pages or ad IDsarrayemptyup to 50; page link, numeric page ID or facebook.com/ads/library/?id=…Lists every ad of that page, or finds one ad.
countriesCountriesarray["US"]up to 10 two-letter codes, or ALLEach country is a separate search.
activeStatusAd statusstringactiveactive, inactive, allWhich ads to return.
adTypeAd categorystringallall, political_and_issue_ads, housing_ads, employment_ads, credit_adsAd Library category.
mediaTypeMedia typestringallall, image, video, meme, noneKeep only this kind of creative.
maxAdsMax ads per searchinteger100, prefill 101–3000Stops each search (one entry in one country) at this many ads.
sortBySort resultsstringsourcesource, winnerScore, longestRunning, newestOrder inside each search, after maxAds ads are collected.
includeAdDetailsAdd EU reach, demographics and payer detailsbooleanfalseOpens each ad's detail panel; extra ad-details event.
saveMediaSave ad images to storagebooleanfalseSaves the first image or video preview; extra media-file event.
trackChangesTrack new and stopped adsbooleanfalseAdds changeStatus and the CHANGES record.
onlyNewOnly new adsbooleanfalseDelivers only ads not seen before; turns tracking on.
stateNameTracking memory namestringfacebook-ad-library-statelowercase letters, digits, hyphensSeparate names keep separate monitors.
includeAdvertiserSummaryAdvertiser comparison summarybooleanfalseWrites ADVERTISER_SUMMARY. Free.
outputFormatRow formatstringflatflat, nestedFlat: one column per field. Nested: grouped objects.
proxyConfigurationProxy for search pages, details and imagesobjectApify ProxySee Interacting settings.
deliveryConnectorSend results tostringemptyyour MCP connectorPosts a short summary after the run.
deliveryTargetNotion page or channelstringemptyNotion page link or Slack channel IDWhere the summary goes.

Interacting settings

WhenInput AInput BWhat happens
Several entries and countriessearchTerms / pageUrlscountriesEvery entry runs in every country. maxAds applies to each pair. The same ad found twice is delivered and charged once.
Pasted Ad Library linksearchTermsactiveStatus, adType, mediaTypeFilters inside the link win; countries still applies.
MonitoringonlyNewtrackChangesonlyNew turns tracking on. Already seen ads are skipped and cost the small ad-checked fee.
SortingsortBymaxAdsThe Actor first collects maxAds ads in Facebook's order, then sorts them. It does not search deeper for higher scores.
Table viewsoutputFormatOutput tab viewsThe Ads, Winner analysis and EU reach & political views need flat. With nested, use JSON export or the API.
ProxyproxyConfigurationYour setting is used for the first search page, details and images. Next result pages always use Apify residential IPs of the search country, because Facebook limits them from datacenter IPs.

Input examples

Competitor monitor (run daily on a schedule)

{
"pageUrls": ["https://www.facebook.com/nike", "https://www.facebook.com/adidas"],
"countries": ["US"],
"maxAds": 200,
"onlyNew": true,
"stateName": "sportswear-monitor",
"includeAdvertiserSummary": true
}

Expected behavior: the first run delivers up to 200 ads per page. Later runs deliver only ads that appeared since, and CHANGES lists the ads that stopped.

EU reach of a keyword in Germany

{
"searchTerms": ["hautpflege"],
"countries": ["DE"],
"maxAds": 50,
"includeAdDetails": true
}

Expected output: ads with euTotalReach, ageMin, ageMax, gender and payers, detailsStatus ok.

👉 Try it now: open the input form

Output

Output fields

The main fields of a flat row. The full list is in the Output tab schema.

FieldMeaningCan be empty?Example
adArchiveIdAd Library ID of the adno1550534860016399
adLibraryUrlPublic Ad Library linknohttps://www.facebook.com/ads/library/?id=…
pageName, pageIdAdvertiserrarelyPerfect themes
isActive, startDate, endDateRunning state and datesendDate is null while active2026-05-19
daysRunningDays the ad has run, start day includedrarely166
variantCountAd versions Facebook groups togetherno1
winnerScore0–100 performance signal, see belowno75
platformsWhere it runscan be []["facebook","instagram"]
body, title, ctaTextAd text, headline, buttonyesInstall now
linkUrl, landingDomainLanding page and its domainyes, for ads without a linkplay.google.com
utmSource … utmTermUTM tags of the landing linkyesfacebook
spendText, impressionsTextRanges for political and some EU adsyes€7K-€8K
paidForBy"Paid for by" linepolitical ads only
languageDetected language, ISO 639-3null for very short texteng
changeStatus, firstSeenAtnew or seennull when tracking is offnew
detailsStatusok, empty, failed, skipped, not_requestednook
euTotalReach, ukTotalReachReach from the EU or UK panelyes53398
searchSource, searchCountryYour input and the country that found the adnoapify, US

Winner score. winnerScore = 50 × longevity + 20 × variants + 15 × active + 10 × platforms + 5 × reach, rounded. Longevity is ln(1 + daysRunning) / ln(91), capped at 1 (full at 90 days). Variants is (variantCount − 1) / 9, capped at 1. Active is 1 while the ad runs. Platforms is platforms / 4, capped at 1. Reach is 1 when the lower impressions bound is at least 100,000. It is a signal, not Facebook data: advertisers tend to keep ads that work.

Output entities and surfaces

SituationWhereWhat you see
Normal adDatasetOne row per unique ad.
Search with no adsRUN_SUMMARYNo row; the search has status not_found and a reason. Run status NO_RESULTS.
Search stopped earlyDataset + RUN_SUMMARYRows read so far are kept; the search has status partial and a reason.
Detail panel failedDatasetThe ad row is kept with detailsStatus failed; no ad-details charge.
New and stopped adsCHANGESNew ad IDs and stopped ad IDs for each tracked search.
Advertiser comparisonADVERTISER_SUMMARYPer page: adsDelivered, activeAds, avgDaysRunning, avgWinnerScore, topAds, formats, platforms, CTAs, landing domains.
Saved imagesKey-value storemedia-<adId>.<ext>; keys in mediaKeys.

RUN_SUMMARY.status is one of COMPLETE, PARTIAL, NO_RESULTS, CAPPED (stopped at your maximum charge), ERROR, INVALID_INPUT or CANCELLED. Each entry of searches has its own status and reason.

Output example

A trimmed flat row from a real run:

{
"adArchiveId": "1550534860016399",
"pageName": "Perfect themes",
"isActive": true,
"startDate": "2026-05-19",
"daysRunning": 166,
"platforms": ["facebook", "instagram", "audience_network", "messenger", "threads"],
"variantCount": 1,
"winnerScore": 75,
"ctaText": "Install now",
"landingDomain": "play.google.com",
"language": "eng",
"detailsStatus": "ok",
"euTotalReach": 53398,
"endDate": null
}

Facebook does not show every field for every ad. null means Facebook did not show the value for this ad. targetedCountries is usually empty because the public guest view does not show it.

Send results to Slack or Notion

Pick your connector in the optional deliveryConnector field. After the run saves its data, the Actor posts a short summary (up to 25 rows and a link to the full dataset) there. Put a Notion page link or a Slack channel ID in deliveryTarget: in Notion every run is added on top of that one page, so your workspace does not fill up with new pages. A delivery error never fails the run and costs nothing extra. Connect a service once in Apify Console → MCP connectors.

👉 Try it now: open the input form

Pricing

Pricing unit

You pay per event. Apify platform usage (compute, proxy, storage) is included; you do not pay it separately.

EventWhen it is chargedFree planPaid plans
Actor startOnce per run, one event per GB of run memory (minimum one; the default 512 MB is one)$0.001$0.001
SearchOne search term or page in one country that got an answer from Facebook, even with no ads$0.0003$0.0003
AdOne unique ad saved to the Dataset$0.0007from $0.00049
Ad detailsOne ad that got EU reach, payer or demographic data (only with details on)$0.0003$0.0003
Saved ad imageOne image saved (only with Save ad images on)$0.001$0.001
Already seen ad checkedOne already seen ad skipped (only with Only new ads on)$0.00025$0.00025

Ad price by plan: Free $0.0007, Bronze $0.00063, Silver $0.00056, Gold and higher $0.00049.

  • A search with no ads costs the start fee and the search fee, nothing more.
  • An entry that is skipped as invalid, and a search that fails before Facebook answers, cost nothing.
  • A repeated ad (same ad found by two searches) is charged once.
  • A detail panel that fails or comes back empty is not charged.
  • The run stops cleanly at your Maximum cost per run; status CAPPED, rows already saved are kept.

Cost examples

Free plan prices.

ScenarioInputEventsCost
Prefillapify, US, 10start + 1 search + 8 ads$0.0069
Keyword with no ads1 term, USstart + 1 search$0.0013
One competitor1 page, US, 100start + 1 search + 100 ads$0.0713
Niche research with details1 term, DE, 1000, details onstart + 1 search + 1000 ads + 1000 details$1.0013
Daily monitor, nothing new1 page, onlyNew, 200 ads seen beforestart + 1 search + 200 checked$0.0513

👉 Try it now: open the input form

Limits

  • Up to 3,000 ads per search (one entry in one country), 50 search terms, 50 pages and 10 countries per run, and at most 100 searches per run (entries × countries: 50 terms in 2 countries is the maximum). A bigger input fails at the start with INVALID_INPUT and only the start event is charged. For more, split the job across runs.
  • Each run makes at most 3,000 requests to Facebook. A search page holds about 10 ads, and Add ad details makes one more request per ad. Tested maximum: 3,000 ads without details in about 11 minutes, or 1,000 ads with details in about 10 minutes. With details on, keep the total of all searches under about 2,500 ads per run.
  • A run works at most 1 hour (or your run timeout, if shorter), then saves what it has as PARTIAL.
  • Saved images are limited to 8 MB each; a bigger image is skipped and not charged.
  • The run status is FAILED for invalid input, and for ERROR (no ads because every search failed) or PARTIAL after a source failure. Rows already saved stay in the Dataset and RUN_SUMMARY names the reason.
  • The Actor shows only what the public Ad Library shows. Commercial ads outside the EU and UK have no spend, reach or targeting data. Facebook keeps stopped commercial ads for a limited time and political ads for 7 years.
  • targetedCountries is usually empty: the public guest view does not show it.
  • Image and video links from Facebook expire after some days. Turn on Save ad images to storage to keep them.
  • A page name that Facebook cannot match returns not_found. Paste the page ID or an Ad Library link instead.
  • The Actor does not log in and does not solve CAPTCHAs. If Facebook blocks a search after all retries, the search is marked partial or error with the reason.
  • You are responsible for using the data in line with Meta's terms and the laws that apply to you.

👉 Try it now: open the input form

API, export and integrations

  • API: start a run with the Apify API and read the Dataset items. The RUN_SUMMARY record is in the default key-value store.
  • Export: CSV, Excel, JSON, XML or HTML from the Output tab or the Dataset API. Use flat for spreadsheets.
  • Scheduling: add a schedule with onlyNew on and a fixed stateName to get only new ads every day.
  • Webhooks and no-code: use webhooks or the Apify integrations for Zapier, Make and Google Sheets to send rows on.
  • Slack or Notion: see Send results to Slack or Notion.

Use with AI agents (MCP)

  • MCP server with only this Actor: https://mcp.apify.com?tools=cliqtomedia/facebook-ad-library. See Apify MCP.

  • Smallest useful input:

    { "searchTerms": ["apify"], "countries": ["US"], "maxAds": 10 }
  • Key output fields: adLibraryUrl (link to the ad), pageName (advertiser), daysRunning (days live), winnerScore (0–100 signal), landingDomain (where the ad leads), changeStatus (new or seen).

  • A small run takes about 10 seconds and costs about $0.007 (start, one search, 8 ads); 1,000 ads take about 5–10 minutes. Keep maxAds and the number of countries small for quick answers.

👉 Try it now: open the input form

Troubleshooting

Fewer rows than Facebook's "about N results"

Symptom: Facebook shows "about 12 results", the run returns 8 rows. Likely reason: Facebook counts every ad version; the Actor returns one row per ad and puts the versions in variantCount. Check: RUN_SUMMARY.searches[].status is complete. Action: nothing to fix. Set activeStatus to all to add stopped ads.

A page returns no ads

Symptom: the search status is not_found. Likely reason: the page has no ads in that country, or Facebook cannot match the page name. Check: open the page's Ad Library link in your browser for the same country. Action: paste the numeric page ID or the Ad Library link, or use ALL in countries.

The run stopped with CAPPED

Symptom: RUN_SUMMARY.status is CAPPED. Likely reason: the next ad would go over your Maximum cost per run. Action: raise the maximum cost or lower maxAds. Rows already saved are kept.

The run stopped with PARTIAL and TIME_LIMIT

Symptom: RUN_SUMMARY.reason is TIME_LIMIT. Likely reason: the run reached 1 hour, your run timeout, or the cap of 3,000 Facebook requests per run (the message says which). Action: lower maxAds or the number of countries, turn off Add ad details, or split the job across runs. Rows already saved are kept.

The run failed with INVALID_INPUT

Likely reason: more than 100 searches (entries × countries), more than 50 terms or pages, or an unknown country code. Action: fix the input as the message says. Only the start event is charged.

Likely reason: Facebook image links expire. Action: run again with Save ad images to storage on and use mediaUrls.

👉 Try it now: open the input form

FAQ

Do I need a Facebook account or an access token?

No. The Actor reads the public Ad Library without login.

Can I get ad spend for normal commercial ads?

Only where Facebook shows it: political and issue ads everywhere, and some data for ads in the EU and UK. For other commercial ads Facebook does not publish spend.

How fresh is the data?

Each run reads the Ad Library at that moment. fetchedAt shows when each ad was read. daysRunning counts up to today for active ads.

Why is language empty for some ads?

The language is detected from the ad text. Very short texts give null.

👉 Try it now: open the input form

👉 Try it now: open the input form

Support

For a bug, question or feature request, open the Issues tab of this Actor on Apify. Add the run ID, the input mode (keyword, page or link) and the error text from RUN_SUMMARY. Never send tokens, cookies or billing data.

👉 Try it now: open the input form