Ad Intel: Google, Meta & TikTok Ad Library Monitor
Pricing
from $1.00 / 1,000 google ads
Ad Intel: Google, Meta & TikTok Ad Library Monitor
One input → Google, Meta & TikTok ad libraries (LinkedIn: beta, opt-in). Resolves brands/domains, unified schema, new/changed/stopped diffs. Pay per ad only — no start fee.
Pricing
from $1.00 / 1,000 google ads
Rating
0.0
(0)
Developer
Jeremy Lam
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Enter a competitor's website, brand name or page URL. In one run you get every ad they show on the Google Ads Transparency Center (Search, YouTube, Display, Maps, Play, Shopping), the Meta Ad Library (Facebook, Instagram, Messenger, Audience Network, Threads), the TikTok Ads Library (ads shown in the EU/EEA, Switzerland and the UK) and, experimentally, the LinkedIn Ad Library. Everything comes back in one clean, de-duplicated schema, and each ad is flagged as new, changed or stopped since your last run.
No login. No cookies. No API keys. You pay only for the ads you get.
LinkedIn (beta): off by default, experimental, currently blocked by LinkedIn. LinkedIn's Ad Library currently answers our requests with HTTP 403, so LinkedIn usually returns no ads (and costs nothing, because you pay only per ad). It is not searched unless you add
"linkedin"to Platforms; if you do, the run still succeeds andOUTPUT.errorsreports the block. Google, Meta and TikTok are not affected.
Why this Actor
| Ad Intel | Typical ad-library Actor | |
|---|---|---|
| Platforms per run | Google + Meta + TikTok (LinkedIn: beta, opt-in, currently blocked by LinkedIn) | One |
| Input | nike.com, Nike, a page URL, a page ID, an AR… ID, a LinkedIn company URL or any library URL | Usually a library URL or page ID |
| Advertiser matching | Resolves the domain or brand to the right Facebook page, the Google advertiser(s) verified by the domain their ads link to (e.g. notion.so → Notion Labs, Inc), the LinkedIn company and the TikTok advertiser. Look-alikes are dropped, and a "did you mean" list goes to OUTPUT | Keyword results with other advertisers mixed in |
| EU transparency (DSA) | Meta: EU reach plus age × gender × country reach, targeting (age, gender, locations), payer/beneficiary. TikTok: impressions by country × age × gender, targeting, audience size. LinkedIn: impressions by country, targeting | Usually missing, or a separate paid add-on |
| Google text ads | Headline, description, display URL, sitelinks, local-ad business/category/address. Ads published only as images are read with OCR | Raw preview strings or nothing |
| "What's new?" | Built-in diff: new, changed (with the changed fields), unchanged, stopped | Re-scrape everything and diff it yourself |
| Cost control | Hard cap per advertiser and platform, charge per ad only, no start fee, stops at your max charge | Issues report runs that ignored limits |
| Output | One schema plus optional raw platform record, and 3 ready-made table views | Different field names per platform |
Quick start
{"queries": ["nike.com", "notion.so", "https://www.facebook.com/nike"],"platforms": ["google", "meta", "tiktok"],"countries": ["US"],"maxAdsPerPlatform": 50}
Schedule this input daily with Output = "Only ads that are new since last run", then attach a webhook, Slack or email integration. You get an alert whenever a competitor launches a new creative.
Accepted queries
| You type | Meta | LinkedIn (beta, opt-in) | TikTok | |
|---|---|---|---|---|
nike.com | ads for that domain + advertisers whose ads link to the brand's domains (verified) | resolves the Facebook page linking to nike.com | company whose name exactly matches "nike" | advertiser named "Nike" (TikTok advertiser search) |
Notion Labs | advertiser-name lookup with domain verification | page-name match (or set treat queries as keywords for full-text search) | exact company-name match | advertiser search, keyword fallback optional |
AR16735076323512287233 / adstransparency.google.com/advertiser/AR… | that advertiser | – | – | – |
https://www.facebook.com/nike or 15087023444 | – | that page | – | – |
https://www.facebook.com/ads/library/?… | – | that exact search, all filters kept | – | – |
https://www.linkedin.com/company/hubspot/ | – | – | that company (exact name) | – |
https://www.linkedin.com/ad-library/search?… | – | – | that exact search | – |
https://library.tiktok.com/ads?…adv_biz_ids=… | – | – | – | that advertiser |
Output
Each dataset item is one ad. Fields that a platform doesn't publish are null, never invented.
| Field | Meta | LinkedIn (beta) | |
|---|---|---|---|
id (platform:adId, stable) | ✓ | ✓ | ✓ |
advertiser.name / id / url | ✓ | ✓ | ✓ |
advertiser.payer (paid for by) | – | ✓ when shown | ✓ |
status active/inactive | ✓ (last shown ≤2 days ago) | ✓ | ✓ when dates shown |
format | text/image/video | image/video/carousel/… | image/video/carousel/document/… |
firstShown, lastShown, daysActive | ✓ | ✓ | ✓ (EU-served ads) |
headline, body, description, cta | parsed from the preview | ✓ | ✓ |
landingUrls, displayUrl | display domain | ✓ | ✓ |
images, videos, thumbnail | ✓ (YouTube links for video) | ✓ | ✓ |
variants | ✓ | ✓ (collation / cards) | ✓ |
publisherPlatforms | search/youtube/maps/play/shopping | facebook/instagram/… | |
countries | ✓ | when Meta lists them | ✓ (impression share) |
impressions (+ byCountry, per surface) | ✓ ranges | when shown (political/EU) | ✓ total + share by country |
spend | – | ✓ (political/issue ads) | – |
reach.eu | – | when shown | – |
targeting | EU DSA flags (raw) | – | language, location, targeting parameters targeted/excluded |
change.status (+ changedFields, firstSeenAt) | ✓ | ✓ | ✓ |
storedMedia (with Download images) | ✓ | ✓ | ✓ |
raw (with Include raw) | search + detail records | full Ad Library record | labels |
The run also writes an OUTPUT record to the key-value store. It holds counts per platform and per change status, the advertiser each query resolved to, each platform's timing and status, and any errors. Webhooks and agents can read that one record instead of the whole dataset.
Sample item (real output, trimmed)
{"id": "meta:1107386451952079", "platform": "meta", "adId": "1107386451952079","adLibraryUrl": "https://www.facebook.com/ads/library/?id=1107386451952079","advertiser": { "name": "Nike", "id": "15087023444", "url": "https://www.facebook.com/nike/", "domain": "nike.com" },"status": "active", "format": "carousel", "firstShown": "2026-09-01", "lastShown": "2026-10-14", "daysActive": 46,"headline": "Nike Air Monarch IV", "body": "Run through summer with gear that can take the heat.", "cta": "Shop now","landingUrls": ["https://www.nike.com/"], "publisherPlatforms": ["facebook", "instagram"], "variants": 6,"change": { "status": "new", "firstSeenAt": "2026-10-15T12:41:15.171Z" }}
Monitoring and diffs
- History lives in a named key-value store (
ad-intel-monitorby default). Apify never auto-deletes named stores. - By default the monitor name is derived from your queries, platforms, countries, status and media type. The same input always compares against its own history. Set Monitor name to keep separate watchlists.
changedignores noise. A movinglastShowndate and rotating signed CDN URLs don't count as changes. For Meta dynamic product ads (isDynamic: true), only non-creative fields are compared, because Meta rotates their cards on every load.stoppedis reported only when a platform was scraped completely for that query. A capped or failed run never produces false "stopped" alerts. Stopped notices are free.
Pricing (pay per event, no start fee)
| Event | Price | Example |
|---|---|---|
| Google ad (with impressions, surfaces, text) | $1.00 / 1,000 | 50 ads ≈ $0.05 |
| Meta ad | $2.00 / 1,000 | 50 ads ≈ $0.10 |
| LinkedIn ad (with detail page; experimental, currently blocked by LinkedIn) | $2.00 / 1,000 | 50 ads ≈ $0.10 |
| TikTok ad (with age/gender/country detail) | $3.00 / 1,000 | 50 ads ≈ $0.15 |
| Stopped-ad notices, unchanged ads hidden by the output mode | free |
Max cost ≈ queries × platforms × max ads per platform × price. The Actor stops cleanly when it reaches the run's max-charge limit.
Google, Meta and TikTok connect directly (no proxy) and switch to Apify datacenter proxy only if blocked. Residential proxy is used for LinkedIn only. This keeps per-ad prices low and stable.
Use with AI agents and MCP
- Pricing is per event only, and the Actor needs only limited permissions (it writes only to its own storages and the named monitor store), so agents can call it on a fixed budget.
- Call it through the Apify MCP server (
https://mcp.apify.com) asad-intel-unified-ad-library, or withPOST https://api.apify.com/v2/acts/<user>~ad-intel-unified-ad-library/run-sync-get-dataset-items. - Minimal agent input:
{"queries":["competitor.com"],"maxAdsPerPlatform":20,"outputMode":"new"}. The flat JSON items need no post-processing. - Read
OUTPUTfrom the default key-value store for a one-object summary.
Proxy and reliability
- Google: public Transparency Center RPC with no proxy. If Google starts rate-limiting, the Actor switches to your proxy on its own.
- Meta: real Chrome session on the public Ad Library page, direct connection first, Apify datacenter proxy as fallback. Meta throttles infinite-scroll pagination from data-center IPs, so the Actor reuses one browser session and loads the first page of up to ~40 filter slices (media type × platform × sort). Measured on Nike: 197 unique ads from 16 slices, against 30 from plain scrolling. EU detail calls are not throttled.
- Google: if Google rate-limits direct requests, the Actor switches to Apify Proxy when you have it, and otherwise to a real browser session, which still gets answers.
- TikTok: drives the public library UI in Chrome, because the API needs a page-generated signature. It paginates with View more and opens 3 ad-detail pages in parallel (about 2.4 s per ad with details).
- LinkedIn (experimental, currently blocked by LinkedIn: requests get HTTP 403): public Ad Library HTML with browser-grade headers, 2 parallel requests, and backoff that honours
Retry-Afteron HTTP 429. If a detail page still fails, you still get the ad from its search card (detailsMissing: true). - Every platform call retries with exponential backoff. One platform failing never fails the run;
OUTPUT.errorsexplains what happened and how to fix it.
FAQ
Does it need my Facebook, Google or LinkedIn login? No. It only reads public ad libraries.
Why did Meta return fewer ads than the library shows? Meta throttles infinite-scroll pagination from data-center IPs. The Actor widens coverage with filter slices (see above); for very large advertisers, split the run by country or media type. The log reports how many ads each step added.
How are Google text ads parsed? HTML previews are parsed by template: Search ads give headline, description, display URL and sitelinks; Local ads give business, category, address, headline and description. Ads published only as a rendered image are OCR'd (extensions.textSource: "ocr", with ocrConfidence). Headlines that Google generates on the fly are flagged extensions.dynamicHeadline: true instead of being made up.
LinkedIn shows no dates or impressions for some ads? LinkedIn publishes dates, impressions and targeting for ads served in the EU (DSA). For other ads those fields are null.
TikTok outside Europe? TikTok publishes its ad library only for ads shown in the EU/EEA, Switzerland and the UK. If none of your countries is in that list, the Actor searches DE, FR, IT, ES and GB and says so in the log.
Is this legal? It collects only data the platforms publish in their public transparency libraries. Make sure your use complies with applicable laws and the platforms' terms.