Facebook Ads Library Scraper - Ad Library & Spy avatar

Facebook Ads Library Scraper - Ad Library & Spy

Pricing

from $0.50 / 1,000 ad delivereds

Go to Apify Store
Facebook Ads Library Scraper - Ad Library & Spy

Facebook Ads Library Scraper - Ad Library & Spy

Facebook Ad Library scraper for competitor ads: pull every ad a Facebook Page runs, or search the Facebook Ads Library by keyword, ad ID or URL, no login. Political spend and EU reach as columns, optional page details (creation date, name changes, admin countries). JSON, CSV, Excel, API. Pay per ad.

Pricing

from $0.50 / 1,000 ad delivereds

Rating

5.0

(4)

Developer

Automation Craft

Automation Craft

Maintained by Community

Actor stats

3

Bookmarked

67

Total users

32

Monthly active users

2 days ago

Last modified

Features

Limited permissions

Share

Facebook Ads Library Scraper - Ad Library & Spy

Facebook Ads Library Scraper: search the Meta Ad Library (Facebook, Instagram, Messenger, Audience Network, WhatsApp and Threads ads) by keyword, advertiser page, ad ID or a pasted Ad Library URL, apply every filter the Ad Library website has, and export the ads as clean JSON, CSV or Excel. No login, no cookies, no API keys, no code, and you pay per ad you receive, plus Apify's start fee of $0.00005 per run and any add-on you turn on.

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

  • Every Ad Library filter, applied on Meta's side. Country (all 250 plus "All countries"), ad category (all, political, housing, employment, financial), active status, media type, platform, ad language, impressions date window, sort order, "targeted at country", and the political-only filters: paid-for-by, delivery region and estimated audience size. Filters that Meta applies are filters you do not pay for.
  • A cap that is honoured, billing that matches it. maxAds and maxAdsPerQuery are hard caps. You are charged exactly once per delivered ad; duplicates, filtered-out ads, total counts and summaries are free, and a run that delivers no ad still pays the run's start fee ($0.00005 at the default memory).
  • A total count before you pay, with no ad fee. Tick "Only count matching ads" and you get the Ad Library's own total per query without paying for a single ad (the run's start fee, $0.00005 at the default memory, still applies, and from 27 November 2026 the run minimum of $0.005), so you can size a job first.
  • A real-time API, no run to start (from 27 November 2026). Ask for one ad, the first ads of a keyword or page, or a total count over HTTP and get the answer in a few seconds. See "Real-time API" below.
  • Honest coverage. Every query ends with totalCount, delivered, coverage (complete, partial, empty) and stopReason (cap, end_of_results, thin_pages, blocked, work_allowance). You are told when the data is incomplete instead of guessing.
  • EU and political transparency, parsed into columns. Political ads carry spend, impressions and audience-size ranges as numbers; "Fetch ad details" adds EU (DSA) reach by country, age and gender, political age / gender and region distributions, payer / beneficiary data and the advertiser profile (page likes, Instagram followers, lifetime spend by disclaimer).
  • Free cross-run memory. Name a memory and later runs return only ads you have not received yet. Schedule it and you have a change feed of a competitor's new ads.
  • Derived "winning ad" filters, free. Minimum days running, minimum creative reuse (collation count), creative format, text must / must not contain, exclude pages.
  • Who is behind a page, optional. "Page details" adds, per advertiser page, when it was created, each name change with its date, the countries its admins are in, the confirmed owner where Meta shows one, and how many ads it runs right now. A page created last month, renamed four times and run from abroad stands out at a glance.
  • The exact page a name matched, optional. "Page match" saves which page each advertiser name was matched to, how, and the other pages it could have been, so you can pin the right page ID and stop paying for ads from a lookalike page.

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

Quick start (30 seconds)

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

Active status matters for cost. The default, activeStatus: "all", returns ads that are no longer running as well as live ones, as the Ad Library does, and every delivered ad is charged. For live ads only, pick "Active ads only" (activeStatus: "active"); on a big brand that is often a small part of the total. Tick "Only count matching ads" (no ad fee) to see both numbers first.

