The Meta Ad Library shows every active ad on Facebook and Instagram. That makes it the best free source for competitor research. Getting the data out of it is the annoying part:

  • The official Ad Library API needs identity verification and covers political and issue ads, plus ads shown in the EU. A US shoe brand's normal ads aren't in it.
  • The website has no export button. You scroll and screenshot.
  • Its date filter means "ran during these days", not "started on these days", so you can't easily find new launches.
  • Nothing tells you which ads are winners, the ones an advertiser has kept paying for over weeks.

I built an Apify Actor that turns the Ad Library into clean rows: Facebook Ads Library Scraper & Ad Monitor. It reads the public Ad Library like a logged-out visitor, so there's no Facebook login, cookies or Meta API token. This post shows three uses: keyword research, a competitor's full ad list, and a daily monitor that only reports new ads.

What you get

One row per ad. A real row from a run (media links shortened):

{ "adArchiveId": "1454592559544426", "adLibraryUrl": "https://www.facebook.com/ads/library/?id=1454592559544426", "isActive": true, "startDate": "2026-05-13", "daysRunning": 170, "pageName": "Follow Your Legend", "pageLikeCount": 124078, "platforms": ["facebook", "instagram", "audience_network", "messenger", "threads"], "mediaType": "video", "bodyText": "AlpacaCloud is our custom-engineered blend of alpaca fiber and eucalyptus fiber...", "title": "Your feet deserve better than basic socks.", "ctaText": "Shop now", "linkUrl": "https://followyourlegend.com/pages/alpacacloud%C2%AE-socks-for-running", "linkDomain": "followyourlegend.com", "videoDurationSeconds": 44, "searchTerm": "running shoes", "searchCountry": "DE", "totalAdsInSource": 6993 } 

daysRunning is the column I use most. An ad that has run for 170 days is one the advertiser keeps paying for, which usually means it converts. You also get image and video links, carousel cards with their own text and links, and UTM tags from the landing page. For political ads you get spend and impressions as numbers.

Turn on ad details to add EU reach by country, age and gender, the payer and beneficiary, and the advertiser's Instagram followers.

Option 1: try it in the browser (2 minutes)

  1. Open the Actor page and click Try for free.
  2. Type a search term such as running shoes, pick one or more countries, and set Max ads per source to 30.
  3. Click Start. Ads show up in the Output tab within seconds.
  4. Download them as CSV, Excel or JSON.

You can also paste any Ad Library URL from facebook.com. Every filter in it is kept: country, status, media type, platforms, languages, dates and sort order.

Option 2: Python

Install the client with pip install apify-client.

Winning ads in a niche (video ads that have run for 30+ days, one per creative group):

from apify_client import ApifyClient client = ApifyClient("<YOUR_APIFY_TOKEN>") run = client.actor("panix/facebook-ads-library-scraper").call(run_input={ "searchTerms": ["skincare"], "countries": ["US"], "mediaType": "video", "minDaysRunning": 30, "skipDuplicateVersions": True, "maxAdsPerSource": 200, }) ads = sorted(client.dataset(run.default_dataset_id).iterate_items(), key=lambda a: a["daysRunning"], reverse=True) for ad in ads[:20]: print(ad["daysRunning"], ad["pageName"], ad["linkDomain"], ad["adLibraryUrl"]) 

Every active ad of your competitors (Page URL or numeric Page ID; 0 means no limit):

run_input = { "pageUrls": ["https://www.facebook.com/nike", "https://www.facebook.com/adidas"], "countries": ["ALL"], "activeStatus": "active", "maxAdsPerSource": 0, } 

New launches from the last 7 days: set "startDateFrom": "7 days". Unlike the website's date filter, this checks each ad's real start date.

Just counts (how many active ads does a brand or keyword have right now?): add "countOnly": True. You get one count row per source and no ad data.

Each run also saves an ADVERTISERS record with ads per advertiser, active ads, platforms, the longest-running ad and top landing domains. That's handy for a quick competitor overview.

Option 3: a daily competitor monitor

The goal is a message only when a competitor launches something new.

{ "pageUrls": ["https://www.facebook.com/nike"], "monitoringKey": "nike-daily", "onlyNewAds": true, "maxAdsPerSource": 500 } 

With a monitoringKey, the Actor stores the IDs of delivered ads in your own Apify account. Later runs with the same key skip those ads, and skipped ads aren't charged.

Save that input as a task in Apify and add a schedule. Or call it from n8n:

  • HTTP Request node: POST https://api.apify.com/v2/acts/panix~facebook-ads-library-scraper/run-sync-get-dataset-items with header Authorization: Bearer <YOUR_APIFY_TOKEN> and the JSON above as the body.
  • Then add a Slack or Gmail node that posts pageName, title, linkUrl and adLibraryUrl for each new ad.

What it costs

Pay per ad, no monthly fee:

  • $0.50 per 1,000 ads on Apify's Starter plan ($0.60 on the free plan, $0.30 on Business).
  • Ad details add $0.25 per 1,000 ads.
  • A start fee of $0.003 per run, and $0.001 per count row.
  • Failed sources, duplicates, filtered ads and ads already in your watchlist are free.

A daily monitor that finds 20 new ads costs about $0.013 per day. Apify's free plan includes monthly credits, so you can try it on your own competitors first.

Limitations

  • Meta publishes spend and impressions only for political and issue ads. For other ads, daysRunning, the number of versions and EU reach are the best public signals.
  • Meta keeps inactive commercial ads mainly when they ran in the EU (for 1 year). Older ads elsewhere may be missing from the public Ad Library, whatever tool you use.
  • Media links expire after a few days (see mediaUrlsExpireAt), so download files you want to keep.
  • Meta caps counts at 50,000+.
  • The Ad Library is a public transparency tool, but results can include public names of people (for example political advertisers). Use the data for a legitimate purpose and check Meta's terms. The Actor isn't affiliated with Meta.

If you need a field that isn't there, leave a comment or open an issue on the Actor's Issues tab with the run ID. I read them all.