Facebook Ads Library Scraper — Meta & Instagram avatar

Facebook Ads Library Scraper — Meta & Instagram

Pricing

from $2.00 / 1,000 successful ads

Go to Apify Store
Facebook Ads Library Scraper — Meta & Instagram

Facebook Ads Library Scraper — Meta & Instagram

Scrape Facebook Ad Library and Meta ads by advertiser, keyword, or URL. Get ad copy, images, videos, carousel cards, links, dates, and optional EU data. Export JSON, CSV, or Excel for competitor ad research. Pay only for successful saved ads.

Pricing

from $2.00 / 1,000 successful ads

Rating

0.0

(0)

Developer

Chronometrica

Chronometrica

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

📊 What does Facebook Ads Library Scraper do?

Search public Facebook and Instagram ads from Meta Ad Library, also called Facebook Ad Library. Enter a Facebook page, keyword, or Ad Library URL. Get ad copy, images, videos, carousel cards, landing page links, dates, and advertiser details in one row per ad. Export the results to JSON, CSV, or Excel.

Compare competitor offers and ad copy. Build a creative library or schedule repeat searches to track competitor ads. A carousel card is one item in a multi-card ad. Each card keeps its text, links, and media together. Partnership ads keep the advertiser separate from the page that published the creative.

No Facebook login, supplied account cookies, or Meta API plan is required. The Actor collects public library data. It does not collect private Ads Manager performance, conversions, or return on ad spend.

Pay only for successful ads saved to your dataset. Creative cards and optional public details have no extra fee.

With Facebook Ads Library Scraper, you can:

  • 🔎 Find competitor ads by Facebook page, keyword, or Ad Library URL.
  • 🖼️ Save ad copy, carousel cards, image and video URLs, and preview images.
  • 🔗 Keep each card's copy, call to action, and destination together.
  • 🤝 Keep the advertiser and creative publisher separate on partnership ads.
  • 🌍 Filter by search country, active status, ad category, media, platform, language, and dates.
  • 🧾 Add public advertiser profiles and EU audience or payer data when available.
  • ♻️ Save each ad once per run, with the inputs that found it in matches.
  • 📦 Export JSON, JSONL, CSV, Excel, XML, RSS, or HTML through Apify.

📦 What data can I extract?

One dataset row represents the first retained observation of one ad archive ID during one run. Its key is (runId, adArchiveId). A later match adds a relationship to the same row. It does not replace the first ad facts. Cards, media versions, and input matches stay inside that row.

The JSON fields follow this order:

Data groupFields and use
🆔 ObservationrunId, adArchiveId, observedAt, sourceAdId, adUrl
🏢 AdvertiserPage identity, profile image, categories, deleted flag, likes, and advertiserDetails
🤝 Creative publishercreativePage keeps the source creative page identity and its own page measurements
📅 Ad contextActive flag, source dates, platforms, countries, categories, collation, and EU eligibility
🖼️ ContentFormat, copy, links, ordered creatives, extra variants, media lists, and content flags
🧾 DisclosuresSource currency, spend, reach, impressions, and EU audience data under disclosures
📌 Input relationshipsmatches records the processed inputs that returned the ad
🚦 Qualitystatus, statusReason, and fieldStates

This is the exported JSON model. The Actor does not create separate database tables. Missing values remain null. An explicit source zero stays zero. An explicit empty source list stays [].

For dynamic product ads, the ad-level title can contain {{product.name}}. That is source text. Use each entry in creatives for its product title, link, and media. The Actor does not generate replacement copy.

⚙️ Can I use this as a Meta Ad Library API?

Yes. Use the Apify API to search public Facebook and Instagram ads and return structured data. This is an independent scraper API. It is not Meta’s official Ad Library API. You can also use Apify schedules, webhooks, and integrations. JSON keeps each ad’s creative cards and media together. CSV and Excel are useful for flat ad lists.

Example with the Apify JavaScript client:

