# Facebook Ads Library Scraper — Meta Ad Library & Instagram Ads (`hyperbach/meta-ads-library-scraper`) Actor

Every ad a page runs on Facebook, Instagram, Messenger, WhatsApp and Threads as one flat row: headline, body, CTA, landing URL, image and video URLs, run dates, platforms. Optional EU reach and audience per ad, political spend ranges. Honest counts, only-new-ads monitoring, proxies handled.

- **URL**: https://apify.com/hyperbach/meta-ads-library-scraper.md
- **Developed by:** [Hyperbach](https://apify.com/hyperbach) (community)
- **Categories:** Social media, Integrations, Marketing
- **Features:** Limited permissions
- **Stats:** 145 total users, 84 monthly users, 99.9% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.75 / 1,000 ad creatives

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Meta Ad Library Scraper — Facebook & Instagram Ads + EU Reach

**Every ad a page is running on Facebook, Instagram, Messenger, WhatsApp, Audience Network and Threads — as one flat row per creative, with the copy, the landing URL, the media, the run dates and, when you ask, the EU reach.** Headline, body, description, call to action, image and video URLs, first and last shown, platforms, and for ads about social issues, elections or politics the spend, impressions and reach ranges Meta publishes with the funding entity. **[Watch a competitor on a schedule](https://apify.com/hyperbach/meta-ads-library-scraper/examples/follow-a-competitors-meta-ads-weekly) and get only their new ads** — into Slack or your inbox through Apify's own integrations, with ads you already have never returned or charged. Same column names as our [Google Ads Transparency Scraper](https://apify.com/hyperbach/google-ads-transparency-scraper), so one sheet holds both platforms. **Proxies are handled for you.**

**From $1.00 per 1,000 rows (`ad`) on the Free plan; every row and add-on is priced under Pricing below.** From 5 November 2026 a run start is $0.01. The default input returns about 50 rows for about $0.05.

![A Meta Ad Library page in a browser: the ads of one invented advertiser, a lighting brand with about 38 results, and one active desk lamp ad that four ads use, with its EU transparency panel. Beside it, the row this Actor returns for the ad, with advertiser_active_ads and collation_count highlighted, then the advertiser and its page ID, the Library ID, the headline, the call to action, the EU reach and the payer.](https://api.apify.com/v2/key-value-stores/dcLLNGq99686o2A4v/records/hero-meta-ads-library-scraper.webp)

### Tested head-to-head on 2026-11-01

The job: every ad of one advertiser page (Meta Ad Library page id 20409006880), 50 ads. Every Actor ran on it the same day from the same harness; a rival's cost is what a buyer account paid, this Actor's is its list price on the Free plan. Fields counts the filled fields of a typical row. Re-measured monthly.

| Actor | Rows | First row | Run time | Cost | Fields | Failures |
|---|---|---|---|---|---|---|
| **this Actor** | 50 | 19 s | 21 s | $0.050 | 40 | none |
| `apify/facebook-ads-scraper` | 50 | 23 s | 32 s | $0.25 | 26 | none |
| `curious_coder/facebook-ads-library-scraper` | 50 | 18 s | 18 s | $0.038 | 27 | none |

### Why this scraper, not the other Ad Library actors

- **Every ad, not one per creative.** The Ad Library lists one ad for each group that shares a creative and text ("N ads use this creative") and counts them all: Shopify shows ~5,700 results behind ~1,750 groups. Most actors in this category page the list and stop at what it shows — the two biggest, curious_coder and igolaizola, open every group and deliver 5,597 (measured 2026-09-20, both, identical sets). This actor opens every group too (`expandVariants`, on by default), walks an advertiser in the order that does not repeat itself, and continues past the source's cursor limit in media and platform slices. Switch `expandVariants` off for the creative-level view: one row per group with `collation_count`.
- **Typed columns for the figures that matter, not a nested node to unpick.** Ten actors already flatten Meta's record; the three most-used hand back the raw GraphQL node. What none of them types: the EU reach as `eu_total_reach`, the country × age × gender breakdown as a list, the audience as `eu_audience_age_min` … `eu_audience_locations`, the payer and beneficiary; the political ranges as `spend_min`/`spend_max`, `impressions_min`/`impressions_max`, `reach_min`/`reach_max` with the currency and `paid_for_by`. Carousel and dynamic creatives keep every card in `cards` and put the first one's copy in the top-level columns, so a text column is never blank when the creative has words somewhere. Meta's own flags are columns too: `removal_reason` for an ad Meta took down, `digitally_created_media` for its AI label, `partner_page_name` for a partnership ad.
- **What the ad says and shows, in the same row.** `transcribeVideos` turns the speech into `transcript`, its language, the first three seconds (`hook_3s`) and `speech_seconds` — a music-only video is checked and not billed. A video longer than 3 minutes is transcribed for its first 3 minutes. `ocrImageAds` reads the words baked into the artwork with a vision model. `analyzeAds` returns a typed read: hook, offer, angle, audience, CTA intent, tone, sentiment. `enrichLandingPage` follows the link to the page it lands on and reads its product name, price and currency. In this category those are four separate actors (steadyfetch, dltik, apify's e-commerce event, automation-lab's resolver), each with its own bill.
- **Monitoring that tells you what ended.** `onlyNewAds` skips every creative earlier runs delivered — not returned, not charged. Every row carries `first_seen_at` and `last_seen_at` from the same memory, and `includeEndedAds` adds an `ended` row for each creative a complete scan no longer finds. The memory is named after the targets, or after your `stateStoreName`.
- **Honest counts, and the limits named.** The status of every run says what was delivered next to what the page claimed, and why a walk stopped: the source's cursor limit (about 1,900 creatives in one order, measured), a filter that excluded everything, a spending cap. Nothing is presented as complete that is not.
- **A run that stops can be continued.** A checkpoint is written after every page. Resurrect a timed-out run, or pass its id as `resumeFromRunId`, and it carries on from the cursor with no creative delivered or billed twice and `maxAds` counted across both.
- **Files that outlive Meta's links.** `image_url` and `video_url` expire in about four days. `downloadMedia` copies the files into a key-value store you own, keyed by creative, and links them from `media_files`; a creative met again is linked to its existing file, not fetched or billed twice.
- **The proxies are ours.** The Ad Library limits datacenter IPs on their first request and each residential exit after a few hundred; this actor carries its own rotating residential exits — a walker per target and a pool for the details and variant calls — and moves to a fresh one the moment the source pushes back, resuming the same cursor. Until 4 November 2026 a run bills per row only; from 5 November 2026 it also pays a $0.01 start fee (see Pricing).
- **Fast where it counts.** 500 Shopify ads with details in 103 s, against 122 s for curious_coder and 297 s for igolaizola on the same input the same afternoon (2026-09-20); three advertisers walked in parallel, each on its own exit.
- **The same columns as our Google Ads Transparency Scraper.** `advertiser_id`, `creative_id`, `headline`, `body_text`, `destination_url`, `cta`, `first_shown`, `last_shown`, `days_shown`, `image_url`, `spend_min`, `region`, `ad_url` mean the same thing in both. Schedule both against a competitor and one sheet holds their Google and Meta advertising side by side.

### Same competitor, other ad platforms

Our ad library scrapers share their column names — `advertiser_name`, `body_text`, `cta`, `destination_url`, `first_shown`, `last_shown`, `days_shown`, `regions` and more — so one competitor's ads from every platform land in one table with no mapping.

- **[Google Ads Transparency Scraper](https://apify.com/hyperbach/google-ads-transparency-scraper)** — every ad an advertiser runs on Google Search, YouTube and Display, with the copy read from image ads, run dates and, for political ads, spend.
- **[LinkedIn Ad Library Scraper](https://apify.com/hyperbach/linkedin-ad-library-scraper)** — every ad a company runs on LinkedIn, with the full detail page: payer, impressions by country and the targeting the advertiser chose.
- **[TikTok Ads Library Scraper](https://apify.com/hyperbach/tiktok-ad-library-scraper)** — every ad an advertiser runs in TikTok's Ad Library, with targeting, reach per country, payer and landing page.
- **[TikTok Creative Center Scraper](https://apify.com/hyperbach/tiktok-creative-center-scraper)** — TikTok's top ads by country and period with CTR and retention curves, plus trending hashtags and videos.

### Who it's for

- **Performance marketers & agencies** — read a competitor's live creatives — the copy, the offer, the landing page, the formats they are testing — and how long each has been running.
- **Creative strategists & copywriters** — a corpus of ad copy that is proven in market, with run dates showing which messages survived, instead of a swipe file nobody knows performed.
- **Founders sizing up a market** — who is paying to acquire customers in a niche, on which platforms, with what positioning — evidence of a market with money in it, before you spend any.
- **Researchers, journalists & compliance teams** — political and issue advertising with spend, impressions, reach and the funding entity, and the EU transparency figures Meta publishes per ad.

### Quick start

**Every active ad one page runs**

```json
{
  "advertiserIds": [
    "20409006880"
  ],
  "region": "ALL",
  "maxAds": 100
}
```

**With EU reach and audience per ad**

```json
{
  "advertiserIds": [
    "20409006880"
  ],
  "includeEuReach": true,
  "maxAds": 50
}
```

**Political ads about a topic, with spend**

```json
{
  "searchTerms": [
    "election"
  ],
  "adType": "political_and_issue",
  "region": "US",
  "maxAds": 50
}
```

**Monitoring — only what appeared since the last run**

```json
{
  "advertiserIds": [
    "20409006880"
  ],
  "onlyNewAds": true
}
```

### Switching from `apify/facebook-ads-scraper` or `curious_coder/facebook-ads-library-scraper`?

Your input works as it is. Each name in the left column is read as this Actor's own field and the run log says so (`read <theirs> as <ours>`); a name marked *not read* is ignored with a note. A value in the other Actor's shape, an object list or its own date form, is read too.

| apify/facebook-ads-scraper / curious_coder/facebook-ads-library-scraper | this Actor | what to know |
|---|---|---|
| `startUrls` | `startUrls` | The same name and shape: page URLs and Ad Library URLs. |
| `resultsLimit` | `maxAdsPerTarget` | Ads per URL, page or keyword. |
| `onlyTotal` | *not read* | Read as `resultType: counts` (the run log says so). |
| `includeAboutPage` | `includePageTransparency` | Also read as `includeAdvertiserInfo`. |
| `isDetailsPerAd` | `includeEuReach` | The per-ad details: EU reach and audience. |
| `activeStatus` | `activeStatus` | The same name; empty = `active`. |
| `sorting` | `sortBy` | The leaders' sort names are read. |
| `onlyAdsNewerThan` | `startDate` | A date, or a span such as `30 days`. |
| `onlyAdsOlderThan` | `endDate` | A date, or a span. |
| `enrichWithEcommerceData` | `enrichLandingPage` | The landing page's product name, price and currency. |
| `urls` | `startUrls` | curious_coder's links. |
| `scrapeAdDetails` | `includeEuReach` | As `isDetailsPerAd`. |
| `limitPerSource` | `maxAdsPerTarget` | Ads per URL. |
| `count` | `maxAds` | The whole run's cap. |
| `scrapePageAds.period` | `period` | The same window names (`last7d`, `last30d`). |
| `scrapePageAds.activeStatus` | `activeStatus` | The same values. |
| `scrapePageAds.sortBy` | `sortBy` | `impressions_desc` and `most_recent` are read. |
| `scrapePageAds.countryCode` | `region` | A country code or `ALL`. |
| `runTag` | `runTag` | The same name. |
| `proxy` | *not read* | Not read: the Actor's own residential exits. |

**What differs:**

- Both leaders take a list of `{"url": …}` items (a Facebook page URL, an advertiser's Ad Library URL, a keyword search URL): `startUrls` and `urls` are read as this Actor's `startUrls`, in that shape or as plain strings.
- `onlyTotal: true` is `resultType: counts` (one row per page with the Ad Library's total). `isDetailsPerAd` and `scrapeAdDetails` are `includeEuReach`; `includeAboutPage` is `includePageTransparency` and `includeAdvertiserInfo`.
- The leaders' sort names are read (`total_impressions`, `relevancy_monthly_grouped`, `impressions_desc`, `most_recent`), an empty `activeStatus` is the default (`active`), and a relative date in `onlyAdsNewerThan` (`30 days`) becomes a day. curious_coder's dotted names (`scrapePageAds.period`, `.activeStatus`, `.sortBy`, `.countryCode`) are read as `period`, `activeStatus`, `sortBy` and `region`.
- The cap per URL (`resultsLimit`, `limitPerSource`) is `maxAdsPerTarget`; `count` is `maxAds` (the whole run). `proxy` is not read: the Actor carries its own residential exits.

### Output

One record per ad creative:

| field | meaning |
|---|---|
| `row_type` | `ad` for a creative; `advertiser` for a page resolved from `advertiserNames` / `searchTerms` in `resultType: advertisers`; `count` for a claimed-total row in `resultType: counts`; `advertiser_profile` and `landing_page` for the rows `summaryRows` adds; `ended` for a creative a complete scan no longer returns (`includeEndedAds`). |
| `advertiser_name` | The page's name as the Ad Library shows it. |
| `advertiser_id` | The numeric Facebook page ID behind the ads. Join on this across runs and platforms; it is also what `advertiserIds` takes. |
| `creative_id` | The ad's Library ID — the `id` in an Ad Library URL. Unique per creative; the key for `onlyNewAds`. |
| `collation_id` | Meta groups near-identical creatives (the same ad run in several sizes or placements) under one collation. The group's id. |
| `collation_count` | How many ads share this creative and text — Meta's group ("N ads use this creative"). With `expandVariants` on, every one of them is a row and they share the `collation_id`; off, this row stands for the group. |
| `ad_format` | `image`, `video`, `dco` (a dynamic creative with per-card copy and media), `carousel`, `multi_images` and the other display formats Meta uses, lowercased. |
| `is_active` | Whether the ad was running when the run read it. |
| `headline` | The ad's title. For carousel and dynamic creatives, the first card's title when the top level has none. |
| `body_text` | The primary text. For carousel and dynamic creatives, the first card's body when the top level has none. |
| `link_description` | The description line under the link, when the creative carries one. |
| `headlines` | Every card's title, for carousel and dynamic creatives. Empty on single-card ads. |
| `descriptions` | Every card's body text, for carousel and dynamic creatives. |
| `display_url` | The caption shown under the creative — usually the advertiser's domain. |
| `destination_url` | Where the ad sends a click. |
| `cta` | The call-to-action button's label, e.g. `Sign up`, `Shop now`. |
| `cta_type` | Meta's own CTA code, e.g. `SIGN_UP`, `SHOP_NOW`. |
| `cards` | For carousel and dynamic creatives, one entry per card: `headline`, `body_text`, `link_description`, `display_url`, `destination_url`, `cta`, `cta_type`, `image_url`, `video_url`, `video_preview_url`. |
| `first_shown` | The day the ad started running (UTC). |
| `last_shown` | The day the ad was last seen running — today for an active ad. |
| `days_shown` | Days between `first_shown` and `last_shown`, inclusive. Long-running ads are the ones that earn their budget. |
| `image_url` | The creative's image — the ad's own, or the first card's. A direct CDN link that fetches without a login and expires about four days after the run. |
| `image_urls` | Every distinct image URL the creative carries, in order. |
| `image_small_url` | Meta's ~600 px rendition of the first image — the one `ocrImageAds` reads; `image_url` is the original. |
| `video_url` | The creative's video, HD rendition where there is one. Same expiry as the images. |
| `video_urls` | Every distinct video of the creative, in order (multi-video and carousel creatives carry several). |
| `video_duration_s` | Length of `video_url` in whole seconds, read from the URL's own encode tag (`efg.duration_s`, present on 80 of 80 video ads measured 2026-09-20) — no extra request. Null for image ads. |
| `video_preview_url` | The poster frame of the video. |
| `media_files` | With `downloadMedia`: one entry per file copied into your key-value store — `kind` (image/video), `key`, `url` (the store URL, which does not expire), `bytes`, `content_type`, `source_url`, and `reused` (true when an earlier run already stored it — linked, not billed again). Empty otherwise. |
| `image_file_url` | With `downloadMedia`: the store URL of the first image. Null otherwise. |
| `video_file_url` | With `downloadMedia: all`: the store URL of the first video. Null otherwise. |
| `transcript` | With `transcribeVideos`: what is said in the video, as one text. Null when the video has no speech (then `speech_seconds` is 0) or when it was not transcribed. A video longer than 3 minutes is transcribed for its first 3 minutes. |
| `transcript_language` | With `transcribeVideos`: the spoken language as an ISO 639-1 code (`en`, `fr`, `ko`), as detected. |
| `hook_3s` | With `transcribeVideos`: the words spoken in the first three seconds of speech — the hook — counted from the first word, not from the start of the video. |
| `speech_seconds` | With `transcribeVideos`: seconds of speech in the video. 0 means the video was checked and carries no speech (music or silence); null means it was not transcribed. |
| `ad_ocr_text` | With `ocrImageAds`: the words a vision model read out of the creative's image (or a video's poster frame), in reading order. Null when the image carries no readable text or was not read. |
| `sentiment_score` | On every row, free: the mean AFINN-165 valence of the English words in the copy, scaled to -1 (negative) .. 1 (positive). Null when the copy has no scored word — a non-English ad is unscored, not neutral. |
| `analysis_hook` | With `analyzeAds`: the line meant to stop the scroll, quoted or closely paraphrased from the ad. |
| `analysis_offer` | With `analyzeAds`: the concrete thing on offer — product, discount, trial, free item — or null when the ad makes no specific offer. |
| `analysis_angle` | With `analyzeAds`: the persuasion angle — `problem_solution`, `social_proof`, `scarcity`, `discount`, `feature`, `lifestyle`, `curiosity`, `education`, `comparison`, `brand` or `other`. |
| `analysis_audience` | With `analyzeAds`: who the ad is written for, in the model's words (e.g. `small retailers opening a second location`). |
| `analysis_cta_intent` | With `analyzeAds`: what the ad wants done — `purchase`, `signup`, `download`, `learn_more`, `contact`, `visit`, `watch`, `apply`, `book` or `other`. |
| `analysis_tone` | With `analyzeAds`: `playful`, `urgent`, `professional`, `friendly`, `luxurious`, `informative`, `emotional`, `bold` or `other`. |
| `analysis_sentiment` | With `analyzeAds`: the model's read of the copy's emotional valence, -1 .. 1. |
| `landing_final_url` | With `enrichLandingPage`: the page the ad's link lands on after redirects, with tracking parameters (utm\_\*, fbclid, gclid …) stripped. Null when nothing answered. |
| `landing_domain` | With `enrichLandingPage`: the final page's domain, without `www.`. |
| `landing_status` | With `enrichLandingPage`: the HTTP status the final page answered (200, 404, 403 …); null when the site did not answer at all. |
| `landing_title` | With `enrichLandingPage`: the page's title (Open Graph title first, then `<title>`). |
| `landing_is_product` | With `enrichLandingPage`: true when the page declares itself a product (JSON-LD `Product`, `og:type` product, or a price in its markup); false for other pages; null when the page did not answer. |
| `landing_product_name` | With `enrichLandingPage`: the product's name from JSON-LD, or the page title on a product page. |
| `landing_price` | With `enrichLandingPage`: the price the page publishes for machines (JSON-LD offers, `product:price:amount`), as shown to the run's location — a storefront that localises by visitor answers in that country's currency. Null when the page publishes none. |
| `landing_currency` | With `enrichLandingPage`: the ISO currency of `landing_price`. |
| `platforms` | Where the ad ran: `facebook`, `instagram`, `messenger`, `whatsapp`, `audience_network`, `threads`. |
| `launched_last_7_days` | Whether the ad started running within the last 7 days of the run. |
| `launched_last_30_days` | Whether the ad started running within the last 30 days of the run. |
| `long_running` | Whether the ad has been shown for at least `longRunningDays` (default 30) — the creatives that survive are the ones that convert. |
| `platform_count` | How many Meta platforms the ad ran on. |
| `categories` | Meta's categories for the ad — `political` for ads about social issues, elections or politics; `unknown` for ordinary commercial ads. |
| `regions` | Countries the ad targeted or reached, when Meta lists them (political ads). |
| `region` | The country the run asked for — a code, `ALL`, or a comma-separated list when `regions` held several. |
| `spend_min` | Lower bound of the spend range Meta publishes. Political and issue ads only; null on commercial ads. |
| `spend_max` | Upper bound of the spend range; null when the range is open-ended (`>$1M`). |
| `spend_currency` | ISO currency of the spend range. |
| `impressions_min` | Lower bound of the impressions range. Political and issue ads; on a commercial ad, only the `<100` band Meta shows in its first hours (never billed as political). |
| `impressions_max` | Upper bound of the impressions range; null when open-ended (`>1M`). |
| `reach_min` | Lower bound of the estimated reach range. Political and issue ads only. |
| `reach_max` | Upper bound of the estimated reach range; null when open-ended. |
| `paid_for_by` | The disclaimer's funding entity on a political or issue ad. |
| `funding_entity_website` | The funding entity's website, from Meta's verification record. |
| `funding_entity_address` | The funding entity's address, from Meta's verification record. |
| `eu_total_reach` | Accounts reached across the EU, as published under the DSA. Filled by `includeEuReach` on ads that reached the EU; null on the rest. |
| `eu_reach_breakdown` | Reach per EU country, age range and gender: entries of `country`, `age_range`, `male`, `female`, `unknown`. Filled by `includeEuReach`. |
| `eu_audience_age_min` | The youngest age the advertiser targeted in the EU. |
| `eu_audience_age_max` | The oldest age the advertiser targeted in the EU. |
| `eu_audience_gender` | The gender the advertiser targeted in the EU: `All`, `Men` or `Women`. |
| `eu_audience_locations` | The locations the advertiser included or excluded in the EU: entries of `name`, `excluded`, `type`. |
| `payer` | Who paid for the ad, as disclosed under the DSA. Filled by `includeEuReach`. |
| `beneficiary` | Who benefits from the ad, as disclosed under the DSA. |
| `political_reach_by_region` | For political ads with `includeEuReach`: the share of reach per region, entries of `region` and `reach_share` (0–1), regions with reach only. |
| `political_reach_by_age_gender` | For political ads with `includeEuReach`: the share of reach per age range and gender. |
| `advertiser_category` | The page's category, e.g. `Software`. Filled by `includeAdvertiserInfo` or `includeEuReach`. |
| `advertiser_about` | The page's About text. Filled by `includeAdvertiserInfo` or `includeEuReach`. |
| `advertiser_categories` | The page's categories as the Ad Library lists them, e.g. `["Software"]`. |
| `advertiser_ads_seen` | On an `advertiser` row: how many ads of the name search belonged to this page. |
| `advertiser_match` | How the page matched the brand name it was found by — `exact`, `starts`, `contains`, `domain` (the name was a domain and the page's ads link to it), `page_url` (read from the page a pasted URL names) or `none` (its ads only mention the name). Set on `advertiser` rows and on every ad and summary row of a page found by `advertiserNames` or a pasted page URL; null when you gave the page id. |
| `advertiser_claimed_ads` | On an ad row from a page target: the total the Ad Library page claims for that advertiser — ads, not creative groups. With `expandVariants` on, the delivered count comes close; the status says what the source's cursor limit cut (see the FAQ). |
| `claimed_count` | On a `count` row: the total the Ad Library shows for the target, from one page load. |
| `count_note` | On a `count` row whose `claimed_count` is empty: why there is no number (the Ad Library stated no total, or did not answer). An empty count with a note is unknown, not zero. |
| `summary_ad_count` | On a summary row: how many delivered ads it covers. |
| `summary_active_count` | On an `advertiser_profile` row: how many of them are active. |
| `summary_longest_days` | On an `advertiser_profile` row: the longest `days_shown` among them. |
| `summary_formats` | On an `advertiser_profile` row: `{format, count}` per ad format, most common first. |
| `summary_landing_domains` | Distinct landing-page domains behind the ads. |
| `summary_distinct_copies` | On an `advertiser_profile` row: how many distinct primary texts the ads use. |
| `summary_ad_ids` | On a summary row: the Library IDs of the ads it covers. |
| `advertiser_likes` | The page's like count. |
| `advertiser_ig_username` | The page's linked Instagram handle. Filled by `includeAdvertiserInfo` or `includeEuReach`. |
| `advertiser_ig_followers` | Followers of the linked Instagram account. |
| `advertiser_verified` | Whether the page carries Meta's verification badge. |
| `advertiser_spend_total` | For political pages: lifetime spend across the page's disclaimers, as Meta reports it. Filled by `includeAdvertiserInfo` or `includeEuReach`. |
| `advertiser_spend_week` | For political pages: spend in the last seven days. |
| `advertiser_url` | The page's Facebook URL. |
| `advertiser_picture_url` | The page's profile picture. |
| `ad_url` | The ad in the Ad Library, for a human to open. |
| `matched_query` | The keyword or Library ID that produced this row; null on rows from a page ID. |
| `matched_fields` | On a keyword row: which of the ad's texts contain the whole search term — `headline`, `body_text`, `link_description`, `destination_url`, `display_url`, `advertiser_name`, `cards`. Empty means the Library matched it loosely; `keywordRelevance: visible` drops those. |
| `first_seen_at` | With a monitoring memory: the day a run of these targets first saw the creative. |
| `last_seen_at` | With a monitoring memory: the day a run last saw it — today on a live row. |
| `ended_at` | On an `ended` row: the day a complete scan first failed to return the creative. |
| `run_tag` | The `runTag` you gave the run, on every row of every type — so rows from several runs sort in one sheet. Null when none was given. |
| `raw` | Meta's own record, untouched — only with `includeRawData`. |
| `extra_texts` | Other texts Meta lists on the creative: a lead form's questions, a second offer line. |
| `extra_links` | Other links Meta lists on the creative, e.g. a lead form's privacy policy. |
| `removal_reason` | Null for an ad Meta lets run. Otherwise why Meta stopped it: `taken_down` (removed for breaking its rules), `ad_account_disabled`, `missing_disclaimer` (a political ad without its "Paid for by"). Spot the competitors whose ads get pulled. |
| `digitally_created_media` | Meta's "digitally created or altered" label: the media was made or changed with AI or other digital tools. |
| `sensitive_content` | Meta marks the creative as sensitive content (shown behind a warning). |
| `page_is_deleted` | The advertiser's page has been deleted since the ad ran. |
| `financial_services_ad` | Meta classes the ad as a financial-services ad under its regional rules. |
| `state_media_label` | Meta's state-controlled media label on the ad, when it carries one. |
| `partner_page_name` | A partnership (branded content) ad: the creator or brand page it runs with. |
| `partner_page_id` | The partner page's id. |
| `partner_page_url` | The partner page's Facebook URL. |
| `advertiser_entity_type` | What kind of Facebook presence runs the ads, as Meta states it, e.g. `PERSON_PROFILE`. Filled by `includeAdvertiserInfo` or `includeEuReach`. |
| `advertiser_active_ads` | How many ads the page is running now in the run's region, all formats and platforms — the total the Ad Library shows on the page. Filled by `includeTotalActiveAds`; free on every row of a page target that no filter narrows. |
| `page_created` | The day the advertiser's Facebook page was created. Filled by `includePageTransparency`, like the next eight. |
| `page_name_changes` | Every rename of the page, newest first: `{name, date}`, the name it took on that day. Empty when it never changed. |
| `page_admin_countries` | Where the people who manage the page are: `{country, admins}`, most admins first. |
| `page_owner` | The confirmed owner Meta names for the page, e.g. `Shopify Inc.`. |
| `page_owner_location` | The confirmed owner's location as Meta states it. |
| `page_owner_type` | How the owner was confirmed, e.g. `verified_business_manager`. |
| `page_partners` | Confirmed partner organisations that help manage the page. |
| `page_ran_political_ads` | Whether the page has ever run ads about social issues, elections or politics. |
| `page_state_media_label` | The country whose state controls the page, when Meta labels it state media. |

Example record:

```json
{
  "row_type": null,
  "advertiser_name": "Shopify",
  "advertiser_id": "20409006880",
  "creative_id": "1657481092030542",
  "collation_id": "1308187987545965",
  "collation_count": 1,
  "ad_format": "dco",
  "is_active": true,
  "headline": "{{product.name}}",
  "body_text": "{{product.brand}}",
  "link_description": "{{product.description}}",
  "headlines": [
    "Vendez gratuitement sur Shopify",
    "Jetzt kostenlos auf Shopify verkaufen"
  ],
  "descriptions": [
    "Obtenez le bouton que tout le monde adore. Commencez à vendre et à gagner de l’argent avec Shopify 💰",
    "Der Button, den die Leute kennen und lieben. Jetzt verkaufen und mit Shopify verdienen 💰"
  ],
  "display_url": "shopify.com",
  "destination_url": "https://www.shopify.com/se/gratis-trial",
  "cta": "Sign up",
  "cta_type": "SIGN_UP",
  "cards": [
    {
      "headline": "Vendez gratuitement sur Shopify",
      "body_text": "Obtenez le bouton que tout le monde adore. Commencez à vendre et à gagner de l’argent avec Shopify 💰",
      "link_description": null,
      "display_url": "shopify.com",
      "destination_url": "https://www.shopify.com/fr/essai-gratuit",
      "cta": "Sign Up",
      "cta_type": "SIGN_UP",
      "image_url": null,
      "video_url": "https://video-mxp2-1.xx.fbcdn.net/o1/v/t2/f2/m366/AQOMRBFZd7QAA0ruNoYgN6JXjAiwMNc_Yicgl9hZIh8lOlORAPEtR50aqEB4yfQkm6GjRg_8HrdX7-RYLqyVQCzvuyhfySfpOGVKhVn39EzgmQ.mp4?_nc_cat=101&_nc_sid=b66105&_nc_ht=video-mxp2-1.xx.fbcdn.net&_nc_ohc=dEUkWdMHDwYQ7kNvwHiJ8XN&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5WSV9VU0VDQVNFX1BST0RVQ1RfVFlQRS4uQzMuNzIwLmRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHAiLCJ4cHZfYXNzZXRfaWQiOjgxNTc1MTk4NzU2ODUxNSwiYXNzZXRfYWdlX2RheXMiOjM1OSwidmlfdXNlY2FzZV9pZCI6MTAxMzksImR1cmF0aW9uX3MiOjEyLCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&vs=257c57522b2c310e&_nc_vs=HBksFQIYRWZiX2VwaGVtZXJhbC9CODQzMDVENjk4MkMzOThBMjBFRTg0MzRBMkUyRDg5N19tdF8xX3ZpZGVvX2Rhc2hpbml0Lm1wNBUAAsgBEgAVAhhAZmJfcGVybWFuZW50LzExNEI1QjA2OTQzNEMyQzVCMjVGODRERUJCQjJFQjhEX2F1ZGlvX2Rhc2hpbml0Lm1wNBUCAsgBEgAoABgAGwKIB3VzZV9vaWwBMRJwcm9ncmVzc2l2ZV9yZWNpcGUBMRUAACaG_oz3gfvyAhUCKAJDMywXQCgAAAAAAAAYGWRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHARAHUAZbaeAQA&_nc_gid=CXhKjE0I-jAPsnY2HWAufw&_nc_ss=7e289&_nc_zt=28&oh=00_AQKP-fMbUXalHsS9wXs3LMR-_48s_pNZB8-Q4gsPXfn6Nw&oe=6AB52807",
      "video_preview_url": "https://scontent-mxp1-1.xx.fbcdn.net/v/t39.35426-6/710447423_2716333165389931_221950439945272582_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=c53f8f&_nc_ohc=Iq_OjuwglZMQ7kNvwFbTh7K&_nc_oc=Adpm8fPUfnukFoj49quE74I_yce8YnDZH9bV7imwtVDPiUtTJFYVW9EdKEwXNHklqYY&_nc_zt=14&_nc_ht=scontent-mxp1-1.xx&_nc_gid=CXhKjE0I-jAPsnY2HWAufw&_nc_ss=7e289&oh=00_AQI43eq2bcH5RMsal09q_k9dBmEqZ4ebUy7tpCOHT7Yc9Q&oe=6AB51813"
    },
    {
      "headline": "Jetzt kostenlos auf Shopify verkaufen",
      "body_text": "Der Button, den die Leute kennen und lieben. Jetzt verkaufen und mit Shopify verdienen 💰",
      "link_description": null,
      "display_url": "shopify.com",
      "destination_url": "https://www.shopify.com/de/kostenloser-test",
      "cta": "Sign Up",
      "cta_type": "SIGN_UP",
      "image_url": null,
      "video_url": "https://video-mxp1-1.xx.fbcdn.net/o1/v/t2/f2/m366/AQMzymmwQZ2QnkOpNdfkNUDVtUuEF_DCKrHfcjYtpPp0SVWyHsjV3IHAZNshaq4BvtK4tZKod6N2t8ZH1me2ehX7_coU5YYzbFp7Fqetyh3Fng.mp4?_nc_cat=107&_nc_sid=b66105&_nc_ht=video-mxp1-1.xx.fbcdn.net&_nc_ohc=ZQvNX5y0_1gQ7kNvwFWdBMd&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5WSV9VU0VDQVNFX1BST0RVQ1RfVFlQRS4uQzMuNzIwLmRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHAiLCJ4cHZfYXNzZXRfaWQiOjEyNTgyNDg1NDI3NDA4NjUsImFzc2V0X2FnZV9kYXlzIjozNTksInZpX3VzZWNhc2VfaWQiOjEwMTM5LCJkdXJhdGlvbl9zIjoxMiwidXJsZ2VuX3NvdXJjZSI6Ind3dyJ9&ccb=17-1&vs=70346b3d005c775a&_nc_vs=HBksFQIYRWZiX2VwaGVtZXJhbC9BMTQ4Mzk4RkE3M0IwMkVBNDgzNjRCODA5MDZFNjBCMV9tdF8xX3ZpZGVvX2Rhc2hpbml0Lm1wNBUAAsgBEgAVAhhAZmJfcGVybWFuZW50LzlGNDlFMUVFMDhFNzFEQzAyNEUzQzIyMDI4RDcxOTkxX2F1ZGlvX2Rhc2hpbml0Lm1wNBUCAsgBEgAoABgAGwKIB3VzZV9vaWwBMRJwcm9ncmVzc2l2ZV9yZWNpcGUBMRUAACaChu-W2pe8BBUCKAJDMywXQCgAAAAAAAAYGWRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHARAHUAZbaeAQA&_nc_gid=CXhKjE0I-jAPsnY2HWAufw&_nc_ss=7e289&_nc_zt=28&oh=00_AQLzjp0zQpdcDwTblT9m2eHv5WFYDx4hLzEUSMmU_TpnjA&oe=6AB5139D",
      "video_preview_url": "https://scontent-mxp1-1.xx.fbcdn.net/v/t39.35426-6/711695694_2404378190031495_3175246454924053399_n.jpg?_nc_cat=102&ccb=1-7&_nc_sid=c53f8f&_nc_ohc=8n96Gr_0IrEQ7kNvwHWtXgQ&_nc_oc=AdqVInXxe25_YfyFpmh849bsCEOj61j5_c_9sTORV1Zd9ZUVJu5bB12kx7TKh-PcXzo&_nc_zt=14&_nc_ht=scontent-mxp1-1.xx&_nc_gid=CXhKjE0I-jAPsnY2HWAufw&_nc_ss=7e289&oh=00_AQKyNhZtD7LZhNeGGIbt0hnRLCgoPYbTXKH6-TCNdN5L9w&oe=6AB5359C"
    }
  ],
  "first_shown": "2026-06-03",
  "last_shown": "2026-09-19",
  "days_shown": 109,
  "image_url": null,
  "image_urls": [],
  "image_small_url": null,
  "video_url": "https://video-mxp2-1.xx.fbcdn.net/o1/v/t2/f2/m366/AQOMRBFZd7QAA0ruNoYgN6JXjAiwMNc_Yicgl9hZIh8lOlORAPEtR50aqEB4yfQkm6GjRg_8HrdX7-RYLqyVQCzvuyhfySfpOGVKhVn39EzgmQ.mp4?_nc_cat=101&_nc_sid=b66105&_nc_ht=video-mxp2-1.xx.fbcdn.net&_nc_ohc=dEUkWdMHDwYQ7kNvwHiJ8XN&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5WSV9VU0VDQVNFX1BST0RVQ1RfVFlQRS4uQzMuNzIwLmRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHAiLCJ4cHZfYXNzZXRfaWQiOjgxNTc1MTk4NzU2ODUxNSwiYXNzZXRfYWdlX2RheXMiOjM1OSwidmlfdXNlY2FzZV9pZCI6MTAxMzksImR1cmF0aW9uX3MiOjEyLCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&vs=257c57522b2c310e&_nc_vs=HBksFQIYRWZiX2VwaGVtZXJhbC9CODQzMDVENjk4MkMzOThBMjBFRTg0MzRBMkUyRDg5N19tdF8xX3ZpZGVvX2Rhc2hpbml0Lm1wNBUAAsgBEgAVAhhAZmJfcGVybWFuZW50LzExNEI1QjA2OTQzNEMyQzVCMjVGODRERUJCQjJFQjhEX2F1ZGlvX2Rhc2hpbml0Lm1wNBUCAsgBEgAoABgAGwKIB3VzZV9vaWwBMRJwcm9ncmVzc2l2ZV9yZWNpcGUBMRUAACaG_oz3gfvyAhUCKAJDMywXQCgAAAAAAAAYGWRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHARAHUAZbaeAQA&_nc_gid=CXhKjE0I-jAPsnY2HWAufw&_nc_ss=7e289&_nc_zt=28&oh=00_AQKP-fMbUXalHsS9wXs3LMR-_48s_pNZB8-Q4gsPXfn6Nw&oe=6AB52807",
  "video_urls": null,
  "video_duration_s": null,
  "video_preview_url": "https://scontent-mxp1-1.xx.fbcdn.net/v/t39.35426-6/710447423_2716333165389931_221950439945272582_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=c53f8f&_nc_ohc=Iq_OjuwglZMQ7kNvwFbTh7K&_nc_oc=Adpm8fPUfnukFoj49quE74I_yce8YnDZH9bV7imwtVDPiUtTJFYVW9EdKEwXNHklqYY&_nc_zt=14&_nc_ht=scontent-mxp1-1.xx&_nc_gid=CXhKjE0I-jAPsnY2HWAufw&_nc_ss=7e289&oh=00_AQI43eq2bcH5RMsal09q_k9dBmEqZ4ebUy7tpCOHT7Yc9Q&oe=6AB51813",
  "media_files": null,
  "image_file_url": null,
  "video_file_url": null,
  "transcript": null,
  "transcript_language": null,
  "hook_3s": null,
  "speech_seconds": null,
  "ad_ocr_text": null,
  "sentiment_score": null,
  "analysis_hook": null,
  "analysis_offer": null,
  "analysis_angle": null,
  "analysis_audience": null,
  "analysis_cta_intent": null,
  "analysis_tone": null,
  "analysis_sentiment": null,
  "landing_final_url": null,
  "landing_domain": null,
  "landing_status": null,
  "landing_title": null,
  "landing_is_product": null,
  "landing_product_name": null,
  "landing_price": null,
  "landing_currency": null,
  "platforms": [
    "facebook",
    "instagram",
    "messenger"
  ],
  "launched_last_7_days": null,
  "launched_last_30_days": null,
  "long_running": null,
  "platform_count": null,
  "categories": [
    "unknown"
  ],
  "regions": [],
  "region": "ALL",
  "spend_min": null,
  "spend_max": null,
  "spend_currency": null,
  "impressions_min": null,
  "impressions_max": null,
  "reach_min": null,
  "reach_max": null,
  "paid_for_by": null,
  "funding_entity_website": null,
  "funding_entity_address": null,
  "eu_total_reach": 11900089,
  "eu_reach_breakdown": [
    {
      "country": "HU",
      "age_range": "55-64",
      "male": 9965,
      "female": 9552,
      "unknown": 165
    },
    {
      "country": "HU",
      "age_range": "45-54",
      "male": 28878,
      "female": 15165,
      "unknown": 539
    },
    {
      "country": "HU",
      "age_range": "Unknown",
      "male": 85,
      "female": 15,
      "unknown": 3
    }
  ],
  "eu_audience_age_min": 18,
  "eu_audience_age_max": 65,
  "eu_audience_gender": "All",
  "eu_audience_locations": [
    {
      "name": "Guadeloupe",
      "excluded": false,
      "type": "countries"
    },
    {
      "name": "Aland Islands",
      "excluded": false,
      "type": "countries"
    }
  ],
  "payer": "Shopify",
  "beneficiary": "Shopify",
  "political_reach_by_region": [],
  "political_reach_by_age_gender": [],
  "advertiser_category": "Software",
  "advertiser_about": null,
  "advertiser_categories": null,
  "advertiser_ads_seen": null,
  "advertiser_match": null,
  "advertiser_claimed_ads": null,
  "claimed_count": null,
  "count_note": null,
  "summary_ad_count": null,
  "summary_active_count": null,
  "summary_longest_days": null,
  "summary_formats": null,
  "summary_landing_domains": null,
  "summary_distinct_copies": null,
  "summary_ad_ids": null,
  "advertiser_likes": 4538277,
  "advertiser_ig_username": "shopify",
  "advertiser_ig_followers": 2670355,
  "advertiser_verified": true,
  "advertiser_spend_total": null,
  "advertiser_spend_week": null,
  "advertiser_url": "https://www.facebook.com/shopify/",
  "advertiser_picture_url": "https://scontent.fqrc1-1.fna.fbcdn.net/v/t39.30808-1/532226807_1206379304854792_3907785642549358122_n.jpg?stp=dst-jpg_s148x148_tt6&_nc_cat=1&ccb=1-7&_nc_sid=418b77&_nc_ohc=yojCiHRPRY4Q7kNvwGR-8_b&_nc_oc=Adp5zqArc6xx9hsnNw2-EYHw4eLMMZ49xkrwMX29M0tmUmkCHREDl7smamLE02Cskg4&_nc_zt=24&_nc_ht=scontent.fqrc1-1.fna&_nc_gid=seJgawSzPhkdwcP5u-9nbA&_nc_ss=7e289&oh=00_AQKgCRxis0r2Mjv2KY7tp0qyvceiUdbG2025-XHTVxgm5A&oe=6AB52A47",
  "ad_url": "https://www.facebook.com/ads/library/?id=1657481092030542",
  "matched_query": null,
  "matched_fields": null,
  "first_seen_at": null,
  "last_seen_at": null,
  "ended_at": null,
  "run_tag": null,
  "raw": null,
  "extra_texts": null,
  "extra_links": null,
  "removal_reason": null,
  "digitally_created_media": null,
  "sensitive_content": null,
  "page_is_deleted": null,
  "financial_services_ad": null,
  "state_media_label": null,
  "partner_page_name": null,
  "partner_page_id": null,
  "partner_page_url": null,
  "advertiser_entity_type": null,
  "advertiser_active_ads": null,
  "page_created": null,
  "page_name_changes": null,
  "page_admin_countries": null,
  "page_owner": null,
  "page_owner_location": null,
  "page_owner_type": null,
  "page_partners": null,
  "page_ran_political_ads": null,
  "page_state_media_label": null
}
```

### Pricing

**Pay for what a run actually returns.** Prices are per 1,000. The Free and Starter plans pay the
same; Scale pays 15% less and Business 25% less, on every fee but image text and the start fee.

**Every run also pays a start fee** (since 5 November 2026) of $0.01 per GB of its memory, charged once
when the run starts: $0.01 at the default 1 GB. It pays for the fixed part of a run, the container's
start and the first page load, which a run pays even when its advertisers have no ads.

| per 1,000 | Free | Starter | Scale | Business |
|---|---|---|---|---|
| Ad creative — copy, link, CTA, media URLs, run dates, platforms | $1.00 | $1.00 | $0.85 | $0.75 |
| EU reach and audience, when the ad reached the EU (`includeEuReach`) | +$0.80 | +$0.80 | +$0.68 | +$0.60 |
| Political spend, impressions and reach, when Meta published them | +$1.00 | +$1.00 | +$0.85 | +$0.75 |
| Advertiser (page) resolved from a name (`resultType: advertisers`) | $2.00 | $2.00 | $1.70 | $1.50 |
| Count row (`resultType: counts`) | $10.00 | $10.00 | $8.50 | $7.50 |
| Creative file copied into your store (`downloadMedia`), per file written | $4.00 | $4.00 | $3.40 | $3.00 |
| Video transcript, when the video has speech (`transcribeVideos`) | +$30.00 | +$30.00 | +$25.50 | +$22.50 |
| Image text read by a vision model, when words were found (`ocrImageAds`), model usage included | +$1.00 | +$1.00 | +$1.00 | +$1.00 |
| Typed analysis — hook, offer, angle, audience, CTA intent, tone, sentiment (`analyzeAds`), model usage included | +$10.00 | +$10.00 | +$8.50 | +$7.50 |
| Landing page resolved and read, when the page answered (`enrichLandingPage`) | +$5.00 | +$5.00 | +$4.25 | +$3.75 |

Each row fee is charged **per row that actually carries the thing**. A commercial ad is never
billed the political fee; an ad that never reached the EU is never billed the EU fee; a
repeat the source served twice is never returned, so never billed; a file an earlier run
already stored is linked, not billed again; a music-only video is checked and not billed. `includeAdvertiserInfo` is one request per
advertiser and is not billed separately. Enterprise plans have their own rates — the
Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Follow a competitor weekly** — Put their page ID in `advertiserIds`, tick `onlyNewAds`, save it as a task and schedule it. Each run returns only creatives the earlier runs did not — the first run delivers the current set, every later run only what appeared since. Connect the task to Slack or email through Apify's integrations and the new ads land where you read.
- **Read what they dropped** — `activeStatus: inactive` returns the ads a page has stopped running. Next to the active set, with `first_shown` and `last_shown`, it is the history of what they tried and for how long.
- **Compare against their Google ads** — Run the same competitor through our Google Ads Transparency Scraper. The two outputs share `advertiser_name`, `headline`, `body_text`, `destination_url`, `cta`, `first_shown`, `last_shown` and `days_shown`, so one sheet shows both platforms.
- **Read what the creatives say** — `transcribeVideos`, `ocrImageAds` and `analyzeAds` together turn a competitor's feed into a table you can pivot: what each video says in its first three seconds, what the artwork promises, and a typed read — angle, offer, audience, CTA intent, tone — per ad. Add `enrichLandingPage` for the page each ad sells, with its price.
- **Measure a political campaign** — `adType: political_and_issue` with a keyword or a page ID, `region` set to the country, and `includeEuReach` for the demographic and regional reach shares. Spend, impressions and reach arrive as the ranges Meta publishes.
- **Size a competitor before you scrape** — `resultType: counts` with page IDs or brand names returns one row per page with the total the Ad Library shows for it (`claimed_count`) — from a single page load, no ads scraped, a cent per row. Run it first to see whether a competitor has 60 ads or 6,000, or schedule it weekly and watch the number move. Ready-made: [Count how many Meta ads each competitor runs](https://apify.com/hyperbach/meta-ads-library-scraper/examples/count-a-competitors-meta-ads).
- **What they launched between two dates** — `launchedFrom` and `launchedTo` keep only the ads that started in that window — a competitor's Black Friday launches, or everything new last month. `startDate` and `endDate` answer a different question: every ad that was running at some point in the window, including one that started two years ago. `period` is the same launch filter as a preset (`last7d`, `last30d`).
- **Vet an advertiser** — `includePageTransparency` adds Facebook's Page transparency facts to every row: when the page was created, every name it had before, how many admins sit in which country, and the confirmed owner. A page created last month, renamed twice and run from another country reads differently from a ten-year-old brand. `removal_reason` shows which of its ads Meta took down, and `includeTotalActiveAds` how many ads it runs right now.
- **Keep the media** — `image_url` and `video_url` are direct links to Meta's CDN and fetch without a login; they expire about four days after the run. Set `downloadMedia: all` and the files are copied into a key-value store of yours, linked from `media_files`, `image_file_url` and `video_file_url` — no expiry, and a creative met again on a later run is linked, not fetched twice.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `advertiserIds` | `array` | `[]` | Numeric Facebook page IDs, e.g. `20409006880` (Shopify) — the `view_all_page_id` value in an advertiser's Ad Library URL. The whole Ad Library URL is accepted too. Every ad the page runs, across Facebook, Instagram, Messenger, WhatsApp, Audience Network and Threads. |
| `advertiserNames` | `array` | `[]` | Brand or company names, e.g. `Nike`. Each is resolved to the Facebook pages advertising under that name — the pages behind the ads a name search returns, ranked by how well the page name matches — and those pages are then scraped (`resultType: ads`) or listed (`resultType: advertisers`). Use `maxAdvertisersPerName` to control how many pages per name are followed. Pages whose ads only mention the name are followed too (`advertiser_match: none`), unless `onlyMatchingAdvertisers` is on. With `maxAds`, the pages whose name matches are read first and fill the cap; the others get what is left, and the run's status says how many ads came from each. |
| `searchTerms` | `array` | `[]` | Keywords or brand names, e.g. `shopify`. A keyword search is Meta's ranked sample across every advertiser — the Ad Library returns at most a few hundred creatives for any keyword, whatever total it displays. To get everything one advertiser runs, use its page ID instead. `keywordMatch` chooses loose or exact-phrase matching. |
| `creativeIds` | `array` | `[]` | Library IDs of individual ads, e.g. `1657481092030542` — the `id` in an Ad Library URL (the whole URL is accepted too). One lookup each. |
| `resultType` | `ads` / `advertisers` / `counts` | `"ads"` | `ads` returns one record per ad creative. `advertisers` returns one record per page that `advertiserNames` or `searchTerms` resolve to — page id, name, categories, likes, how many matching ads it runs — so you can see which page a brand advertises under before pulling its ads (the same idiom as our Google Ads Transparency Scraper). `counts` returns one record per target with the total the Ad Library shows for it, from a single page load and without scraping any ad. |
| `maxAdvertisersPerName` | `integer` | `3` | How many resolved pages to follow for each entry in `advertiserNames`. A common name can match several pages (regional pages, resellers); the best name match ranks first. |
| `onlyMatchingAdvertisers` | `boolean` | `false` | With `advertiserNames`: follow only the pages whose name matches the name you gave (`advertiser_match` is `exact`, `starts`, `contains` or `domain`). Off, the pages whose ads only mention the name are followed too, and their rows say `advertiser_match: none`. On, a name that matches no page follows the pages whose ads mention it instead, and the run's status says so. |
| `startUrls` | `array` | `[]` | Paste Ad Library URLs — an advertiser page (`...?view_all_page_id=...`), a keyword search (`...?q=...`) or a single ad (`...?id=...`). A Facebook page URL (`facebook.com/shopify`) works too: the Actor finds the advertiser whose page name is that name, and returns nothing rather than a different page when no name matches. A `country` in the URL is used unless you set `region` explicitly. |
| `region` | `string` |  | Two-letter country code for where the ads were shown, e.g. `US`, `GB` (`UK` also works), `DE`, or `ALL` for every country at once — the same `country=` value the Ad Library puts in its URLs. Leave it empty and the country in a pasted `startUrls` URL is used, or `ALL` when there is none. The EU transparency figures (`includeEuReach`) do not depend on this: they come back for any ad that reached the EU whatever country you ask for. |
| `regions` | `array` | `[]` | Several country codes in one run, e.g. `["DE", "FR", "IT"]` — ads shown in any of them. Wins over `region` when given; `ALL` in the list means every country. Rows carry the list in `region`. |
| `targetedCountryOnly` | `boolean` | `false` | With a country in `region`/`regions`: ask the Ad Library for ads whose advertiser targeted that country, not every ad that happened to be shown there — its own 'targeted' switch, passed through as is. Measured 2026-09-20 on a page and on a keyword search in DE: the first 30 rows were identical with and without it, so expect a small difference at most. No effect with `ALL`. |
| `activeStatus` | `active` / `inactive` / `all` | `"active"` | `active` returns ads running now, `inactive` ads that have stopped, `all` both. Inactive ads are how you read a competitor's history: what they tried and dropped. |
| `adType` | `all` / `political_and_issue` / `housing` / `employment` / `financial_products` | `"all"` | `all` for every ad. `political_and_issue` narrows to ads about social issues, elections or politics — the only ads Meta publishes spend, impressions and reach for; those arrive as ranges in `spend_min`/`spend_max`, `impressions_min`/`impressions_max` and `reach_min`/`reach_max`, with the funding entity in `paid_for_by`. `housing`, `employment` and `financial_products` are Meta's special ad categories. |
| `mediaType` | `all` / `image` / `video` / `meme` / `image_and_meme` / `none` | `"all"` | Keep only creatives of this media type, as the Ad Library's own filter defines them. Meta files most image ads with words on the picture under `meme`, so `image` alone can miss most of a page's image ads (1 of 7 on one page, measured 2026-11-05): choose `image_and_meme` for every image ad. `all` is every type. |
| `platforms` | `array` | `[]` | Keep only ads that ran on these Meta platforms. Empty means all. Every row also lists where it ran in `platforms`. |
| `languages` | `array` | `[]` | Keep only creatives in these languages, as two-letter ISO 639-1 codes, e.g. `["es", "pt"]` — the Ad Library's own language filter. Empty means all. |
| `sortBy` | `auto` / `relevance` / `most_recent` / `most_impressions` / `least_impressions` | `"auto"` | `auto` picks the order that reaches the most: for an advertiser the newest-first order walks every creative in a straight line (measured: relevance starts repeating after ~1,240), for a search term relevance yields more. `most_impressions` / `least_impressions` order by Meta's impressions bucket, which exists on ads about social issues, elections or politics only. |
| `expandVariants` | `boolean` | `true` | The Library groups ads that share a creative and text ("N ads use this creative") and shows one per group; its result count counts them all. On, each group is opened and every ad in it is a row of its own — the count the Library shows, the way the lane's biggest actors deliver it (Shopify: 1,753 creatives, 5,597 ads). Off, one row per creative with `collation_count` saying how many ads use it: fewer rows, fewer fees, the creative-level view. |
| `keywordMatch` | `unordered` / `exact_phrase` | `"unordered"` | How `searchTerms` match ad text: `unordered` finds ads containing the words in any order, `exact_phrase` only the phrase as typed. A pasted search URL keeps its own `search_type`. |
| `keywordRelevance` | `any` / `visible` | `"any"` | A keyword search is the Ad Library's loose match: `shopify` also returns ads that mention the word in passing. Every keyword row carries `matched_fields` — which of its texts (headline, body, cards, link, advertiser name) contain the whole term. `visible` keeps only rows with at least one such field; `any` keeps everything the Library returned. |
| `dedupeBy` | `none` / `advertiser` | `"none"` | `advertiser` keeps one ad per advertiser in a keyword search — a who-is-advertising-on-this read instead of every creative. `none` keeps all. |
| `startDate` | `string` |  | `YYYY-MM-DD`. Keeps ads whose run overlaps this date or later — an ad that started years ago and is still running counts, because it is still running. |
| `endDate` | `string` |  | `YYYY-MM-DD`. Keeps ads whose run overlaps this date or earlier. |
| `period` | \`\` / `last24h` / `last7d` / `last14d` / `last30d` / `last90d` | `""` | Keep only creatives that started running in the window: the last 24 hours, 7, 14, 30 or 90 days. A preset over `first_shown`; combine with `sortBy: most_recent` for a 'what did they launch this week' read. Empty means no window. |
| `launchedFrom` | `string` |  | `YYYY-MM-DD`. Keeps only ads that STARTED on this date or later (`first_shown`), unlike `startDate`, which keeps any ad still running then. With `period`, the later start wins. |
| `launchedTo` | `string` |  | `YYYY-MM-DD`. Keeps only ads that started on this date or earlier. With `launchedFrom`: the ads launched between the two dates, e.g. a competitor's Black Friday launches. |
| `longRunningDays` | `integer` | `30` | An ad shown for at least this many days gets `long_running: true` — the fastest read on which creatives earn their budget. Default 30. |
| `includeEuReach` | `boolean` | `false` | One extra request per ad that adds what the Ad Library publishes under the EU Digital Services Act: total EU reach, the reach breakdown by country, age and gender, the audience the advertiser chose (age range, gender, included and excluded locations) and the payer and beneficiary. Present on ads that reached the EU, null on the rest; billed only on rows that carry a figure. Also fills the advertiser columns below. |
| `includeAdvertiserInfo` | `boolean` | `false` | One extra request per advertiser (not per ad) that fills `advertiser_category`, `advertiser_about`, `advertiser_likes`, `advertiser_ig_username`, `advertiser_ig_followers`, `advertiser_verified` and, for political pages, lifetime and last-week spend. Not billed separately. |
| `includePageTransparency` | `boolean` | `false` | One extra request per advertiser (not per ad), from Facebook's "Page transparency" panel: `page_created`, every name change (`page_name_changes`), how many admins sit in which country (`page_admin_countries`), the confirmed owner and its location (`page_owner`, `page_owner_location`), `page_partners`. Spots a page that was renamed last month or is run from another country. Not billed separately. |
| `includeTotalActiveAds` | `boolean` | `false` | `advertiser_active_ads` on every row: how many ads the page is running now in your region, all formats and platforms — the total the Ad Library shows on the page. One small request per advertiser; free and automatic when the target is the page itself and no filter narrows it. Not billed separately. |
| `downloadMedia` | `none` / `images` / `all` | `"none"` | Copy each creative's files from Meta's CDN into a key-value store you own, so the links do not expire with Meta's ~4-day URLs. Every row then carries `media_files` (store URL, size, type per file) plus `image_file_url` / `video_file_url`. Files are keyed by creative id, so a creative met again on a later run is linked to its existing file and not fetched or charged twice. Billed per file written. |
| `mediaStoreName` | `string` |  | Named key-value store the files go into. Optional: left empty, the name is derived from the targets (`meta-ads-media-…`), so a scheduled task keeps adding to the same store. Set one to collect several inputs into one place, e.g. `creatives-shopify`. |
| `transcribeVideos` | `boolean` | `false` | Turn the speech in every video creative into text: `transcript`, `transcript_language` (ISO code), `hook_3s` (the words spoken in the first three seconds of speech) and `speech_seconds`. A music-only or silent video is checked and comes back with `speech_seconds: 0` — and is not billed. Billed per row that carries a transcript. |
| `ocrImageAds` | `boolean` | `false` | Read the words baked into the artwork with a vision model — the headline set over the photo, the offer sticker, the price — into `ad_ocr_text`. The image read is the creative's first image, or the poster frame of a video ad. Read once per creative and cached. Billed per row where words were found; model usage is included. |
| `analyzeAds` | `boolean` | `false` | One structured read per creative by an OpenAI model: `analysis_hook` (the scroll-stopper), `analysis_offer`, `analysis_angle` (problem_solution, social_proof, scarcity, discount, feature, lifestyle, curiosity, education, comparison, brand, other), `analysis_audience`, `analysis_cta_intent`, `analysis_tone` and `analysis_sentiment` (-1..1). Reads the copy, the cards, the CTA, and the image text and transcript when those switches are on. A free `sentiment_score` (AFINN word list) is on every row regardless. Billed per row analysed; model usage included. |
| `enrichLandingPage` | `boolean` | `false` | Follow each ad's link to the page it lands on: `landing_final_url` (redirects followed, tracking parameters stripped), `landing_domain`, `landing_status`, `landing_title`, and — from the page's own product markup (Open Graph, JSON-LD) — `landing_is_product`, `landing_product_name`, `landing_price`, `landing_currency`. One fetch per distinct URL per run. Billed per row whose page answered. |
| `openaiApiKey` | `string` |  | Optional. The Actor uses its own model access by default and bills a flat fee per transcript, image read or analysis. Supply your own OpenAI key only if you would rather the model usage went to your account. |
| `summaryRows` | `boolean` | `false` | After the ads, add one `advertiser_profile` row per advertiser (ad count, active count, longest-running days, formats, landing domains, distinct copies, the ad ids) and one `landing_page` row per destination URL with the ads pointing at it. Computed from the rows already delivered — no extra requests, not billed. |
| `runTag` | `string` |  | Your own label for this run — a client name, a campaign, a week — copied into `run_tag` on every row, so rows from several runs sort in one sheet or table. |
| `maxAds` | `integer` | `0` | Stop after this many ads in total, across every search. 0 means no limit. |
| `maxAdsPerTarget` | `integer` | `0` | Cap each page ID, keyword or URL separately. Without this, one prolific advertiser can use the whole `maxAds` budget before the next target starts. 0 means no per-target cap. |
| `onlyNewAds` | `boolean` | `false` | Skip creatives delivered by previous runs of the same targets (or of the same `stateStoreName`). Skipped ads are never returned and never charged — a weekly monitoring run pays only for what actually changed (and, from 5 November 2026, the start fee). Every row also carries `first_seen_at` / `last_seen_at` from the same memory. |
| `includeEndedAds` | `boolean` | `false` | With a monitoring memory (the same store `onlyNewAds` uses): after a complete scan of a target, add one `ended` row for every creative earlier runs saw there that the Library no longer returns — with when it was first and last seen. A capped or cut-off scan reports none, because it cannot vouch for an absence. Not billed. |
| `stateStoreName` | `string` |  | Name of the memory that holds the creative IDs already delivered, so `onlyNewAds` can tell new from old across runs. The memory is kept by the Actor on its side, one per Apify account — nothing is stored in your account. Optional: left empty, the name is derived from the targets, so a scheduled task remembers on its own. Set one to share memory between different inputs, e.g. `ads-shopify`, or change it to start over. |
| `resumeFromRunId` | `string` |  | The id of an earlier run of this Actor that timed out or was aborted, with the same input: this run continues from its checkpoint — finished targets skipped, the target in progress continued from its cursor, no creative delivered twice, `maxAds` counted across both. A run that stops on its own can also simply be resurrected (Apify's Resurrect): it finds its own checkpoint. |
| `requestDelaySecs` | `number` | `0.3` | Seconds to wait between result pages. The source paces itself by latency; a small delay keeps one exit useful for longer. |
| `concurrency` | `integer` | `3` | No longer needed: every advertiser and search term now starts at once, and the run opens more residential exits as its work needs them, up to 8. An input that still sets it runs as before. |
| `includeRawData` | `boolean` | `false` | Add Meta's own untouched record to each row as `raw`, for callers who would rather map the fields themselves. |
| `feedback` | `string` |  | A note to the people who maintain this Actor: what you needed and did not get. For example a missing field, a wrong or empty result, or the Actor you switch from or to, and why. The run does not read it: the rows and the charges stay the same. A person reads every note. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing (from 5 November 2026, only the start fee), and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

A search that matches nothing is not an error: the dataset is empty and no row is charged. A run that ends **Failed** is a fault on our side, never your input; it charges no row, and we are alerted. From 5 November 2026 both still pay the start fee, which Apify charges when a run starts.

### FAQ

**The Ad Library says a page has ~5,700 ads. Will I get them all?**

Nearly. The count is ads; the list shows one per creative group, and this actor opens every group (`expandVariants`, on by default). On Shopify, measured 2026-09-20: 2,310 creatives opened into 5,337 ads, 95% of the 5,597 the two most-used actors deliver, in 19 minutes. The source refuses cursors past roughly 1,900 creatives in any one order; when a walk hits that, the actor continues in media-type and platform slices and the status names what was cut. A keyword search is different: it is Meta's ranked sample, a few hundred creatives whatever the count says, on every actor.

**Why are `spend_min`, `impressions_max` and `reach_min` null on my ads?**

Meta publishes spend, impressions and reach only for ads about social issues, elections or politics. Commercial ads never carry spend or reach, on the Ad Library site or anywhere else; every actor in this category returns null there, whatever its schema promises. The one exception is the `<100` impressions band Meta shows on a new ad in its first hours; it is never billed as political. Use `adType: political_and_issue` for the ads that have them. For commercial ads the one reach figure that exists is the EU one: switch on `includeEuReach`.

**What does `includeEuReach` cost me?**

One extra request per ad, and the per-row EU fee only on rows that came back with a figure — an ad that never reached the EU is not billed for it. `includeAdvertiserInfo` alone is one request per advertiser and is not billed separately.

**Can I search by a brand name instead of a page ID?**

Yes — put it in `advertiserNames`, or paste the brand's Facebook page URL into `startUrls`. A name is resolved to the pages that carry it, and every ad of those pages is returned. A tail like `USA`, `Inc.`, `LLC`, `Store` or `Official Site` is tried without it when the full name finds nothing, and a domain (`brand.com`) matches the page whose ads link there. The search also finds pages whose ads only mention the name; their ads are returned too, and every ad row says how its page matched in `advertiser_match` — set `onlyMatchingAdvertisers` to follow only the brand's own pages, or keep the rows where it is not `none`. With `maxAds`, the brand's own pages are read first and fill the cap. `searchTerms` is different: Meta's ranked sample across every advertiser mentioning the words, right for a topic and wrong for a page.

**Which `region` should I use?**

`ALL` returns every ad the page runs anywhere. A country code narrows to ads shown there. The EU figures do not depend on it: `includeEuReach` returns them for any ad that reached the EU, whatever country you asked for.

**The image or video link stopped working.**

Meta's CDN links carry an expiry of about four days from the moment the run fetched them. Download what you need within that window; a new run issues fresh links.

**What does a PARTIAL status mean?**

The source refused to keep answering even after the actor rotated through fresh exits, or answered with errors on every attempt, for the searches named in the status. Whatever was delivered is in the dataset; the named searches are incomplete. It is rare, and it is ours to fix — a PARTIAL run is a bug report, not a configuration problem on your side.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/meta-ads-library-scraper').call({"advertiserIds": ["20409006880"], "region": "ALL", "maxAds": 100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/meta-ads-library-scraper').call(run_input={'advertiserIds': ['20409006880'], 'region': 'ALL', 'maxAds': 100})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/meta-ads-library-scraper --input '{"advertiserIds": ["20409006880"], "region": "ALL", "maxAds": 100}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~meta-ads-library-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"advertiserIds": ["20409006880"], "region": "ALL", "maxAds": 100}'
```

#### AI agents and MCP

Apify's MCP server offers this Actor as a tool to Claude, Cursor, VS Code and any other MCP client. Connect `https://mcp.apify.com?tools=hyperbach/meta-ads-library-scraper` and ask, for example: "List every active Facebook and Instagram ad the page with id 20409006880 runs, up to 100". The agent fills the input from the table above and reads the rows from the run's dataset. A field under the name another Actor gives it is read as this Actor's own: `urls`, `url`, `links`, `pageUrls` as `startUrls`; `pageIds`, `pageId`, `page_ids`, `advertiserId` as `advertiserIds`; `advertisers`, `brands`, `brandNames`, `pageNames` as `advertiserNames`; `keywords`, `keyword`, `query`, `queries` as `searchTerms` (and more). An agent or a person can add `feedback` to any run and say what was needed and not delivered: a missing field, a wrong or empty result, the Actor switched from or to, and why. The run returns the same rows at the same price, and a person reads every note.

#### n8n, Make and Zapier

The Apify node in n8n, the Apify app in Make and the Apify integration in Zapier run this Actor on a schedule or from a trigger and read its dataset. A webhook on the run's end (Integrations on the Actor page) sends the rows on to your own endpoint. Nothing to install on our side.

### Support

**Disagree with a charge?** Write to apify@hyperbach.com with the run ID. We will ask Apify to compensate you, and the charge comes back to your Apify account as platform credit.

**Did it do the job?** A short [review on the Store](https://apify.com/hyperbach/meta-ads-library-scraper/reviews), at any rating, helps the next buyer choose.

### See also

- **[Facebook Pages Scraper](https://apify.com/hyperbach/facebook-pages-scraper)** — Facebook pages by URL, id, name or search words: the exact follower count, categories, address, phone, e-mail and websites, and the page transparency facts.
- **[Threads Scraper](https://apify.com/hyperbach/threads-scraper)** — Threads profiles with the exact follower count, their posts and replies with view counts, and keyword and tag search, without a login.
- **[Instagram Reels Transcript Scraper](https://apify.com/hyperbach/instagram-reels-transcript-scraper)** — the spoken words of Instagram reels, by reel link or creator, with the exact posting time.
- **[Instagram Posts Scraper](https://apify.com/hyperbach/instagram-posts-scraper)** — the posts and reels of Instagram profiles, with the whole caption, the exact publish time, likes, comments and plays.
- **[Instagram Hashtag Scraper](https://apify.com/hyperbach/instagram-hashtag-scraper)** — Instagram's own ranked list of top reels for a hashtag or a search keyword.
- **[Facebook Marketplace Scraper](https://apify.com/hyperbach/facebook-marketplace-scraper)** — Marketplace listings by search, category and city: the price, the date listed and the seller, and on request the description and the vehicle facts.

**Run them together:** Put the `advertiser_id` of the ads this run returns into Facebook Pages Scraper to get each advertiser's page: the exact follower count, the contact details and when the page was created.

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

## `advertiserIds` (type: `array`):

Numeric Facebook page IDs, e.g. `20409006880` (Shopify) — the `view_all_page_id` value in an advertiser's Ad Library URL. The whole Ad Library URL is accepted too. Every ad the page runs, across Facebook, Instagram, Messenger, WhatsApp, Audience Network and Threads.

## `advertiserNames` (type: `array`):

Brand or company names, e.g. `Nike`. Each is resolved to the Facebook pages advertising under that name — the pages behind the ads a name search returns, ranked by how well the page name matches — and those pages are then scraped (`resultType: ads`) or listed (`resultType: advertisers`). Use `maxAdvertisersPerName` to control how many pages per name are followed. Pages whose ads only mention the name are followed too (`advertiser_match: none`), unless `onlyMatchingAdvertisers` is on. With `maxAds`, the pages whose name matches are read first and fill the cap; the others get what is left, and the run's status says how many ads came from each.

## `searchTerms` (type: `array`):

Keywords or brand names, e.g. `shopify`. A keyword search is Meta's ranked sample across every advertiser — the Ad Library returns at most a few hundred creatives for any keyword, whatever total it displays. To get everything one advertiser runs, use its page ID instead. `keywordMatch` chooses loose or exact-phrase matching.

## `creativeIds` (type: `array`):

Library IDs of individual ads, e.g. `1657481092030542` — the `id` in an Ad Library URL (the whole URL is accepted too). One lookup each.

## `resultType` (type: `string`):

`ads` returns one record per ad creative. `advertisers` returns one record per page that `advertiserNames` or `searchTerms` resolve to — page id, name, categories, likes, how many matching ads it runs — so you can see which page a brand advertises under before pulling its ads (the same idiom as our Google Ads Transparency Scraper). `counts` returns one record per target with the total the Ad Library shows for it, from a single page load and without scraping any ad.

## `maxAdvertisersPerName` (type: `integer`):

How many resolved pages to follow for each entry in `advertiserNames`. A common name can match several pages (regional pages, resellers); the best name match ranks first.

## `onlyMatchingAdvertisers` (type: `boolean`):

With `advertiserNames`: follow only the pages whose name matches the name you gave (`advertiser_match` is `exact`, `starts`, `contains` or `domain`). Off, the pages whose ads only mention the name are followed too, and their rows say `advertiser_match: none`. On, a name that matches no page follows the pages whose ads mention it instead, and the run's status says so.

## `startUrls` (type: `array`):

Paste Ad Library URLs — an advertiser page (`...?view_all_page_id=...`), a keyword search (`...?q=...`) or a single ad (`...?id=...`). A Facebook page URL (`facebook.com/shopify`) works too: the Actor finds the advertiser whose page name is that name, and returns nothing rather than a different page when no name matches. A `country` in the URL is used unless you set `region` explicitly.

## `region` (type: `string`):

Two-letter country code for where the ads were shown, e.g. `US`, `GB` (`UK` also works), `DE`, or `ALL` for every country at once — the same `country=` value the Ad Library puts in its URLs. Leave it empty and the country in a pasted `startUrls` URL is used, or `ALL` when there is none. The EU transparency figures (`includeEuReach`) do not depend on this: they come back for any ad that reached the EU whatever country you ask for.

## `regions` (type: `array`):

Several country codes in one run, e.g. `["DE", "FR", "IT"]` — ads shown in any of them. Wins over `region` when given; `ALL` in the list means every country. Rows carry the list in `region`.

## `targetedCountryOnly` (type: `boolean`):

With a country in `region`/`regions`: ask the Ad Library for ads whose advertiser targeted that country, not every ad that happened to be shown there — its own 'targeted' switch, passed through as is. Measured 2026-09-20 on a page and on a keyword search in DE: the first 30 rows were identical with and without it, so expect a small difference at most. No effect with `ALL`.

## `activeStatus` (type: `string`):

`active` returns ads running now, `inactive` ads that have stopped, `all` both. Inactive ads are how you read a competitor's history: what they tried and dropped.

## `adType` (type: `string`):

`all` for every ad. `political_and_issue` narrows to ads about social issues, elections or politics — the only ads Meta publishes spend, impressions and reach for; those arrive as ranges in `spend_min`/`spend_max`, `impressions_min`/`impressions_max` and `reach_min`/`reach_max`, with the funding entity in `paid_for_by`. `housing`, `employment` and `financial_products` are Meta's special ad categories.

## `mediaType` (type: `string`):

Keep only creatives of this media type, as the Ad Library's own filter defines them. Meta files most image ads with words on the picture under `meme`, so `image` alone can miss most of a page's image ads (1 of 7 on one page, measured 2026-11-05): choose `image_and_meme` for every image ad. `all` is every type.

## `platforms` (type: `array`):

Keep only ads that ran on these Meta platforms. Empty means all. Every row also lists where it ran in `platforms`.

## `languages` (type: `array`):

Keep only creatives in these languages, as two-letter ISO 639-1 codes, e.g. `["es", "pt"]` — the Ad Library's own language filter. Empty means all.

## `sortBy` (type: `string`):

`auto` picks the order that reaches the most: for an advertiser the newest-first order walks every creative in a straight line (measured: relevance starts repeating after ~1,240), for a search term relevance yields more. `most_impressions` / `least_impressions` order by Meta's impressions bucket, which exists on ads about social issues, elections or politics only.

## `expandVariants` (type: `boolean`):

The Library groups ads that share a creative and text ("N ads use this creative") and shows one per group; its result count counts them all. On, each group is opened and every ad in it is a row of its own — the count the Library shows, the way the lane's biggest actors deliver it (Shopify: 1,753 creatives, 5,597 ads). Off, one row per creative with `collation_count` saying how many ads use it: fewer rows, fewer fees, the creative-level view.

## `keywordMatch` (type: `string`):

How `searchTerms` match ad text: `unordered` finds ads containing the words in any order, `exact_phrase` only the phrase as typed. A pasted search URL keeps its own `search_type`.

## `keywordRelevance` (type: `string`):

A keyword search is the Ad Library's loose match: `shopify` also returns ads that mention the word in passing. Every keyword row carries `matched_fields` — which of its texts (headline, body, cards, link, advertiser name) contain the whole term. `visible` keeps only rows with at least one such field; `any` keeps everything the Library returned.

## `dedupeBy` (type: `string`):

`advertiser` keeps one ad per advertiser in a keyword search — a who-is-advertising-on-this read instead of every creative. `none` keeps all.

## `startDate` (type: `string`):

`YYYY-MM-DD`. Keeps ads whose run overlaps this date or later — an ad that started years ago and is still running counts, because it is still running.

## `endDate` (type: `string`):

`YYYY-MM-DD`. Keeps ads whose run overlaps this date or earlier.

## `period` (type: `string`):

Keep only creatives that started running in the window: the last 24 hours, 7, 14, 30 or 90 days. A preset over `first_shown`; combine with `sortBy: most_recent` for a 'what did they launch this week' read. Empty means no window.

## `launchedFrom` (type: `string`):

`YYYY-MM-DD`. Keeps only ads that STARTED on this date or later (`first_shown`), unlike `startDate`, which keeps any ad still running then. With `period`, the later start wins.

## `launchedTo` (type: `string`):

`YYYY-MM-DD`. Keeps only ads that started on this date or earlier. With `launchedFrom`: the ads launched between the two dates, e.g. a competitor's Black Friday launches.

## `longRunningDays` (type: `integer`):

An ad shown for at least this many days gets `long_running: true` — the fastest read on which creatives earn their budget. Default 30.

## `includeEuReach` (type: `boolean`):

One extra request per ad that adds what the Ad Library publishes under the EU Digital Services Act: total EU reach, the reach breakdown by country, age and gender, the audience the advertiser chose (age range, gender, included and excluded locations) and the payer and beneficiary. Present on ads that reached the EU, null on the rest; billed only on rows that carry a figure. Also fills the advertiser columns below.

## `includeAdvertiserInfo` (type: `boolean`):

One extra request per advertiser (not per ad) that fills `advertiser_category`, `advertiser_about`, `advertiser_likes`, `advertiser_ig_username`, `advertiser_ig_followers`, `advertiser_verified` and, for political pages, lifetime and last-week spend. Not billed separately.

## `includePageTransparency` (type: `boolean`):

One extra request per advertiser (not per ad), from Facebook's "Page transparency" panel: `page_created`, every name change (`page_name_changes`), how many admins sit in which country (`page_admin_countries`), the confirmed owner and its location (`page_owner`, `page_owner_location`), `page_partners`. Spots a page that was renamed last month or is run from another country. Not billed separately.

## `includeTotalActiveAds` (type: `boolean`):

`advertiser_active_ads` on every row: how many ads the page is running now in your region, all formats and platforms — the total the Ad Library shows on the page. One small request per advertiser; free and automatic when the target is the page itself and no filter narrows it. Not billed separately.

## `downloadMedia` (type: `string`):

Copy each creative's files from Meta's CDN into a key-value store you own, so the links do not expire with Meta's ~4-day URLs. Every row then carries `media_files` (store URL, size, type per file) plus `image_file_url` / `video_file_url`. Files are keyed by creative id, so a creative met again on a later run is linked to its existing file and not fetched or charged twice. Billed per file written.

## `mediaStoreName` (type: `string`):

Named key-value store the files go into. Optional: left empty, the name is derived from the targets (`meta-ads-media-…`), so a scheduled task keeps adding to the same store. Set one to collect several inputs into one place, e.g. `creatives-shopify`.

## `transcribeVideos` (type: `boolean`):

Turn the speech in every video creative into text: `transcript`, `transcript_language` (ISO code), `hook_3s` (the words spoken in the first three seconds of speech) and `speech_seconds`. A music-only or silent video is checked and comes back with `speech_seconds: 0` — and is not billed. Billed per row that carries a transcript.

## `ocrImageAds` (type: `boolean`):

Read the words baked into the artwork with a vision model — the headline set over the photo, the offer sticker, the price — into `ad_ocr_text`. The image read is the creative's first image, or the poster frame of a video ad. Read once per creative and cached. Billed per row where words were found; model usage is included.

## `analyzeAds` (type: `boolean`):

One structured read per creative by an OpenAI model: `analysis_hook` (the scroll-stopper), `analysis_offer`, `analysis_angle` (problem_solution, social_proof, scarcity, discount, feature, lifestyle, curiosity, education, comparison, brand, other), `analysis_audience`, `analysis_cta_intent`, `analysis_tone` and `analysis_sentiment` (-1..1). Reads the copy, the cards, the CTA, and the image text and transcript when those switches are on. A free `sentiment_score` (AFINN word list) is on every row regardless. Billed per row analysed; model usage included.

## `enrichLandingPage` (type: `boolean`):

Follow each ad's link to the page it lands on: `landing_final_url` (redirects followed, tracking parameters stripped), `landing_domain`, `landing_status`, `landing_title`, and — from the page's own product markup (Open Graph, JSON-LD) — `landing_is_product`, `landing_product_name`, `landing_price`, `landing_currency`. One fetch per distinct URL per run. Billed per row whose page answered.

## `openaiApiKey` (type: `string`):

Optional. The Actor uses its own model access by default and bills a flat fee per transcript, image read or analysis. Supply your own OpenAI key only if you would rather the model usage went to your account.

## `summaryRows` (type: `boolean`):

After the ads, add one `advertiser_profile` row per advertiser (ad count, active count, longest-running days, formats, landing domains, distinct copies, the ad ids) and one `landing_page` row per destination URL with the ads pointing at it. Computed from the rows already delivered — no extra requests, not billed.

## `runTag` (type: `string`):

Your own label for this run — a client name, a campaign, a week — copied into `run_tag` on every row, so rows from several runs sort in one sheet or table.

## `maxAds` (type: `integer`):

Stop after this many ads in total, across every search. 0 means no limit.

## `maxAdsPerTarget` (type: `integer`):

Cap each page ID, keyword or URL separately. Without this, one prolific advertiser can use the whole `maxAds` budget before the next target starts. 0 means no per-target cap.

## `onlyNewAds` (type: `boolean`):

Skip creatives delivered by previous runs of the same targets (or of the same `stateStoreName`). Skipped ads are never returned and never charged — a weekly monitoring run pays only for what actually changed (and, from 5 November 2026, the start fee). Every row also carries `first_seen_at` / `last_seen_at` from the same memory.

## `includeEndedAds` (type: `boolean`):

With a monitoring memory (the same store `onlyNewAds` uses): after a complete scan of a target, add one `ended` row for every creative earlier runs saw there that the Library no longer returns — with when it was first and last seen. A capped or cut-off scan reports none, because it cannot vouch for an absence. Not billed.

## `stateStoreName` (type: `string`):

Name of the memory that holds the creative IDs already delivered, so `onlyNewAds` can tell new from old across runs. The memory is kept by the Actor on its side, one per Apify account — nothing is stored in your account. Optional: left empty, the name is derived from the targets, so a scheduled task remembers on its own. Set one to share memory between different inputs, e.g. `ads-shopify`, or change it to start over.

## `resumeFromRunId` (type: `string`):

The id of an earlier run of this Actor that timed out or was aborted, with the same input: this run continues from its checkpoint — finished targets skipped, the target in progress continued from its cursor, no creative delivered twice, `maxAds` counted across both. A run that stops on its own can also simply be resurrected (Apify's Resurrect): it finds its own checkpoint.

## `requestDelaySecs` (type: `number`):

Seconds to wait between result pages. The source paces itself by latency; a small delay keeps one exit useful for longer.

## `concurrency` (type: `integer`):

No longer needed: every advertiser and search term now starts at once, and the run opens more residential exits as its work needs them, up to 8. An input that still sets it runs as before.

## `includeRawData` (type: `boolean`):

Add Meta's own untouched record to each row as `raw`, for callers who would rather map the fields themselves.

## `feedback` (type: `string`):

A note to the people who maintain this Actor: what you needed and did not get. For example a missing field, a wrong or empty result, or the Actor you switch from or to, and why. The run does not read it: the rows and the charges stay the same. A person reads every note.

## Actor input object example

```json
{
  "advertiserIds": [
    "20409006880"
  ],
  "advertiserNames": [],
  "searchTerms": [],
  "creativeIds": [],
  "resultType": "ads",
  "maxAdvertisersPerName": 3,
  "onlyMatchingAdvertisers": false,
  "startUrls": [],
  "regions": [],
  "targetedCountryOnly": false,
  "activeStatus": "active",
  "adType": "all",
  "mediaType": "all",
  "platforms": [],
  "languages": [],
  "sortBy": "auto",
  "expandVariants": true,
  "keywordMatch": "unordered",
  "keywordRelevance": "any",
  "dedupeBy": "none",
  "period": "",
  "longRunningDays": 30,
  "includeEuReach": false,
  "includeAdvertiserInfo": false,
  "includePageTransparency": false,
  "includeTotalActiveAds": false,
  "downloadMedia": "none",
  "transcribeVideos": false,
  "ocrImageAds": false,
  "analyzeAds": false,
  "enrichLandingPage": false,
  "summaryRows": false,
  "maxAds": 50,
  "maxAdsPerTarget": 0,
  "onlyNewAds": false,
  "includeEndedAds": false,
  "requestDelaySecs": 0.3,
  "concurrency": 3,
  "includeRawData": false
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped records in the default dataset. One record per ad creative:

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "advertiserIds": [
        "20409006880"
    ],
    "maxAds": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/meta-ads-library-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "advertiserIds": ["20409006880"],
    "maxAds": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/meta-ads-library-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "advertiserIds": [
    "20409006880"
  ],
  "maxAds": 50
}' |
apify call hyperbach/meta-ads-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hyperbach/meta-ads-library-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/uHm94BMyxm1koOhQg/builds/cS2DKXcobchFBAwEG/openapi.json