Prices in one line: $0.50 per 1,000 ads delivered, plus $0.25 per 1,000 for ad details and $0.20 per 1,000 for resolved landing pages, so $0.75 per 1,000 for an ad with details. Duplicates, filtered-out ads and total counts are free. Full breakdown below.

What you get

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

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

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

FieldCommercial ads (US, keyword)Political ads (US)
adText98%100%
title79%82%
linkUrl93%78%
landingDomain92%78%
ctaType93%77%
imageUrls40%10%
videoUrls25%55%
pageLikeCount98%97%
spendText2%100%
impressionsText2%100%
audienceSizeText2%98%
byline2%98%
collationCount80%93%
endDate100%100%

Output

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

With "Page match" on, the run's key-value store also holds a RESOLVED_PAGES record: for each advertiser name, the page ID and name it matched, how it matched (page_alias, page_name, instagram_username or first_suggestion) and the other pages the Ad Library suggested, with their likes, verification and Instagram handle.

Sample item from a live run:

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

How much does it cost to scrape Facebook ads?

You pay per event, never per hour, and every price below is the current base price on this listing.

Charge eventWhat triggers itPrice
Ad delivered (ad-result)One ad record delivered to the dataset$0.50 per 1,000
Ad details fetched (ad-details)Advertiser profile, EU (DSA) reach breakdown, political age / gender / region insights and payer data for one ad, when "Fetch ad details" is on$0.25 per 1,000
Landing page resolved (landing-page-resolved)Final landing URL and domain after redirects, when "Resolve landing pages" is on, charged only when a page answered$0.20 per 1,000
Actor Start (apify-actor-start)Apify's standard start event, once per run per gigabyte of memory. This Actor defaults to 256 MB, so it is one event per run$0.05 per 1,000 runs ($0.00005 per run)

Two optional events, off unless you turn them on, are charged from 23 October 2026 (before that date both options work and are free):

Optional eventWhat triggers itFree / BronzeSilverGold
Page details (optional) (page-details)Once per advertiser page per run whose details came back, when "Page details" is on$2.50 per 1,000 pages$2.25$2.00
Page match (optional) (page-match)Once per advertiser name that matched a page, when "Page match" is on; names without a match are free$0.60 per 1,000 names$0.55$0.50

From 27 November 2026 every run is billed at least $0.005, the price of 10 ads (the start fee apart):

EventWhat triggers itPrice
Minimum charge (minimum-charge)When the ads and other results a run (or a real-time API request) charged come to less than $0.005, the difference, rounded up to whole events; and once per minute for a flood of rejected API requests (see "Real-time API")$0.50 per 1,000 ($0.0005 each)

A run with 10 ads or more pays nothing extra. A run with 3 ads pays 3 ads and 7 minimum charge events ($0.005 in all, plus the start fee). A run that finds nothing, an empty search or a count-only run pays 10 ($0.005). The minimum is charged once per run, at its end, and the run summary says so (minimumCharge). It never goes past your maximum charge per run: a run whose limit leaves room for fewer events is charged only those. A run the platform restarts (a migration or a reboot) after it has begun its work is not charged the minimum at all. Runs that fail, or are aborted or time out before their end, and runs that stop because Apify's storage could not open or save their memory, are not charged it (it is the run's very last step, after every result and the summary were written; a run aborted in the moment its minimum is being charged is charged). It exists because a run that delivers almost nothing still costs a container start and the first page loads, more than its ads pay.

Platinum and Diamond plans pay the Gold price. The other events above have one price on every plan.

An ad with details costs $0.75 per 1,000 ads.

Always free: duplicates inside a run, ads the memory already knows across runs, ads dropped by the post-filters, the total counts from "Only count matching ads", status rows and the run summary. A run that finds nothing still pays the run's start fee and, from 27 November 2026, the $0.005 minimum.