import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor("chronometrica/facebook-ads-scraper").call({
targets: ["15087023444"],
countries: ["US"],
activeStatus: "active",
maxAds: 10,
details: "none",
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

🎯 Why scrape Facebook Ad Library and Meta ads?

Use caseHow the data helps
🕵️ Competitor researchCompare public offers, messages, formats, and landing pages
🖼️ Creative librariesStore ordered cards and media links with stable ad IDs
🌍 Regional researchCompare library results under country and platform filters
🤝 Partnership researchKeep a sponsoring advertiser separate from a creator page
📅 Ad comparisonsRepeat inputs and compare observations by ad ID
🧱 Data pipelinesLoad a fixed output shape into your database or reporting workflow

Repeated runs provide observations. They do not automatically compute change reports. An ad missing from a filtered, capped, or incomplete run does not prove that it stopped running. To build a latest view, select the latest eligible observation for each ad ID. Keep earlier observations when you need history.

💵 Pricing

You pay only for a successful unique ad saved to your dataset. The custom event is successful-ad.

Your Apify planStore discount tierPrice per 1,000 successful ads
FreeFREE$2.75
StarterBRONZE$2.50
ScaleSILVER$2.25
BusinessGOLD$2.00
EnterprisePLATINUM / DIAMOND$2.00

There is no start fee, subscription fee, or separate details fee. Standard-run platform and proxy usage are included. Apify account plan fees are separate. These are plan discounts, not volume bands within one run.

A successful ad has a valid archive ID, passes the output and creative checks, has usable creative text, a link, or media, and has row status ok. Missing optional source values, such as undisclosed spend, do not make an otherwise valid ad fail. Source product templates remain unchanged; use creatives[].title for product titles.

ResultActor event charge
Successful unique ad savedOne successful-ad event
Saved partial ad or empty creative recordFree
Invalid input, failed lookup, blocked request, filtered candidate, or duplicateFree
Empty search or count-only runFree
Valid ads saved before a later failure, time limit, or spending stopCharge only those successful saved ads

Cards, media versions, input matches, and optional public details inside an ad do not add charges. A later run creates new observations and can charge for successful ads again.

At the Free-plan rate, the Actor event charge is:

successful unique ads × $0.00275

Successful adsFree-plan event charge
0$0
10$0.0275
100$0.2750
1,000$2.7500

The Console requires a spending allowance of at least $0.01. This is a spending limit, not a minimum charge. A run with no successful ads has no Actor event charge.

The Actor allows 256 MB to 1 GB memory. Keep the default 1 GB for your first run. The default run time limit is 20 minutes. Bulk public details take longer than basic collection. Use maxAds and the Console spending limit to control result volume. maxAds caps all saved ad rows, including free partial rows. It does not guarantee that many results.

🚀 How do I use Facebook Ads Library Scraper?

  1. Open the Actor in Apify Console.
  2. In Pages, Ad Library URLs, or keywords, replace the example with your page URL, Library URL, page ID, or keywords. Each entry starts a separate search.
  3. Set Total ad limit — all inputs to 10 for your first run.
  4. Leave Countries blank to use each Library URL's country, or United States when the input has no country. Select countries only when you want an explicit choice.
  5. Optional sections hold filters and sorting, ad start dates, activity windows, and output choices. Standard ad data already includes creative cards and media links.
  6. Click Start.
  7. Open Output → Ads. Use Table or JSON to inspect the data.
  8. Use Export to download results. Select Run summary and input outcomes to check collection limits and errors.

⬇️ Input

Use the tested small example:

{
"targets": ["https://www.facebook.com/nike/"],
"maxAds": 10
}

🔎 Target types

InputExampleMeaning
Advertiser page ID15087023444List public ads for that advertiser
Public Facebook page URLhttps://www.facebook.com/nike/Resolve its library advertiser ID
Advertiser Library URLhttps://www.facebook.com/ads/library/?view_all_page_id=15087023444&country=USUse the advertiser and supported URL filters
Keyword Library URLhttps://www.facebook.com/ads/library/?q=running%20shoes&country=USUse the keyword and supported URL filters
Single-ad URLhttps://www.facebook.com/ads/library/?id=1829888761376507Collect that exact public archive ad when available

A bare numeric ID means an advertiser page ID. Use a Library URL with id= for a single ad. A modern Facebook profile ID can differ from its library advertiser ID; a supported page URL lets the Actor resolve that difference.

Plain text in targets is a keyword search. For example, "running shoes" searches across advertisers. A public Facebook URL can omit https://; unsupported website URLs are rejected. Replace the example entry for a keyword-only run. Adding both a page and a keyword starts two independent searches.

🎛️ Settings

Enter at least one item in targets. Existing API calls can still use searchTerms; the form hides this field. If you supply both fields, each entry starts a separate search. All searches share one total maxAds limit. Dates use Coordinated Universal Time (UTC).

SettingAllowed values and defaultEffect
targetsUp to 40 IDs, supported URLs, or keywords; omitted means noneOne search per entry; use page URLs for one advertiser
searchTermsUp to 20 non-empty strings; omitted means noneCompatibility field for existing API keyword searches
countriesUp to 5 library country codes; omitted or [] follows the URL, otherwise USSelect library search scope; use ["ALL"] alone for all countries
activeStatusactive, inactive, all; default activeSelect source active status
adTypeall, political_and_issue_ads, housing_ads, employment_ads, credit_ads; default allSelect source ad category
mediaTypeall, image, video; default allSelect source media filter
publisherPlatformsUp to 4: facebook, instagram, messenger, audience_network; default []Empty follows the URL, otherwise applies no platform filter
contentLanguagesUp to 10 two-letter lower-case codes, such as en; default []Empty follows the URL, otherwise applies no language filter
searchModebroad, exactPhrase; default broadChoose keyword matching
sortByimpressions, recent, activeTime; default impressionsSource order within each search; recent uses month-based relevance groups
modeads or count; default adsCollect rows, or read per-search source totals in the summary
startDatePresetany, last7, last30, or custom; optionalFilter when ads started; relative presets include today in UTC
startedOnOrAfterOptional YYYY-MM-DDInclude source start dates on or after this UTC date
startedOnOrBeforeOptional YYYY-MM-DDInclude source start dates on or before this UTC date
activeOnOrAfterOptional YYYY-MM-DDLower bound of an inclusive UTC delivery window
activeOnOrBeforeOptional YYYY-MM-DDUpper bound of an inclusive UTC delivery window
maxAdsNon-negative whole number; default 100; 0 means no ad-count capOne saved unique-ad cap across the run; no fixed maximum; blank and null are invalid
detailsnone, advertiser, transparency, all; default noneRequest extra public fields; more requests can take longer

The form's Use URL setting choice has the API value auto. It is available for activeStatus, adType, mediaType, searchMode, and sortBy. It follows a pasted URL and otherwise uses the default listed above. Empty country, platform, and language lists also follow the URL. Conflicting explicit form and URL filters produce an unsupported-input outcome. Unsupported URL selection fields are not silently removed.

Ad start dates provides Any time, Last 7 days, Last 30 days, and Custom start dates. Relative presets resolve at the start of each run, including scheduled runs. Last 7 days means today and the six previous UTC calendar dates. The summary records the resolved bounds. Choose custom to enter at least one start-date bound. Do not combine another preset with custom bounds. Existing API calls that omit startDatePreset can still supply their date bounds directly.

Start-date filters test when an ad started. Activity-window filters keep source delivery intervals that overlap the requested UTC dates. A currently active ad has no known end for this check. An inactive ad that started before the lower bound needs a valid source end time. Missing dates needed to decide overlap produce an incomplete input outcome. You can combine the two date filter types. The Actor checks them before details and saving. Selective filters can require reading many candidates.

Sorting follows Meta's order within each search. Recent mode uses relevance groups by month, not a strict start-time sort. ActiveTime mode orders by Meta's reported active time and can help find long-running ads. Inputs run in input order; the Actor does not combine all inputs into one global sort. Card order is the order observed in that response and can vary across requests.

Use maxAds: 0 to remove the ad-count cap. This does not remove the spending limit, run time limit, source access limits, or available inventory. Large runs still need sufficient time and budget.

🔎 Search by keyword

{
"targets": ["Nike"],
"countries": ["US"],
"contentLanguages": ["en"],
"searchMode": "exactPhrase",
"maxAds": 25,
"details": "none"
}

Keywords can find resellers and other advertisers that use the term. A brand-name search does not restrict results to that brand's own page. Use an advertiser target for that purpose.

📊 Read counts without collecting ads

{
"targets": ["15087023444"],
"countries": ["US", "DE"],
"mode": "count"
}

Open Run summary and input outcomes for the preview, or Run summary and counts (JSON) for API data. Each attempt has sourceReportedTotal and sourceReportedTotalPrecision. Counts can be approximate or unavailable and can overlap across inputs. Do not add them to claim a unique-ad total. Count mode reads only the first source result per search. It creates no ad rows and no Actor event charges. maxAds is ignored. Date filters and additional details are not supported in count mode.

📅 Find ads active during a window

{
"targets": ["15087023444"],
"activeStatus": "all",
"activeOnOrAfter": "2026-10-01",
"activeOnOrBefore": "2026-10-25",
"sortBy": "recent",
"maxAds": 100
}

This can include an ad that started before October and remained active during the window. It is based on source dates and flags, not a complete historical delivery log.

🧾 Request advertiser and EU details

{
"targets": ["15087023444"],
"countries": ["DE"],
"mediaType": "video",
"publisherPlatforms": ["instagram"],
"maxAds": 3,
"details": "all"
}

advertiser requests public page and linked Instagram information. transparency requests public audience and payer/beneficiary disclosures. all requests both. Availability varies by ad, region, category, and time. Start with a small limit when requesting details.

⬆️ Output sample

This shortened, illustrative partnership record shows the structure. Media URLs use placeholders. The full dataset has 44 top-level fields.

{
"adArchiveId": "1829888761376507",
"adUrl": "https://www.facebook.com/ads/library/?id=1829888761376507",
"pageId": "16453004404",
"pageName": "Sephora",
"pageUrl": "https://www.facebook.com/sephora/",
"creativePage": {
"pageId": "492109351169937",
"pageName": "Jazzy Le",
"pageUrl": "https://www.facebook.com/100066338425888/",
"pageProfileImageUrl": "https://media.example.com/page.jpg",
"pageCategories": null,
"pageIsDeleted": false,
"pageLikeCount": null
},
"publisherPlatforms": ["facebook", "instagram"],
"displayFormat": "VIDEO",
"creatives": [
{
"creativeIndex": 0,
"sourceKind": "snapshot",
"bodyText": "<source ad copy>",
"title": null,
"linkDescription": null,
"linkCaption": null,
"linkUrl": null,
"ctaText": null,
"ctaType": null,
"images": [],
"videos": [
{
"hdUrl": "https://media.example.com/video-hd.mp4",
"sdUrl": "https://media.example.com/video-sd.mp4",
"previewImageUrl": "https://media.example.com/preview.jpg",
"watermarkedHdUrl": null,
"watermarkedSdUrl": null
}
]
}
],
"creativeCount": 1,
"creativeVariants": {
"texts": [],
"linkUrls": [],
"images": [],
"videos": []
},
"isActive": true,
"status": "ok",
"statusReason": null
}

🖼️ Read creative records

For an ad with cards, creatives contains one ordered entry per source card. creativeIndex is a position within this observation. It is not a stable creative ID across runs. For an ad without cards, it contains one snapshot entry. Each entry keeps its own copy, link, images, and videos.

Images can include original, resized, and watermarked URLs. Videos can include source HD and SD URLs, previews, and watermarked versions. Unavailable versions remain null.

creativeVariants keeps extra source lists separately. Array positions do not prove that an extra text, link, and video belong together. creativeCount counts main cards or the snapshot, not these unpaired variants. Neither count measures total advertiser inventory.

📌 Read matches and missing fields

This shortened example shows one ad found by two processed inputs. The single ad row is charged once. inputIndex starts at zero.

{
"adArchiveId": "1016451784037642",
"matches": [
{ "inputIndex": 0, "input": "15087023444", "country": "US" },
{ "inputIndex": 1, "input": "Nike", "country": "US" }
],
"advertiserDetails": null,
"fieldStates": [
{
"field": "advertiserDetails",
"state": "not_requested",
"reason": "Public advertiser details are unavailable or were not requested."
}
]
}

matches contains only processed inputs that actually returned the ad. It does not claim every possible match. fieldStates explains selected missing or invalid fields by field path. It is not a list of every nullable nested field. For example, not_requested means details were off; not_exposed means the public response did not supply the value. False, zero, and an explicit empty list are valid values.

videoPreviewUrls gives flat preview links for exports. Use creatives[].videos[] to keep each preview tied to its video. containsDigitallyCreatedMedia, containsSensitiveContent, and isEuTransparencyEligible preserve source flags. A digitally-created-media flag does not independently prove AI use. EU eligibility does not guarantee that reach details are available.

🤝 Read partnership ads

Top-level page fields describe the advertiser. creativePage describes the source creative publisher. Those pages can differ. Missing advertiser profile images, categories, or page-like counts are not filled with the creator's values.

🕒 Read observation times

observedAt is the time the Actor received the first retained ad response. It is not the ad's start time or a guarantee that all nested values were read at that time.

  • advertiserDetails.profileObservedAt records the profile detail response time.
  • advertiserDetails.pageObservedAt records the public page response time. That page response can be reused for several ads in the run.
  • advertiserDetails.aboutTextObservedAt records the response used for the selected About text.
  • disclosures.euAudience.observedAt records the EU detail response time.

These times stay null when the response time is unavailable. They are collection times, not source publication or update times.

📌 Read the run summary

The Run summary and input outcomes output uses schema version 1.4.0. It includes runId, buildId, startedAt, finishedAt, mode, and observationPolicy: "first_retained". It describes saved unique ads, saved creative units, duplicate and filtered candidates, and an outcome for each input-country attempt.

effectiveInput records the normalized ad cap, detail choice, and four local date bounds. Relative start-date presets appear as their resolved UTC bounds. A cap of 0 means unlimited. In count mode, the cap is null because no ad rows are saved. Each attempt has a selection object. It records the resolved target and effective source filters, including filters from an Ad Library URL. The attempt's country and source URL stay beside that object. This records what the run requested. It cannot guarantee the same source response on a later run. If input validation fails, mode and effectiveInput can be null.

Each attempt includes input, country, source URL, matched-ad count, status, and stop reason. max_ads means the global ad cap was reached. Later distinct inputs are not_attempted; they do not trigger more collection or details. cost_limit means the remaining paid budget cannot cover another ad. source_exhausted means the source ended the result sequence. count_only means the attempt read its source total without saving ads. Time limits and access errors can leave valid saved rows with a partial run summary.

successfulAdsSaved counts saved ads that pass the billing rule. uniqueAdsSaved also includes free partial ads. billableAds reports recorded successful-ad charges after the ads were saved. It is zero when no successful ads were charged. It is null if a charge failed or the count is unavailable or inconsistent. It does not estimate charges from all saved rows.

An ad can match several inputs. Attempt-level counts overlap; do not add them to estimate unique ads. Source-reported search totals have an unknown scope or precision unless stated otherwise. They are not a verified global advertiser inventory.

🚦 Status, quality, and precision

  • Saved row ok: ad identity and contracted creative structure passed the checks.
  • Saved row partial: an identified ad has an unavailable or invalid field that affects its contract. Read fieldStates and statusReason.
  • Run complete: accepted attempts finished under their stated stop conditions. A cap-limited run can be complete.
  • Run partial: useful ads were saved, but an attempt or row could not fully finish.
  • Run failed: collection failed. Rows already saved remain available.

Input outcomes distinguish unsupported input, no public data, blocked or challenged access, login-required responses, rate limits, and parsing failures. Failure outcomes are kept in the summary, not written as fake ad rows.

Spend, reach, and impression disclosures remain source-reported text or ranges. A range does not become an exact value. The Actor does not infer sales, campaign budget, conversions, or profitability. EU reach, target locations, and payer/beneficiary fields are separate source facts. Missing demographic cells are not zero.

➕ Count data at the correct level

ValueSafe use
Saved adsCount unique (runId, adArchiveId) pairs for one run. Across runs, distinguish observations from unique ad IDs.
creativeCountCount main card or snapshot occurrences within the saved observations. This is not a count of globally unique assets.
Page likes and Instagram followersTreat them as page snapshots. The same value can repeat on many ad rows. Do not sum those rows.
EU reachKeep the reported total and each country, age, and gender value. Do not assume cells sum to the total. Do not sum reach across ads or runs to count unique people.
Spend and impression rangesKeep bounds, currency, and source text. Do not turn ranges into exact totals. The source may not expose the measurement period.
collationCountTreat it as a source group count. The group can repeat across ad rows.
Media URLsCount available URL versions only. Several URLs can refer to the same asset.

For example, one ad with two matches, two cards, and three reach cells becomes 12 rows if you expand all three lists together. It is still one ad. Count ads before expanding lists. Summarize each list on its own before joining results. Never sum a repeated page or reach value from the expanded rows.

⚠️ Limits and interpretation

  • maxAds is a saved-row cap. Source availability, filters, source limits, the run time limit, or the spending limit can produce fewer rows.
  • The Actor saves each ad once per run. matches lists only processed inputs that returned that ad. The global ad or spending limit can stop the run before later inputs are searched. For several inputs, rows can appear after the searches finish.
  • Library country is search scope, not proof of an ad's target or reach countries. Read the separate source fields for those facts.
  • A source end time can be present while isActive is true. It does not override the source active flag.
  • Page likes and Instagram followers are page observations. Do not add them across ad rows.
  • Media URLs can expire. Collect them again when you need a current URL. The Actor returns links; it does not host permanent media copies.
  • Full creative extraction means the contracted cards and variants returned in the captured response, not every possible product variation.
  • Historical data depends on what Meta exposes. Access and field availability can change. A requested ad limit does not guarantee that many results.

❓ FAQ

How do I export Facebook Ad Library data to CSV or Excel?

  1. Enter an advertiser page, keyword, or Ad Library URL in Pages, Ad Library URLs, or keywords.
  2. Set an ad limit and click Start.
  3. Open the run’s dataset and select CSV or Excel from the export options.

Use JSON when you need each carousel card’s text, links, images, and videos together. Flat exports are useful for ad lists and spreadsheet research.

Can I track competitor ads on a schedule?

Yes. Save your advertiser inputs as an Apify Task and add a schedule. Compare saved observations by adArchiveId across runs. You must build the comparison or notification step in your workflow. The Actor does not send new-ad alerts or compute change reports. An ad missing from an incomplete or capped run does not prove it stopped.

Can I use this with n8n, Make, or Google Sheets?

Yes. Run the Actor through the Apify API or a supported Apify integration. Send the dataset fields you need to your next workflow step or spreadsheet. Use adArchiveId to match ads across runs. Keep nested creative records in JSON when your workflow needs individual cards or media versions.

Do I need a Facebook account or Meta API plan?

No. The Actor uses public Meta Ad Library data. You need an Apify account to run it.

Why is the result count lower than my limit?

The limit is an upper bound. Filters may match fewer ads, the source may end or limit results, or the run may stop early. Read input outcomes and stop reasons in the summary.

No. It is one saved ad row. Nested cards, media, and variants do not create more ad charges. Only a successful saved ad is charged.

Why are spend or reach fields null?

Meta does not expose those disclosures for every ad. Missing is not zero. Category or country selection does not guarantee a disclosure.

Can I use several inputs without duplicate ads?

Yes. The Actor removes duplicates by archive ID within a run and records matching inputs in matches. A later run is a new observation.

🔒 Source boundary and support

This is an independent tool for public Meta Ad Library data. It is not affiliated with Meta. Respect creative owners' rights when using or sharing their content.

Use the Actor’s Issues tab in Apify Store to report a problem. Include the run ID, public target or search input, expected field or count, and an example result. State whether it affects one ad or the whole run. Do not send account credentials.