Facebook Ads Library Competitor Tracker
Pricing
from $590.00 / 1,000 competitor reports
Facebook Ads Library Competitor Tracker
Track competitor Facebook and Instagram ads using Meta Ad Library data. Detect new and stopped ads, copy, CTA, landing-page, offer, creative, and media-mix changes over time.
Pricing
from $590.00 / 1,000 competitor reports
Rating
0.0
(0)
Developer
Furkan Toluç
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
4 days ago
Last modified
Categories
Share
Meta Ads Competitor Change Radar
Track new, stopped, changed, long-running, and variant-heavy Facebook and Instagram ads by competitor.
Meta Ads Competitor Change Radar is a stateful competitive-intelligence Apify Actor. It answers one narrow question: What changed in my competitors' public Meta advertising since the last time I checked?
It is designed for paid media agencies, DTC brands, performance marketers, competitive-intelligence teams, and growth marketers. It is not a generic scraper, performance detector, AI analyzer, or dashboard.
What it does
For each competitor, the Actor:
- validates one HTTPS Facebook Page or advertiser-scoped Meta Ad Library URL;
- calls
curious_coder/facebook-ads-library-scraperin an isolated child run; - normalizes the public evidence and validates that it represents one advertiser;
- compares it with the last successful snapshot stored in a named key-value store;
- detects supported changes using stable ad IDs;
- groups conservative creative families and extracts duration, media, CTA, landing-domain, and English-oriented offer signals;
- writes one concise Dataset item per competitor; and
- advances state only after the report has been accepted.
The parent Actor does not scrape Facebook directly. It requires no Meta login or Meta API token. It uses the Apify run's authenticated execution context to call the upstream Actor, whose usage may have its own cost.
Input
{"monitorKey": "skincare-us","competitors": [{"name": "Competitor A","pageUrl": "https://www.facebook.com/examplebrand"},{"name": "Competitor B","pageUrl": "https://www.facebook.com/examplebrand2"}],"country": "US","maxAdsPerCompetitor": 100,"durableAdDays": 30,"resetBaseline": false,"debug": false}
Reuse the same monitorKey, country, and normalized competitor name on future runs. That tuple selects the previous snapshot. The first successful run creates a BASELINE; later runs compare against it. resetBaseline: true deliberately ignores the previous snapshot and replaces it only after successful report output.
The MVP accepts 1–5 competitors, requests 20–250 ads per competitor, and uses a 7–180 day long-running threshold. URLs must use HTTPS and a real facebook.com host or subdomain. Keyword inputs are not accepted. ALL is not accepted as a country because its reliability has not been established for the upstream source.
Change language
LIKELY_NEW: a previously unseen, high-confidence ad ID has a start time at or after the previous snapshot minus a six-hour clock-skew window.NEWLY_OBSERVED: a previously unseen stable ID clearly predates the prior snapshot, or its start date is unknown. It is not presented as a newly launched ad.STOPPED_CONFIRMED: the same high-confidence ID was previously active and is now explicitly inactive or has acquired a credible end date.NOT_OBSERVED_THIS_RUN: a previously observed high-confidence ID is absent from the current sample.REACTIVATED: the same high-confidence ID changed from explicitly inactive to active.COPY_CHANGED,CTA_CHANGED, andLANDING_CHANGED: both snapshots contain evidence for the relevant field and its deterministic fingerprint changed on the same stable ID.
Absence never proves an ad stopped. Meta Ad Library results can be sampled or limited, so absence produces NOT_OBSERVED_THIS_RUN, never STOPPED_CONFIRMED. Disappearing low-confidence derived IDs do not produce strong disappearance or stopped claims.
The status is CHANGES_DETECTED only when there is at least one likely-new, explicitly stopped, reactivated, or supported copy/CTA/landing change. NEWLY_OBSERVED or NOT_OBSERVED_THIS_RUN alone yields NO_CONFIRMED_CHANGE.
Current snapshot intelligence
Every successful report includes observed/active/inactive counts, evidence coverage, media mix, top CTAs and landing domains, deterministic offer signals, up to ten oldest active ads, long-running counts, and conservative creative-family counts.
LONG_RUNNING means only that an ad is explicitly active and its public dates show at least durableAdDays of duration. It does not establish profitability.
VARIANT_HEAVY means an observed creative family contains at least three related variants. It does not establish spend, scaling, conversion quality, or profitability.
Offer extraction is deterministic and English-focused. It recognizes explicit signals such as percentage or fixed discounts, free shipping/trials/consultations/gifts, BOGO, promo codes, and limited-time phrases. It does not infer ambiguous offers.
Creative-family grouping
Grouping is deterministic and false-merge averse:
- equal Meta
collationId: confidence1.0; - exact creative fingerprint: confidence
0.95; - token Jaccard similarity ≥
0.82, only with the same known media type and exactly the same registrable landing domain: confidence0.75.
The implementation uses Union-Find. It does not use image similarity, embeddings, OCR, media downloads, or AI.
Evidence strength and report threshold
Evidence strength is a transparent 0–100 score:
round(stableIdCoverage × 45+ startDateCoverage × 20+ activeStatusCoverage × 20+ contentCoverage × 15)
A successful report requires a succeeded child run, at least one valid normalized ad, unambiguous advertiser identity, stable-ID coverage of at least 70%, and evidence strength of at least 60. Failures produce an uncharged analysis_error and never overwrite prior state.
Output
The default Dataset contains exactly one item per requested competitor: either a competitor_report or an analysis_error. Its overview surfaces competitor, status, evidence, observed and active ads, likely new, confirmed stopped, long-running, variant-heavy families, primary change, and snapshot time.
The default run key-value store contains:
OUTPUT: run-level counts;REPORT.md: a human-readable report; andEVIDENCE.json: normalized evidence from the current run only.
With debug: true, the default store also receives at most five raw and five normalized diagnostic records per competitor. Debug output never includes credentials.
Persistent comparison state lives separately in the named store meta-ads-change-radar-state-v1 (configurable through STATE_STORE_NAME). Keys are state- plus 24 hexadecimal SHA-256 characters derived from monitor key, country, and normalized competitor name. State stores only compact diff evidence, uses schemaVersion: 1, and increments a revision. A compare-before-write check preserves existing history if concurrent runs race.
If the confidently resolved page ID changes for the same state key, the Actor reports COMPETITOR_IDENTITY_CHANGED and creates a fresh baseline instead of comparing different advertisers.
Upstream call
One child Actor run is made per competitor, with controlled concurrency. The generated input is exactly:
{"urls": [{"url": "<validated and normalized competitor Facebook URL>"}],"scrapeAdDetails": false,"limitPerSource": 100,"scrapePageAds.activeStatus": "all","scrapePageAds.sortBy": "most_recent","scrapePageAds.countryCode": "US","runTag": "<monitorKey>-<timestamp>-<competitorIndex>"}
limitPerSource and countryCode reflect public input. No period is set, because that would damage long-running-ad evidence. No count, proxy override, or Meta credential is passed. Dataset retrieval is paginated and stops at maxAdsPerCompetitor + 50. Only SUCCEEDED child runs are analyzed.
Environment variables:
META_ADS_ACTOR_ID=curious_coder/facebook-ads-library-scraperMETA_ADS_CHILD_CONCURRENCY=2META_ADS_CHILD_TIMEOUT_SECS=300STATE_STORE_NAME=meta-ads-change-radar-state-v1ENABLE_REPORT_CHARGING=false
Monetization scaffold
Charging is disabled by default and must remain disabled for initial private validation. When explicitly enabled later, successful BASELINE, CHANGES_DETECTED, and NO_CONFIRMED_CHANGE reports are pushed with the single custom event competitor_report. State advances only if that event is accepted. Analysis errors are always pushed without a paid event. No dollar price is hard-coded.
When Pay-Per-Event is later configured in Apify Console, remove or avoid the automatic apify-default-dataset-item paid event. The intended model is Actor Start plus competitor_report only; otherwise one result could be charged twice.
Local development
Node 24 is the production target.
npm cinpm run typechecknpm testnpm run buildnpm run checknpx --yes apify-cli@latest validate-schemanpm audit
No live upstream run is part of automated validation. See LIVE_VALIDATION.md for the manual sequence after private deployment.
Private deployment readiness
- Create a private GitHub repository named
meta-ads-competitor-change-radar. - Push this project without local
storage,.env, or credentials. - Create a private Apify Actor connected to that repository.
- Build with the included Node 24 Dockerfile.
- Keep
ENABLE_REPORT_CHARGING=false. - Follow
LIVE_VALIDATION.md, beginning with a one-competitor, 20-ad schema probe. - Do not publish or enable pricing until the validation targets are met.
Limitations
- Results are sampled public Ad Library evidence and may be truncated.
- The upstream Actor is an external dependency and its schema can change.
- Only publicly available Ad Library fields are used.
- Offer extraction is English-oriented.
- No performance metrics, spend, profitability, conversion, or ROAS inference is made.
- Meta can change data availability or visibility.
- The parent does not crawl landing pages, fetch media, or enrich advertisers.
Long-running, variant-heavy, and newly observed signals are public-ad-library evidence. They do not prove profitability, spend, ROAS, or conversion performance.