Worked examples (start event included): 500 ads for three keywords = $0.25; 2,000 competitor ads with details = $1.50; a daily re-run under a memory name that finds 8 new ads among 300 known ones = $0.00405; a run that finds nothing, with no add-on turned on = $0.00005 (from 27 November 2026: $0.00505, and the daily re-run with 8 new ads $0.00505).

Input reference

InputWhat it does
Keywords, Keyword matchingThe Ad Library search box. "Exact phrase" is the quoted search.
Advertiser pages, Advertiser namesPage IDs, page URLs, Instagram profiles or plain names (resolved through the Ad Library's own advertiser lookup; the run summary lists the candidates). Returns the page's "See all ads" view.
Ad Library URLsPaste a URL from your browser; keyword, page, country, ad type, status, media type, platforms, languages, dates, sort and political filters are all read from it. ?id= URLs fetch that single ad.
Ad IDsLibrary IDs to fetch directly (current status, dates, creative, political numbers). Unknown IDs are reported and free.
CountriesAny of the 250 countries or All countries. Several countries = one query each (fair share of the cap). EU expands to the 27 member states (API).
Ad categoryAll, political, housing, employment, financial (the regulated categories). Political ads are the ones with spend and impressions.
Active status, Media type, Platforms, Ad languagesExactly the website's filters.
Impressions within / from / toThe website's "Impressions by date": ads that were shown in the window (an ad that started earlier and is still running matches).
SortDefault relevance, impressions high to low / low to high, most recent (grouped by launch month), as the website offers.
Only ads targeted at the countryThe is_targeted_country switch.
Paid for by, Delivery regions, Audience sizePolitical-only filters. Region names as the website shows them ("California"). Audience buckets: 100-1K, 1K-5K, 5K-10K, 10K-50K, 50K-100K, 100K-500K, 500K-1M, above 1M.
Minimum days running, Minimum creative reuse, Creative formats, text filters, Exclude pagesFree post-filters. A run where almost no ad passes them stops early (see "What this Actor does NOT do").
Max ads, Max ads per queryHard caps on delivered, charged ads. maxItems: 0 through the API means no cap.
Only count matching adsPreflight: one count row per query, no ads and no ad fee (the run's start fee still applies, and from 27 November 2026 the $0.005 run minimum). About 20 queries per run at the default memory and proxy; queries past that end with a "Counts stopped early" warning, so split longer lists.
Fetch ad details, Resolve landing pagesPaid add-ons, charged only when data came back.
Page details (optional), fetchPageDetailsOff by default. Adds pageDetails to each ad: pageCreatedAt, nameChangeCount, nameChangedAt, history, adminCountries (country and number of admins), confirmedOwner (name, city, state, postal code, country, phone where Meta shows them), stateMediaLabel, about and activeAdsCount (ads the page runs now in all countries; 50001 means more than 50,000). Read once per page per run.
Page match (optional), includePageMatchesOff by default. For advertiser names and page handles: the RESOLVED_PAGES run record and matchedAdvertiserName on each ad. The name lookup itself is unchanged and stays free with or without it.
Memory name, Also return already-known ads, Reset memoryCross-run dedup (named key-value store ads-library-memory-v2-<name> in your account). Known ads are never charged. If Apify's storage cannot open, read or save your memory, the run stops with a status row that says so instead of risking a second charge for an ad you already have; to get the ads at once, run it again with "Memory name (only new ads on later runs)" left empty (every matching ad, including ones you may already have, each delivered ad charged). Give runs that can overlap (a schedule and a manual run, say) their own memory names: two runs with the same memory name at the same moment can both deliver an ad that is new to both, and if they save at the same moment the memory can miss one run's ads, so a later run can deliver them again.
Stop after N fully-known pages (maxKnownPages)Used only with a memory name; left empty it is 3. For scheduled change feeds: a query stops after 3 consecutive result pages that bring nothing new: each holds at least one ad the memory already knows, and its other ads are ones your filters dropped or ones this run already returned (stopReason known_pages, coverage partial), instead of paging to the cap. A page whose ads your filters all dropped neither counts nor breaks the streak. A new ad the Ad Library orders behind those pages is not delivered by that run; the "Most recent" sort puts newer ads nearer the top, which makes that less likely. 0 also means 3 on a memory run (0 was the default until 1.3.6, so many saved tasks carry it). To page deeper past known ads, set a higher number, up to 1000 (a run that reads many result pages without a new ad still stops early, stopReason: work_allowance).
Proxy, Parallel queries, IP retries, Stop after N empty pagesAdvanced. Leave the defaults.

Using the API

Same input as the form.

curl -X POST "https://api.apify.com/v2/acts/automation_craft~facebook-ads-library-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"searchTerms": ["nike"],
"countries": ["US"],
"activeStatus": "active",
"mediaType": "video",
"publisherPlatforms": ["instagram"],
"deliveredWithinDays": "30",
"maxAds": 100
}'
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation_craft/facebook-ads-library-scraper').call({
advertiserNames: ['Nike'], countries: ['ALL'], fetchAdDetails: true, maxAds: 200, memoryName: 'nike-watch',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.type === 'ad').length, 'ads');
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation_craft/facebook-ads-library-scraper").call(run_input={
"searchTerms": ["vote"], "countries": ["US"], "adType": "political_and_issue_ads", "bylines": ["VOTEVETS"], "fetchAdDetails": True, "maxAds": 50,
})
ads = [i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if i["type"] == "ad"]

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

