# Meta Ad Spy (`deepmine/meta-ad-spy`) Actor

Meta Ad Spy is a Facebook ad library scraper and Meta ads scraper: search the Meta Ad Library for Facebook ads by keyword to find winning products, dropshipping trends, competitor ads and top Shopify ad angles. Long-running ads, top CTAs, hooks and formats, to validate products before you spend.

- **URL**: https://apify.com/deepmine/meta-ad-spy.md
- **Developed by:** [DeepMine](https://apify.com/deepmine) (community)
- **Categories:** E-commerce, Social media, Lead generation
- **Features:** Limited permissions
- **Stats:** 11 total users, 1 monthly users, 94.3% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.34 / 1,000 ads

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 Spy: Facebook & Instagram Competitor Ads

Paste a competitor's Facebook page and get every ad it runs on Facebook and Instagram, with the creative, cards, variants, dates, placements and how long each ad has run.

| Advertiser | Ad | Headline | Format | Days running | Same creative | Started |
|---|---|---|---|---|---|---|
| Gymshark | Your go to gym set just got a summer upgrade 👀 | Shop Best Sellers | Dynamic creative | 108 | 1 | 2026-07-02 |
| Gymshark | The pieces everyone’s talking about. Shop our best… | Shop Best Sellers | Catalog (6 cards) | 17 | | 2026-10-01 |
| Gymshark | Megan Thee Stallion does gym and has the receipts… | Real hot girl sh\*t takes work 💅 | Dynamic creative | 4 | 2 | 2026-10-14 |
<sub>Collected 2026-10-18 with the prefilled input (Gymshark's Facebook page, country US, 30 ads). Rows shortened.</sub>

**$0.45 per 1,000 ads** on Starter ($0.55 Free, $0.39 Scale, $0.34 Business), ad details included. No start fee, and the search summary is free. The prefilled run (up to 30 ads) costs about $0.01.

### What you can do with it

- **Spy on competitors.** Every active ad of an advertiser, from its Facebook page link, page id, Ad Library link or name.
- **Research products and niches.** Search ads by keyword or product (e.g. *posture corrector*), in any country.
- **Look up single ads.** Paste Library IDs or ad links and get those ads, whatever their country or status.
- **Build swipe files.** Ad text, headline, button, landing page, picture and video, and every card of carousel, dynamic and catalog ads.
- **See what keeps running.** How long each ad has run and how many ads use the same creative, with every variant on request.
- **Watch competitors launch.** Schedule it with *Only new ads* and get just the ads started since the last run.

No login, no cookies, no browser extension: the data comes from Meta's public Ad Library.

### Input

| Field | What it does |
|---|---|
| 🏢 Competitors (`advertisers`) | Facebook page links (facebook.com/gymshark), page ids, Ad Library links of an advertiser, or page names exactly as they show on the ads. |
| 🔍 Keywords or products (`searchQueries`) | Ad Library keyword searches, one per line. |
| 🆔 Ad IDs (`adIds`) | Single ads: Library IDs or ad links (facebook.com/ads/library/?id=...). |
| 🌍 Country (`country`) | Where the ads ran: a two-letter code (US, GB, DE, ...) or ALL. Default US. |
| 💯 Max ads (`maxAdsPerQuery`) | Ads per competitor, search or URL (default 200). You pay per ad, so this caps the cost. |
| 🧬 Every variant (`includeVariants`) | Also get each creative's other ads (see Variants below). Each is an ad row. |
| 📋 Details (`includeAdDetails`) | The advertiser's verification, Instagram account and followers; for ads shown in the EU or UK, reach, reach by country, ages, gender, payer and beneficiary. |
| 🔔 Only new ads (`onlyNewAds`) | Monitor mode: only ads started since the last run that weren't returned before. |
| Filters | Ad Library URLs (`startUrls`), ad status, media type, ad category, exact phrase, dates shown. |

Example: `{"advertisers": ["https://www.facebook.com/Gymshark/", "https://www.facebook.com/nike"], "country": "US", "maxAdsPerQuery": 100}`

### Output

One row per ad. The Console shows it as tables (Overview, Stats, Creatives, Political, Details); export as JSON, CSV or Excel. The main fields:

- `pageName`, `pageUrl`, `pageId`: the advertiser.
- `adText`, `adTextSnippet`, `headline`, `ctaText`, `landingUrl`: the ad's text, headline, button and where it leads.
- `image`, `videoUrl`: the picture (or video frame) and the video file. Meta's media links expire about 5 days after the run.
- `cards`: for carousel, dynamic creative and catalog ads, every card's `headline`, `text`, `description`, `landingUrl`, `ctaText`, `image` and `videoUrl`.
- `format`: Image, Video, Carousel, Dynamic creative, Catalog, ...
- `platforms`: where the ad runs (placements): Facebook, Instagram, Messenger, Threads, Audience Network, WhatsApp.
- `isActive`, `startedAt`, `lastShownAt`: whether it still runs, the day it started and the last day it ran.
- `adArchiveId`, `adLibraryUrl`: the ad's Library ID and its page in the Ad Library.

Ads whose picture or video Meta shows only to logged-in Facebook users (often creator profiles) are never delivered or charged and don't count toward *Max ads per search*: OUTPUT `creativeHidden` lists them (ID, advertiser, Ad Library link) and the search reads on to fill its maximum.

- `rank`, `totalCount`, `searchInput`: its place in the search (1 = most impressions), Meta's count of ads for the search, and which competitor or search found it.
- With details: `isPageVerified`, `instagramUrl`, `instagramFollowers`, `euReach`, `ukReach`, `targetAges`, `targetGender`, `reachByCountry`, `payer`, `beneficiary`.
- After these, each row also has Meta's full record (`snapshot` with every card, image and video, `collationId`, `startDate`, `endDate`, ...), in the same form as other Ad Library scrapers.

#### Spy signals, and what they mean

- `daysActive`: days from the day the ad started to the last day it ran (today, for active ads). It shows how long the advertiser kept paying to show the ad.
- `collationCount` (🧬 Same Creative): how many ads use this same creative and text, Meta's count of the ad's group. Meta gives it on one ad of each group, so it's often empty.
- `adsCount`: how many ads Meta lists together with this one in the results.
- Variants: with *Every variant* on, the other ads of each group come as rows too (other versions, start dates, placements of the same creative). Group them by `collationId`.

These are signals of what advertisers keep running, not results: Meta publishes no sales, clicks, conversions or return on ad spend for commercial ads. Spend and impressions ranges exist only for ads about social issues, elections or politics (set *Ad category*).

#### Search summary (free)

Each run also writes one summary per competitor or search to the `SUMMARY` record (Output tab, *Search summary*). It is free: it isn't a dataset row.

- `totalAds`: ads the run returned for it.
- `battleTestedCount`, `gainingTractionCount`, `newTestCount`: ads that started 30 or more days ago, 14 to 29 days ago, and under 14 days ago.
- `avgAdAgeDays`, `longestRunningDays`, `longestRunningAdId`, `longestRunningBody`: average days since start, and the ad that started longest ago.
- `topFormat`, `topCta`, `topAdvertisers`, `topHooks`: the most used format and button, the advertisers with the most ads (with counts), and the most used opening lines.
- `scrapeStatus`, `scrapeError`: `ok`, `failed` (with why) or `not_found`.

### Saved inputs from the earlier version

Tasks, schedules and API calls made for the earlier version keep working:

- `productKeywords` is read as Keywords or products, `advertiserNames` as Competitors (now looked up as the advertiser's page), `maxAdsPerAdvertiser` as Max ads.
- A run whose input uses one of those fields gets the earlier rows: its keys first, in the same order and form (`rowType`, `query`, `advertiser`, `adArchiveId`, `adAgeDays`, `trendLabel`, `body`, `headline`, `cta` as SHOP_NOW, `format` as VIDEO or DCO, `imageUrl`, `videoUrl`, `linkUrl`, `startDate` and `endDate` as ISO dates, ...), then the fields above.
- The summary rows are now the free `SUMMARY` record, with the same keys, instead of rows in the dataset.
- Max ads is now exact (the earlier version could return 30 ads for a max of 17), `pageLikes` is filled, and `videoUrl` is the standard-definition file (HD is in `snapshot.videos`).

### Pricing

| | Free | Starter | Scale | Business |
|---|---|---|---|---|
| Ad | $0.55 | $0.45 | $0.39 | $0.34 |

Prices per 1,000 ads, ad details included; no start fee. You pay only for ad rows: summaries, ads not found and failed searches are free. With *Only new ads* you pay only for the new ads. Set a maximum charge in the run options to cap a run.

### Limitations

- Meta shows commercial ads' spend and reach only for ads shown in the EU or UK (with Details), and spend ranges only for political and issue ads.
- An advertiser given by name must match its page name exactly; a page link or id always works.
- Image and video links expire about 5 days after the run: download what you want to keep.
- About 3 minutes per 1,000 ads.

### FAQ

**Do I need a Facebook account or cookies?** No. It reads Meta's public Ad Library, logged out.

**Can I track a competitor every day?** Yes: turn on *Only new ads* and schedule the run. Each run returns only the ads started since the last one.

**Does it show which ads make money?** No one can from the Ad Library: Meta publishes no sales or return on ad spend. Long-running ads and creatives used in many ads are the public signals; this Actor gives you both, with their definitions above.

### Related Actors

- [Meta Ad Library API](https://apify.com/deepmine/meta-ad-library): the same data for any search, with the Ad Library's own filters.
- [Facebook Ads Monitor](https://apify.com/deepmine/facebook-ads-monitor): monitor advertisers for new ads.

### Feedback

Something missing or wrong? Open an issue in the Issues tab and we'll look at it. If Meta Ad Spy helps you, a short review on the Store helps others find it.

# Actor input Schema

## `advertisers` (type: `array`):

The advertisers to spy on: a Facebook page link (facebook.com/gymshark), a page id, an Ad Library link of the advertiser, or the page name exactly as it shows on its ads. One per line.

## `searchQueries` (type: `array`):

Find ads by keyword or product, as you'd type them in the Ad Library search box (e.g. posture corrector). One search per line.

## `adIds` (type: `array`):

Single ads: the Library ID the Ad Library shows on each ad, or the ad's link (facebook.com/ads/library/?id=...). Every country and status.

## `country` (type: `string`):

Two-letter country code where the ads ran (US, GB, DE, ...), or ALL for every country.

## `maxAdsPerQuery` (type: `integer`):

Stop each competitor, search or URL after this many ads. You pay per ad, so this caps the cost of each one. Ads come most impressions first (with Only new ads, most recent first, and this limits the new ads per run). About 3 minutes per 1,000 ads.

## `includeVariants` (type: `boolean`):

Meta groups ads that use the same creative and text, and its results often show one ad per group. Turn this on to also get the group's other ads (its variants: other versions, start dates, placements), each as its own ad row, which you pay for like any ad. They count toward Max ads.

## `includeAdDetails` (type: `boolean`):

Also fetch each ad's public details: the advertiser's verification and Instagram account and followers and, for ads shown in the EU or UK, the reach, reach by country, targeted ages and gender, plus the payer and beneficiary. Included in the ad price; each ad needs one more request, so runs are slower.

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

Return only ads started since the last run that this Actor hasn't returned before for the same competitor or search, reading up to 1,000 ads past Max ads to find them. You pay only for the new ads. Schedule the run (e.g. daily) to get each competitor's new ads as they launch. The first run returns the newest ads, up to Max ads.

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

Searches copied from facebook.com/ads/library in your browser. The filters in the link (country, active status, media type, dates) are used.

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

Only active ads, only ads that stopped, or both.

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

Only ads with this kind of media.

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

All ads, or only ads about social issues, elections or politics (these carry spend and impressions ranges).

## `exactPhrase` (type: `boolean`):

Match the keywords as an exact phrase instead of any order.

## `dateFrom` (type: `string`):

Only ads that were shown on or after this date (YYYY-MM-DD), whatever day they started. This is the Ad Library's own date filter.

## `dateTo` (type: `string`):

Only ads that were shown on or before this date (YYYY-MM-DD).

## `proxyConfiguration` (type: `object`):

Residential proxies are the default and recommended: Meta refuses most datacenter IPs past the first page of results.

## Actor input object example

```json
{
  "advertisers": [
    "https://www.facebook.com/Gymshark/"
  ],
  "country": "US",
  "maxAdsPerQuery": 30,
  "includeVariants": false,
  "includeAdDetails": false,
  "onlyNewAds": false,
  "activeStatus": "active",
  "mediaType": "all",
  "adType": "all",
  "exactPhrase": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

No description

## `searchSummary` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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 = {
    "advertisers": [
        "https://www.facebook.com/Gymshark/"
    ],
    "country": "US",
    "maxAdsPerQuery": 30,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("deepmine/meta-ad-spy").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 = {
    "advertisers": ["https://www.facebook.com/Gymshark/"],
    "country": "US",
    "maxAdsPerQuery": 30,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("deepmine/meta-ad-spy").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 '{
  "advertisers": [
    "https://www.facebook.com/Gymshark/"
  ],
  "country": "US",
  "maxAdsPerQuery": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call deepmine/meta-ad-spy --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,deepmine/meta-ad-spy"
        }
    }
}
```

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/4GbiBe2qAncfNyy88/builds/Gi1cT9AWPfg5HxRci/openapi.json