Real-time API (no run to start)

From 27 November 2026 this Actor also answers HTTP requests directly (Apify Server Actors): no run to start, no dataset to read, the answer comes back in the response, usually within 5 to 10 seconds (measured on Apify in November 2026: half of the requests within 6 seconds, a few up to about 20). It suits apps, agents and automations that look up one thing at a time. Send your Apify token as a Bearer header to https://automation-craft--facebook-ads-library-scraper.apify.actor:

$curl -H "Authorization: Bearer $APIFY_TOKEN" "https://automation-craft--facebook-ads-library-scraper.apify.actor/ads?keyword=nike&country=US&limit=10"
EndpointAnswers
GET /ad?id=<ad id or Ad Library link>[&country=ALL]One ad, the same record as a dataset row; 404 when the Ad Library has no such ad
GET /ads?keyword=<text> or ?pageId=<page id>, [&country=ALL][&limit=10][&active=all]The first ads of the search (limit 1 to 30) with the total count
GET /count?keyword=<text> or ?pageId=<page id>, [&country=ALL][&active=all]The total number of ads the search has
GET /The endpoints, limits and prices

What it costs: every answered request is billed at least $0.005 (the same minimum as a run): its ads at $0.50 per 1,000 (ad-result), and minimum charge events (minimum-charge) for the rest. So one ad, a count, an empty search or "no such ad" costs $0.005; 10 to 30 ads cost $0.0005 each and nothing more. There is no start fee. A request refused before any work (bad input, an option the endpoint does not have, a limit) or one that fails (a 5xx answer) is free, with one exception for floods: if one account sends more than 60 rejected requests (HTTP 400 invalid input or 429 over a limit) in one minute to one API server and had not already paid at least $0.005 there in that minute when the 61st was rejected, that minute is billed one minimum ($0.005, 10 minimum-charge events), once per API server. An API server is one server run: the platform spreads one account's requests over several of them, so a heavy flood can be billed once on each server it reaches in that minute. The answer that carries it says so (floodCharge in the body, X-Flood-Charge and X-Charged-Events headers). Other failures (5xx, timeouts, a lost connection) never count. "No such ad" (404) is billed only when the Ad Library itself says the ad does not exist; a page we could not read is a free 502. The X-Charged-Events response header says what the request was charged, by event key. Three exceptions, rare: a 503 charge-unconfirmed means the charge could not be confirmed (if the platform recorded it, it was recorded once; send us the X-Actor-Request-ID header through the Issues tab); if the platform refuses the minimum charge events after the ads were charged, you get the ads and the request costs less than $0.005; a caller that disconnects before the answer is not charged, but one that disconnects while the charge is being made can be charged without receiving it; and a failure on our side after the charge was accepted (500 internal-error-after-charge) stays charged, so send us its X-Actor-Request-ID too.

Limits: up to 30 ads per request; per account and server run, 60 requests a minute and 3 at once, counted until each answer is out (429 with Retry-After above that); an answer within 50 seconds or a free 504 (a request still waiting on the Ad Library after 20 seconds is stopped and free). An account whose last 3 requests did the work and ended unpaid (the charge refused, for example at a spending limit, or the caller leaving before the answer) is paused for 10 minutes on that server run. Options of the form (filters, memory, ad details, landing pages, page details, several countries or keywords at once, more than 30 ads) are refused with 400, never ignored: start a run for them.

Recipes

  • Competitor monitoring: advertiser pages + memoryName, scheduled daily. Only new ads arrive; changeType: UPDATED (with "Also return already-known ads") flags ads whose status, dates or creative changed.
  • Winning ads: keyword + activeStatus: active + minDaysRunning: 30 + minCollationCount: 3 + sortBy: total_impressions_desc.
  • Political spend research: adType: political_and_issue_ads + bylines or regions + fetchAdDetails: true for age / gender / region distributions and lifetime spend by disclaimer.
  • EU DSA audits: an EU country + fetchAdDetails: true for reach by country, age and gender and the declared targeting.
  • Sizing a job: the same input with onlyTotalCount: true first (no ad fee).
  • Vetting advertisers: a keyword such as a product or offer + activeStatus: active + fetchPageDetails: true. Sort the rows by pageDetails.pageCreatedAt and pageDetails.nameChangeCount: new pages with renamed histories and admins in unexpected countries are the ones to check first.
  • Pinning the right page: advertiser names + includePageMatches: true once, then copy the page IDs from RESOLVED_PAGES into "Advertiser pages" for every later run.

What this Actor does NOT do

  • It does not log in and never asks for cookies or tokens, so it only sees what the public Ad Library shows. Spend, impressions and audience size exist for political ads only; EU reach data only for ads shown in the EU. Meta's ranges (">$1M", "100K-500K") are ranges, not exact numbers.
  • Very broad keywords: the Ad Library's own result pages thin out after a few hundred ads (stopReason: thin_pages, coverage: partial). Split the job by country, platform, date window or advertiser page for complete coverage.
  • It does not search on without end for ads it cannot deliver. A run whose searching costs far more than the ads it delivers (most ads dropped by your filters, already in your memory, repeated, or ad IDs not found) stops early, with one status row at the end (stopReason: work_allowance, coverage: partial) that says what it read. The ads it delivered are charged as usual; nothing is charged for stopping. For more ads, narrow the search (a more specific keyword, one country, an advertiser page) or relax the post-filters. A search that delivers ads at a normal rate stays well inside this allowance (on our tests a plain keyword search used about half of it; with ad details, landing pages, page details or page matches on, the larger 1.3.10 allowance applies, and a search with ad details used about half of that). Searching that delivers nothing new is kept small: such a run stops after about USD 0.001 of searching (a few result pages on the default proxy), so put many keywords, pages or names whose ads you mostly have already into several smaller runs, and for a memory feed sorted by most recent consider maxKnownPages: 1. Count-only runs count about 20 queries per run at the default memory and proxy, and advertiser names are looked up up to about 50 per run (the names looked up are searched); split longer lists. With "Page details" on and a residential proxyConfiguration, every advertiser page is read through a residential IP and costs about as much as it is charged, so such runs stop early; leave proxyConfiguration empty for page details.
  • It does not download media files; it delivers the CDN URLs (which expire after some days, so download what you need).
  • It does not read the aggregate "Ad Library report" spend tables (use Meta Ad Spend Scraper), branded-content listings (use Meta Branded Content Scraper) or the collation "summary" panel.
  • Meta rate-limits roughly half of all residential IPs. The Actor rotates IPs automatically and the run summary shows proxy.blockedIps; a query that cannot get a clean IP is reported as coverage: blocked and costs nothing.

FAQ

Do I need a Meta Ad Library API token or ID verification?

No. This Actor does not use Meta's API and never asks you for a token, a login or cookies. It reads the public Ad Library and bills per ad delivered.

Can I scrape the Facebook Ad Library without logging in?

Yes, that is the only way it works here: no login, no cookies, no keys. The Actor sends its Ad Library requests through Apify residential proxies because Meta rate-limits datacenter IPs, and it rotates to a fresh IP by itself when one is blocked.

Which Facebook ads show spend, impressions and audience size?

Only ads in the "Issues, elections or politics" category. Set "Ad category" to political and those ads arrive with spendLower / spendUpper, impressionsLower / impressionsUpper and audienceSizeLower / audienceSizeUpper as numbers next to Meta's original range text. For commercial ads Meta publishes nothing, so the fields are null and transparencyScope says why.

How do I export every ad a Facebook Page is running to CSV?

Paste the page URL, the page ID or an Ad Library "view all page" URL into "Advertiser pages", leave "Keywords" empty, set "Max ads" and start. That is the page's "See all ads" view. Download it as CSV or Excel from the Dataset tab.

What is in the EU (DSA) transparency data for an ad?

Turn on "Fetch ad details" and ads shown in the EU come back with details.eu: whether the ad targets the EU, total reach, reach by country, a breakdown by country, age and gender, and the age, gender and location targeting the advertiser declared.

How fast is it?

Measured on Apify at the default 256 MB on 8 October 2026: the 20 ad example input finishes in 5 to 23 seconds (usually under 10), because the first 30 ads of a search come with the page itself. 100 ads from one keyword took 43 seconds and 300 ads 197 seconds; 300 ads spread over three keywords took 102 seconds, because queries run in parallel ("Parallel queries", default 3). Deep pages of one broad keyword are the slow part, so split big jobs by keyword, country or page. "Page details" added 44 to 60 seconds for 11 to 22 pages (read four at a time).

Does the Actor stop at my limit?

Yes. "Max ads" (maxAds) is a hard cap on delivered and charged ads for the whole run, and "Max ads per query" (maxAdsPerQuery) caps each keyword x country or page x country. Several queries share the total: each gets an equal share first, then unused budget moves to queries that still have results. You never receive or pay for more ads than the cap, and the run summary shows delivered and stopReason: cap per query.

How much will it cost to scrape 1,000 Facebook ads?

$0.50 for 1,000 ads as delivered, or $0.75 if you also switch "Fetch ad details" on, plus Apify's start event of $0.00005 for the run itself (from 27 November 2026 a run is billed at least $0.005; 1,000 ads are far above it). Ads your memory already knows, ads your post-filters drop and the free total count are not charged, so a scheduled watch is never charged again for ads it already delivered (each run still pays the start fee).

Why does this Actor run with limited permissions?

Least privilege. It only reads and writes its own storages: the run's dataset, and, when you set a memory name, a key-value store it creates itself (ads-library-memory-v2-<your memory name>). It never needs access to the rest of your Apify account, so it does not ask for it, and schedules and API calls do not raise an approval prompt.

Changelog

  • 1.3.13: from 27 November 2026 every run is billed at least $0.005 for its results (not a run the platform restarts after it has begun its work) (the minimum-charge event, $0.0005 each, for the difference; 10 ads or more pay nothing extra), and the real-time API (/ad, /ads, /count) answers without a run (rejected API requests are free, except one minimum for a minute with more than 60 of them from one account on one API server). Until that date nothing changes. Same inputs, item keys and prices per ad; a run charged the minimum gains a minimumCharge block in its summary and a sentence in its final message.

  • 1.3.12: a run with several keywords or pages reads each one's first result page before paging deeper (with the default proxy), so later keywords get read before the allowance is spent on deep pages of the first ones; a run that searches a lot for few new ads now gets the 1.3.10 allowance for its first 10 ads (USD 0.005 of charges) and nothing more for its next ones until it has charged about USD 0.02 to 0.03 (35 to 60 ads); from there it may spend about a fifth of what it charged (before: two fifths on top of the base), while ad details, landing pages, page details and page matches keep the 1.3.10 allowance; a first page that wrongly claims no result is loaded once more. Same item keys, events and prices.

  • 1.3.11: the allowance above measures each run's cost more exactly (the result pages as they arrive over the network, compressed), so searches with strict post filters on "Most recent" are no longer stopped at about half of their allowance; runs that deliver nothing new stop as before. Same item keys, events and prices.

  • 1.3.10: the allowance for searching that delivers nothing new is smaller: about USD 0.001 per run, no longer growing with the number of keywords, pages, names or ad IDs (it was up to USD 0.023). Runs that deliver ads at a normal rate are unchanged; count-only runs count about 20 queries per run at the default memory and proxy, runs over many queries whose ads you already have (or your filters drop) stop sooner, and so can a run with paid extras (ad details, landing pages, page details) and only a few ads per query whose result pages answer very slowly, all with the same status row. Same item keys, events and prices.

  • 1.3.9: a run whose searching costs far more than the ads it delivers (your filters drop them, your memory already has them, they repeat, or ad IDs are not found) now stops early with a status row at the end (stopReason: work_allowance) instead of searching to the end. A search that delivers ads at a normal rate is not stopped. Same item keys, events and prices.

  • 1.3.8: on runs with a memory name, maxKnownPages 0 now means 3, like an empty field (0 was the default until 1.3.6 and stayed in saved tasks and copied inputs); to page deeper past known ads set a higher number, up to 1000. A page of known ads plus ads your filters dropped now counts toward the 3 pages (before, one dropped ad restarted the count, so a filtered feed kept paging). Runs without a memory name are unchanged. Same rows, events and prices.

  • 1.3.7: a run with a memory name that Apify's storage cannot open, read or save stops with a status row instead of going on without the memory (it could have charged an ad you already have); a save keeps the ads another run with the same memory name saved before it; a restarted run stops the same way when its memory missed ads it charged before the restart; memory names longer than about 41 characters now work. Runs without a memory name are unchanged.

  • 1.3.6: runs with a memory name stop a query after 3 result pages that hold only ads the memory already knows (maxKnownPages left empty now means 3 with a memory name; since 1.3.8 a higher number pages deeper, 0 also means 3). Runs without a memory name are unchanged. Same rows, events and prices.

  • 1.3.5: run messages, the input form and this README say that a run with no ads (an empty run, a count-only run, an input with nothing to search) still pays the run's start fee ($0.00005 at the default memory) instead of calling it free. Nothing else changes: same inputs, rows, events and prices.

  • 1.3.4: optional settings other than dropdown selects accept an empty (null) value from Zapier, Make or the API and run as if left out.

  • 1.3.3: ad ID and ad link lookups work again after Meta changed how it shows a linked ad (14 October 2026).

  • 1.3.0: two optional features, off by default: "Page details" (page creation date, name changes, admin countries, confirmed owner, active ads count) and "Page match" (the RESOLVED_PAGES record and the matched name on each ad). Both are charged as listed above from 23 October 2026 (free before that date). Nothing changes for a run that does not turn them on: same inputs, same output, same prices.

  • 1.1.0: cheaper runs, same results; Apify's standard start event ($0.05 per 1,000 runs) added, every other price unchanged. The first page a run loads now provides the session tokens (no second page load per run); the memory is written only when it changed; runs default to 256 MB (enough for this HTTP-only engine; up to 1024 MB can still be requested); optional maxKnownPages for cheaper scheduled change feeds (off by default). No input or output changes; ad, details and landing-page prices unchanged.

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

More data tools by Automation Craft

Same engine, other placements:

More Meta transparency data:

Other marketing and ecommerce data:

⌂ 换网址