# Meta Ad Search (`crawlix/meta-ad-search`) Actor

Meta Ad Search extracts keyword, ad archive id, is active, start date, page name, page id, paid partnership, display format, body text, caption, cta type, videos, cards, publisher platforms, reach estimate for every row. Inputs: keyword list, platform filter, ad status filter, ads per keyword.

- **URL**: https://apify.com/crawlix/meta-ad-search.md
- **Developed by:** [Crawlix](https://apify.com/crawlix) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 ad collecteds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

The **Meta Ad Search** turns a topic into a table of ads. Type a keyword such as a product, a brand or a niche, and it collects the public Meta Ad Library ads that match: who runs them, what they say, where they lead, which platforms they appear on and when they started. Narrow the results by platform and by running or stopped ads, set how many ads each keyword should return, and the rows arrive in your dataset ready to export.

It is a Facebook Ad Library scraper for the moment you do not yet know which advertisers to watch. Scrape Meta ads by keyword, see who shows up, then follow the interesting pages one by one. Every ad is saved the moment it is found, so a run that ends early still leaves you everything it had collected.

***

### Overview & Core Capabilities: Meta Ad Search

The **Meta Ad Search** answers one question: who is advertising on this topic right now, and what are they saying? Finding out by hand means opening the Ad Library, typing a word, scrolling, and copying ads one after another. This Meta Ad Library scraper takes your keywords instead and returns a single table, with one row per ad and the keyword that found it written on every row.

What you choose and what comes back:

| You choose | You get |
| :--- | :--- |
| One keyword or many | A Meta ads keyword search for each one, collected separately and tagged on every row |
| A platform, or all of them | Ads that run on Instagram, Facebook, Messenger, Audience Network, WhatsApp or Threads, or on any platform |
| Running, stopped or both | Ads filtered by status, with an `end_date` on the ones that have stopped |
| A number of ads per keyword | Exactly that many different ads, whenever the keyword has that many |
| Nothing more | Advertiser page, ad text, headline, links, images, videos, cards, platforms and dates, plus impression and spend ranges where Facebook publishes them |

A keyword that finds nothing, or that cannot be completed, never stops the others. Because ads are saved one at a time, the dataset is useful even when you stop a run yourself.

Run the Meta Ad Search once to size up a niche, or schedule it every week to watch a topic change. The rows keep the same shape either way, so spreadsheets, dashboards and automations built on one run keep working on the next. That repeatability is what teams look for in a Facebook Ad Library scraper used for ongoing Meta ads keyword search.

***

### Practical Use Cases & Applications

People reach for the **Meta Ad Search** when the question starts with a topic instead of a company:

1. **Find the advertisers in a niche**: search the words your customers use and list every page that advertises around them, with likes and categories, so you know whom to watch.
2. **Build a swipe file**: scrape Meta ads by keyword and gather headlines, body text and buttons for a product category to see which hooks and offers keep coming back.
3. **Check a rival's footprint**: run a Meta ads keyword search on a competitor's brand name and see which ads mention it, including ads from resellers and partners.
4. **Size up a launch idea**: before you spend on a new product, look at how crowded its keyword is. The run log tells you roughly how many matching ads Facebook holds.
5. **Compare channels**: use the Meta Ad Library scraper once per platform with the same keyword and compare formats and counts to see where a category puts its money.
6. **Track seasons and promotions**: schedule the same search every week and compare `start_date` values to see when sales and campaigns begin.
7. **Follow the traffic**: the `link_url` column shows which landing pages and booking sites advertisers send people to.
8. **Review disclosed ads**: for transparency work, scrape Meta ads by keyword and keep the ones that publish impression and spend ranges.

Whatever the question, the steps are the same: write the keywords, set the filters, and start the Meta Ad Search.

***

### Target Audiences & Engineering Teams

The **Meta Ad Search** is for anyone who needs ad data by topic without collecting it by hand:

| Who | What they use it for |
| :--- | :--- |
| Media buyers | Checking which angles a category is running before a launch |
| Agency strategists | Pitching with a map of who advertises around a client's topics |
| E-commerce founders | Spotting products and creatives that advertisers keep funding |
| Market researchers | Taking repeatable samples of public advertising by topic |
| Journalists and watchdogs | Looking into advertising around news and social topics |
| Data and engineering teams | Loading results into a warehouse or dashboard through the Apify API |
| Sales and growth teams | Building lists of active advertisers in a market |

No code is needed to use this Facebook Ad Library scraper, and anyone who wants to scrape Meta ads by keyword can start from the Apify Console: fill in the **Input** tab and press Start. Developers can call the same actor from code and receive the rows as JSON.

***

### Key Architectural Advantages & Reliability

Looking things up in the Ad Library by hand is slow, hard to repeat and easy to lose, and browser extensions break whenever the page changes. As a Facebook Ad Library scraper, the **Meta Ad Search** is built around six promises:

1. **The same columns every time.** A Meta ads keyword search with the same input returns the same columns, so last week's file and this week's file line up.
2. **Precisely the count you set.** Repeats never count towards the target, so 50 ads per keyword means 50 different ads.
3. **Nothing is lost.** Each ad is saved the moment it is found. If a run ends early for any reason, the ads already collected stay in your dataset and the log says why it stopped.
4. **Keywords stay independent.** One that cannot be finished is skipped and the next one begins right away.
5. **Small hiccups do not matter.** A brief temporary problem is retried once and does not end the run or spoil the results.
6. **Spending stays under your cap.** Set a maximum cost for a run and the Meta Ad Search halts the moment it is reached, with everything collected so far kept.

The log of this Facebook Ad Library scraper stays short and readable: how many ads are collected, how many are left, and nothing else.

***

### Quickstart: Step-by-Step Execution Guide

Your first table from the Meta Ad Search takes about a minute:

1. In the Apify Console, open the actor and go to its **Input** tab.
2. Write the keywords for your Meta ads keyword search, one per line. The prefilled example is `good food`.
3. Pick a **Platform**, or keep **All platforms**.
4. Pick an **Ad Status**: running ads, stopped ads or both. A specific Platform or Ad Status narrows the results and adds a small extra charge per ad; the defaults add nothing.
5. Choose **Ads per Keyword**. The default of 10 is enough to judge the output; raise it afterwards. A free Apify account collects up to 50 ads in total per run, and a paid plan has no limit.
6. Press **Start** and watch the log.
7. When the run has finished, check the table in the **Output** tab and download it as JSON, CSV, Excel, XML or HTML.

That is the whole routine to scrape Meta ads by keyword.

The log of this Meta Ad Library scraper moves through four phases. This is a real run with two keywords, Instagram only and running ads only, on a paid plan:

```text
============================================================
  Meta Ad Search
============================================================
[1/4] Preparing
      Keywords:  2
      Platform:  Instagram
      Status:    Active only
      Target:    40 ads per keyword
      Plan:      Paying user detected - no limit on ads
[2/4] Searching
      [1/2] "running shoes" - preparing the search...
      [1/2] "running shoes" - search ready, about 50,000+ matching ads found
[3/4] Collecting
      [1/2] "running shoes" [#######---] 30 of 40 ads | 10 left | 1s
      [1/2] "running shoes" - completed, 40 of 40 ads collected
      [2/2] "yoga mat" - preparing the search...
      [2/2] "yoga mat" - search ready, about 15,518 matching ads found
      [2/2] "yoga mat" [#######---] 30 of 40 ads | 10 left | 4s
      [2/2] "yoga mat" - completed, 40 of 40 ads collected
[4/4] Finished
      80 ads from 2 keyword(s) in 5s
      1. running shoes | 40/40 ads | Completed
      2. yoga mat      | 40/40 ads | Completed
============================================================
```

The Plan line tells you which kind of account the run detected. On a free account it reads "Free user detected - up to 50 ads per run", followed by how to upgrade.

While a slow step is working, a progress line appears about every 20 seconds, so a healthy run never looks frozen.

***

### Input Parameters & Configuration Reference

Every setting of the **Meta Ad Search** is optional except the keywords:

| Setting | Control | Default | What it does |
| :--- | :--- | :--- | :--- |
| **Keywords** | text list | `good food` | What to look for, one keyword per line. Words on the same line are searched together, and each line is collected separately and written on its rows. |
| **Platform ($)** | dropdown | All platforms | Keep only the ads that run on one platform: Instagram, Facebook, Messenger, Audience Network, WhatsApp or Threads. A specific platform adds a small extra charge for each ad collected. |
| **Ad Status ($)** | dropdown | Active & inactive | Live ads only, finished ads only, or both. Active only or Inactive only adds a small extra charge for each ad collected. |
| **Ads per Keyword** | number box | `10` | How many unique ads to collect per keyword. There is no upper limit; once Facebook has no more ads for the keyword, the run ends by itself. A free Apify account collects up to 50 ads in total per run, across all keywords. |
| **Connection Country** | dropdown | United States (US) | Where the run connects from, chosen from more than 150 countries. If that country is unavailable at the moment, the run connects from the United States and says so in the log. |

How the settings combine:

- The platform and the status are shared by every keyword in a run. To compare them, start one run per option.
- Each filter that leaves its default adds its own small extra charge to every ad collected. With both defaults there is no extra charge, and ads that do not match your filters are never collected or charged.
- **Ads per Keyword** counts per keyword: three keywords at 100 ads can return up to 300 ads.
- A broad keyword has far more matches than you need, while a narrow one may have fewer than you ask for. The run tells you which. Every keyword is its own Meta ads keyword search.

If a value is wrong, the run explains the fix instead of stopping: a web address in the keyword box is skipped, a number out of range is corrected and an unknown option falls back to its default. Only when no usable keyword remains does the run end early.

***

### Extracted Dataset Schema & Field Definitions

Each ad collected by the Meta Ad Search becomes one row, so results from this Meta Ad Library scraper line up across keywords and across runs. A field with no value is left out of the row, which means a field shows up only when the ad has something to show; nothing is invented. Nothing Facebook sends is thrown away: details without their own column sit in `technical_data`. Values are written for people: dates read `2026-05-26` and codes are spelled out, such as Paid for by, Shop now or Audience Network. Every row has `number`, `keyword`, `ad_archive_id` and `ad_library_url`, and nearly every ad also has `is_active` and `start_date`.

| Field | Type | What it contains |
| :--- | :--- | :--- |
| `number` | number | The row's position within its keyword, starting at 1 with no gaps. |
| `keyword` | text | The keyword that surfaced the ad, copied from your input. |
| **`ad_archive_id`** | text | **The ad's permanent Ad Library ID. Use it as the unique key.** |
| `ad_library_url` | text | Opens this exact ad in the Meta Ad Library. |
| `is_active` | on / off | True while the ad is still running, false once it has stopped. |
| `start_date` | date | The first day the ad ran, written YYYY-MM-DD. |
| `end_date` | date | The last day the ad ran; absent while it is still running. |
| `page_name` | text | The name of the Facebook page that runs the ad. |
| `page_id` | text | That page's numeric ID. |
| `page_profile_url` | text | A link to the page on Facebook. |
| `page_profile_picture_url` | text | A link to the page's profile picture. |
| `page_like_count` | number | How many people like the page. |
| `page_categories` | list | The categories the page is listed under. |
| `byline` | text | Who paid for or published the ad, on ads that carry a disclaimer. |
| `disclaimer_label` | text | The kind of disclaimer, for example Paid for by or Published by. |
| `paid_partnership` | object | The partner page behind a paid partnership; absent for ordinary ads. |
| `display_format` | text | The ad's build: Image, Video, Carousel, Multi images, Multi videos, Dynamic product ad or Dynamic creative ad. |
| `body_text` | text | The ad's main copy. |
| `title` | text | The headline. |
| `caption` | text | The short line under the headline, usually the website. |
| `link_url` | text | The destination the ad sends people to. |
| `link_description` | text | The description shown next to the link. |
| `cta_text` | text | The words on the call-to-action button. |
| `cta_type` | text | The button's type, for example Shop now or Learn more. |
| `images` | list | Every picture in the ad; each entry holds Facebook's own fields such as `original_image_url` and `resized_image_url`. |
| `videos` | list | Every video in the ad; each entry holds `video_hd_url`, `video_sd_url` and `video_preview_image_url`. |
| `cards` | list | Each card of a carousel or catalogue ad, with its own text, link, images and videos. |
| `publisher_platforms` | list | Which apps the ad shows up in: Facebook, Instagram, Messenger, Audience Network, WhatsApp, Threads. |
| `collation_count` | number | How many near-identical ads Facebook has folded into this one. |
| `impressions` | text | The impressions range, for ads that publish it. |
| `spend` | text | The spend range, for ads that publish it. |
| `currency` | text | The currency of the spend range. |
| `reach_estimate` | text | The estimated reach range, for ads that publish it. |
| `categories` | list | Facebook's classification of the ad: Unknown for an ordinary ad, or Political, Credit or Housing. |
| `technical_data` | object | Everything else Facebook sends about the ad, including fields it adds later. |

#### Sample JSON Dataset Record

This is a live ad found by the keyword `hotel booking`: an airline's own page running a Dynamic creative ad as a paid partnership with its holidays arm.

```json
{
  "number": 59,
  "keyword": "hotel booking",
  "ad_archive_id": "2116288212591351",
  "ad_library_url": "https://www.facebook.com/ads/library/?id=2116288212591351",
  "is_active": true,
  "start_date": "2026-10-01",
  "page_name": "SriLankan Airlines",
  "page_id": "111452288938705",
  "page_profile_url": "https://www.facebook.com/flysrilankan/",
  "page_profile_picture_url": "https://scontent.fbcdn.net/v/t39.35426-6/82963217...",
  "page_like_count": 854870,
  "page_categories": ["Airline Service"],
  "paid_partnership": {
    "page_id": "963104166886523",
    "page_is_deleted": false,
    "page_name": "SriLankan Holidays ",
    "page_profile_url": "https://www.facebook.com/61586666922143/"
  },
  "display_format": "Dynamic creative ad",
  "body_text": "Get exclusive holiday rates for SriLankan Airlines flight and a stay at Ramada hotel when you book your Sri Lanka getaway with SriLankan Holidays!",
  "title": "SriLankan Holidays website launch offer",
  "caption": "booking.srilankanholidays.com",
  "link_url": "https://booking.srilankanholidays.com/holiday-packages/?active...",
  "cta_text": "Book now",
  "cta_type": "Book travel",
  "cards": [
    {
      "body": "Get exclusive holiday rates for SriLankan Airlines flight and a stay at Ramada hotel when you book your Sri Lanka getaway with SriLankan Holidays!",
      "cta_type": "Book travel",
      "link_url": "https://booking.srilankanholidays.com/holiday-packages/?active...",
      "title": "SriLankan Holidays website launch offer",
      "cta_text": "Book Now",
      "original_image_url": "https://scontent.fbcdn.net/v/t39.35426-6/82964772...",
      "resized_image_url": "https://scontent.fbcdn.net/v/t39.35426-6/83097799..."
    },
    {
      "body": "Get exclusive holiday rates for SriLankan Airlines flight and a stay at Ramada hotel when you book your Sri Lanka getaway with SriLankan Holidays!",
      "cta_type": "Book travel",
      "link_url": "https://booking.srilankanholidays.com/holiday-packages/?active...",
      "title": "SriLankan Holidays website launch offer",
      "cta_text": "Book Now",
      "original_image_url": "https://scontent.fbcdn.net/v/t39.35426-6/8306954...",
      "resized_image_url": "https://scontent.fbcdn.net/v/t39.35426-6/8301782..."
    }
  ],
  "publisher_platforms": ["Facebook", "Instagram", "Audience Network"],
  "collation_count": 7,
  "categories": ["Unknown"],
  "technical_data": {
    "collation_id": "1445688060825088",
    "page_is_deleted": false,
    "has_user_reported": false,
    "impressions_with_index": { "impressions_index": -1 },
    "gated_type": "ELIGIBLE",
    "is_aaa_eligible": false,
    "contains_digital_created_media": false,
    "contains_sensitive_content": false,
    "regional_regulation_data": {
      "finserv": { "is_deemed_finserv": false, "is_limited_delivery": false },
      "tw_anti_scam": { "is_limited_delivery": false }
    },
    "hide_data_status": "NONE",
    "snapshot": { "page_is_deleted": false, "is_reshared": false }
  }
}
```

Long texts and media links are shortened here for readability, and only two of the ad's cards are shown. This ad has no `images`, `videos`, `end_date`, `link_description`, `byline`, `impressions` or `spend`, so those fields are not in the row. Rows for other ads carry whichever of them have a value.

***

### Operational Constraints & Platform Limits

A few limits to keep in mind when planning with the **Meta Ad Search**:

- Everything this Meta Ad Library scraper returns comes from the public Meta Ad Library, with no login and nothing private involved, so the dataset shows what any visitor to the library could see.
- Only some ads, mainly political and social-issue ones, come with impression, spend and reach ranges. For the rest, those three columns do not exist in the row.
- Facebook reports at most 50,000+ matches for a search, and it can stop returning ads before your number is reached. When that happens the run closes the keyword, says so in the log and keeps everything collected. To scrape Meta ads by keyword beyond that, split a broad topic into narrower keywords or run once per Platform or Ad Status.
- Facebook applies the platform filter itself. For some platforms, WhatsApp above all, it also returns ads that do not list that platform in `publisher_platforms`. The Meta Ad Search leaves those ads out, does not charge for them and tells you how many it skipped, so every ad you receive runs on the platform you picked.
- Media links lead to Facebook's own servers and stop working after a time, so save any files you need soon after the run.
- A catalogue ad's text such as `{{product.name}}` is only a template; read the real product details in `cards`.
- A keyword can match fewer ads than you ask for. The run then reports "no more ads available" and keeps what it found.
- You pay per ad collected, at the rate on the actor's pricing tab, and a spending limit set for the run always holds. Picking a non-default Platform or Ad Status adds a separate small charge per ad, shown on the same tab.
- On a free Apify account a run collects at most 50 ads in total, counting all keywords together, and then ends normally with everything saved. Paid plans have no such limit. The log names the account type it detected at the start of every run.
- Keep to the law: how you store and use information about advertisers, and any rules on contacting them, are your responsibility.

***

### Best Practices & Performance Optimization

Habits that get more out of the **Meta Ad Search** as a Meta Ad Library scraper:

- **Start with 10**: the first time you scrape Meta ads by keyword, keep the default of 10 ads per keyword, check the columns you need and only then raise the number.
- **Match the keyword to the question**: a brand name or a specific product returns focused ads, while a one-word keyword such as `food` matches tens of thousands.
- **One platform per run for comparisons**: run the same keywords once per platform and compare `publisher_platforms` and `display_format`.
- **Live against finished**: Active only shows what advertisers push today, and Inactive only shows past campaigns worth learning from.
- **Run it on a timer**: schedule the Meta Ad Search in Apify and compare `start_date` values between weeks to find new ads.
- **Set a cost ceiling**: for a big search, put a maximum cost on the run in the Apify Console first.
- **Keep the default memory**: this Facebook Ad Library scraper uses little, even for long keyword lists.
- **Connect from the target market**: choose the connection country nearest to the audience you are studying.
- **Merge runs by `ad_archive_id`**: it is the unique key to de-duplicate on when you combine files.

***

### Troubleshooting & Frequently Asked Questions

#### Frequently Asked Questions

**Do I need to log in to Facebook?**
No login of any kind. This Facebook Ad Library scraper reads only what the public Meta Ad Library shows, so you never hand over an account or a password.

**What can I export from this Meta Ad Library scraper?**
Anything the Output tab offers: JSON, CSV, Excel, XML or HTML, and the same through the Apify API.

**How are the words of a Meta ads keyword search handled?**
Words on the same line are searched together as one keyword. Put different topics on separate lines and each one is collected on its own.

**What does "about 50,000+ matching ads found" mean?**
It is Facebook's own estimate of how many ads match the keyword, capped at 50,000+. The run collects up to the number you set in Ads per Keyword, not the whole estimate.

**Is there a limit on a free Apify account?**
Yes. A run on a free account collects at most 50 ads in total, with all keywords counted together, then stops normally and keeps everything it saved. Each keyword still has its own Ads per Keyword, so three keywords at 20 ads would want 60 ads: a free account gets 50 of them, a paid plan gets all 60.

**Is the free limit per day or per account?**
Per run. Every run on a free account can collect up to 50 ads, and the limit starts again with the next run. It is not a daily or lifetime allowance. Upgrade your Apify plan to remove it.

**The log says "Free user detected" but I am on a paid plan. What do I do?**
Open an issue in the **Issues** tab of this actor and tell us which plan you use and the run ID. We will look into it and fix it as soon as we can. Until then, your run keeps working within the free limit.

**Do the filters cost extra?**
A little, and only while a filter is off its default. A specific Platform adds one extra charge per ad, and Active only or Inactive only adds another. With All platforms and Active & inactive nothing extra is charged. The rates are on the actor's pricing tab.

**Why does the log mention ads skipped for being outside my filters?**
Facebook sometimes returns an ad that does not really run on the chosen platform, most often with WhatsApp. The Meta Ad Search drops it and does not charge for it, so everything in your dataset matches your filters, and your target is counted in matching ads only.

**What separates Active from Inactive ads?**
Active means the ad is live today. Inactive means it has stopped, and its `end_date` says when. Active & inactive gives you both.

**Why are some rows longer than others?**
A field with no value is left out, and every field Facebook provides is kept, so an ad with more details has more columns. CSV and Excel exports list every column that appears in any row.

**Why does a row have no `end_date`?**
The ad has not stopped yet, so there is no end date to show.

**Is the number of ads exactly what I set?**
Yes, as long as the keyword has that many matching ads. Repeats are skipped and never counted, so every ad you receive is different.

**Why did I get fewer ads than I set for a keyword?**
Either Facebook ran out of matching ads for that keyword and those filters (the log says "no more ads available"), or it stopped returning ads for a very large search before your limit (the log says that too). Try a narrower keyword, All platforms or Active & inactive, or run again later.

**Where are the impression and spend ranges?**
Facebook publishes them only for some ads, mostly political and social-issue ads. Other ads simply have no `impressions` or `spend` column.

**Why does a catalogue ad show text like `{{product.name}}`?**
Catalogue ads are templates. The real product titles, text and images are in `cards`.

**Can I give a profile link or an advertiser name?**
A keyword that matches an advertiser's name finds their ads, but web addresses typed into the keyword box are skipped. To collect everything one specific page runs, use [Meta Ad Scraper](https://apify.com/Crawlix/meta-ad-scraper) with the page's link or username.

**Why does `page_name` differ from the brand in the ad?**
For a paid partnership the publishing page is a partner's, while the second brand is listed in `paid_partnership`.

#### Error Resolution Guidelines

These are the messages you may meet in the log of the Meta Ad Search, and what to do about them:

| What you see | What it means | What to do |
| :--- | :--- | :--- |
| `Ignored - keywords[1] is a web address` | A link was typed where a keyword belongs. | Write the words you want to find instead; the other keywords still run. |
| `Adjusted - ... is not one of the options` | A setting held a value outside its list. | Nothing: the run used the default and named it. |
| `no ads found for this keyword with the current filters` | No ad matched the words and filters. | Try other words, All platforms or Active & inactive. |
| `Moving on to the next keyword` | A keyword could not be completed after one short retry. | Ads gathered so far are kept; run the keyword again later to get the rest. |
| `Facebook stopped returning ads for this keyword` | Facebook ended a very large search before your number was reached. | Collected ads are kept. Narrow the keyword, change Platform or Ad Status, or retry later. |
| `Spending limit reached - stopping here` | The maximum cost you set was reached. | Raise the limit and run again for more. |
| `Free user detected - up to 50 ads per run` | Your account is on the free plan, so a run collects at most 50 ads. | Upgrade your Apify plan to collect more. On a paid plan? Open an issue in the Issues tab. |
| `Free plan limit reached - 50 ads per run` | The run reached the free plan's 50 ads. | The ads are in the dataset. Upgrade your Apify plan to collect more. |
| `Stopped early` with a reference | Something unforeseen ended the run. | Whatever was collected is already in the dataset. Start again, and quote the reference in an issue if it keeps happening. |

***

### Code Integration: Python, JavaScript & REST API

Use this Facebook Ad Library scraper inside your own scripts, as the **Meta Ad Search**, and get every row back as JSON.

#### Python Integration Example

```python
from collections import Counter
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

## Meta ads keyword search: which pages advertise on these topics?
run = client.actor("Crawlix/meta-ad-search").call(run_input={
    "keywords": ["running shoes", "yoga mat"],
    "platform": "instagram",
    "active_status": "active",
    "max_ads": 200,
})

pages = Counter()
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
    pages[(ad["keyword"], ad.get("page_name"))] += 1

for (keyword, page), ads in pages.most_common(10):
    print(f"{keyword:15} {page}: {ads} ads")
```

#### JavaScript / Node.js Integration Example

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });

const run = await client.actor('Crawlix/meta-ad-search').call({
    keywords: ['hotel booking'],
    active_status: 'active',
    max_ads: 50,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const videos = items.filter((ad) => ad.display_format === 'Video');
console.log(`${videos.length} of ${items.length} ads are videos`);
```

#### Direct cURL REST API Execution

```bash
## Scrape Meta ads by keyword in one call
curl -X POST "https://api.apify.com/v2/acts/Crawlix~meta-ad-search/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["good food"],
    "platform": "all",
    "active_status": "all",
    "max_ads": 10
  }'
```

For a long run, start it asynchronously and read the dataset once it has finished, rather than holding one call open.

***

### Technical Architecture Glossary

| Term | Meaning |
| :--- | :--- |
| **Run** | A single start of the actor with the input you filled in. |
| **Dataset** | The table that stores all ad rows; export it from the Output tab or read it with the API. |
| **Meta Ad Library** | Meta's open archive of the ads that run across its apps. |
| **Row** | One ad, as saved in the dataset. |
| **Ad ID** | An ad's permanent Ad Library identifier. It is the `ad_archive_id` column. |
| **Keyword** | A line of words you search for. Each keyword is collected on its own. |
| **Meta ads keyword search** | The search for one keyword with the filters you chose. |
| **Ads per Keyword** | How many unique ads one keyword may collect in a run. |
| **Platform** | The app an ad shows up in: Facebook, Instagram, Messenger, Audience Network, WhatsApp or Threads. |
| **Match estimate** | The "about N matching ads found" figure in the log: Facebook's own count for the keyword. |
| **Extra charge** | A small added charge on each ad collected whenever the Platform or the Ad Status is not on its default. |
| **Free plan limit** | The most ads one run can collect on a free Apify account: 50 in total, across all keywords. |
| **Schedule** | An Apify feature that launches the actor on a timer, such as every week. |
| **Spending limit** | The cost ceiling you put on a run. When it is hit, the run stops. |

***

### Complementary Scrapers in the Ecosystem

If the **Meta Ad Search** shows you advertisers worth following, its companion tool collects everything one of them runs.

- [Meta Ad Scraper](https://apify.com/Crawlix/meta-ad-scraper) — collects all ads of one Facebook profile or page from its link or username.

Other Crawlix actors are listed at [apify.com/Crawlix](https://apify.com/Crawlix).

***

### Support & Feedback Channels

We keep the Meta Ad Search up to date, so this Meta Ad Library scraper follows changes in the Meta Ad Library.

If something looks wrong, a column seems off, or you want a new feature:

1. Go to the actor's page in the Apify Console and open **Issues**.
2. Describe the problem and include your **Run ID** and the input you used.
3. We read the run log and release fixes quickly.

# Actor input Schema

## `keywords` (type: `array`):

Type one keyword or phrase per line, for example a brand, a product, an offer or a topic. Words on the same line are searched together, every line is searched on its own, and each ad in the results shows which keyword found it.

## `platform` (type: `string`):

Choose where the ads you collect must run: Instagram, Facebook, Messenger, Audience Network, WhatsApp or Threads. All platforms (the default) collects ads from every platform and adds no extra charge. Choosing one platform adds a small extra charge for each ad collected, shown on the pricing tab. Ads that do not run on the platform you chose are left out and never charged.

## `active_status` (type: `string`):

Choose which ads to collect by their state: Active only for ads that are running now, Inactive only for ads that have stopped, or Active & inactive (the default) for both, with no extra charge. Choosing Active only or Inactive only adds a small extra charge for each ad collected, shown on the pricing tab.

## `max_ads` (type: `integer`):

How many different ads to collect for each keyword. The same ad is never counted twice, so you get exactly this many whenever that many exist. There is no upper limit: the run stops by itself when no more ads are available for the keyword and keeps everything it collected. The default of 10 is a quick first test. A free Apify account collects up to 50 ads in total per run, across all keywords; a paid plan has no limit.

## `proxyCountry` (type: `string`):

The country your run connects from, chosen from more than 150 countries. Pick the one closest to the market you are researching; the default is the United States. If the chosen country is not available right now, the run connects from the United States instead and the log says so.

## Actor input object example

```json
{
  "keywords": [
    "good food"
  ],
  "platform": "all",
  "active_status": "all",
  "max_ads": 10,
  "proxyCountry": "US"
}
```

# Actor output Schema

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

Every row this run delivered. Use the Export button on the Output tab for CSV, Excel, JSON or XML.

# 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 = {
    "keywords": [
        "good food"
    ],
    "platform": "all",
    "active_status": "all",
    "max_ads": 10,
    "proxyCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlix/meta-ad-search").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 = {
    "keywords": ["good food"],
    "platform": "all",
    "active_status": "all",
    "max_ads": 10,
    "proxyCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("crawlix/meta-ad-search").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 '{
  "keywords": [
    "good food"
  ],
  "platform": "all",
  "active_status": "all",
  "max_ads": 10,
  "proxyCountry": "US"
}' |
apify call crawlix/meta-ad-search --silent --output-dataset

```

## MCP server setup

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

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/g2hng9gjsqyBXVNQY/builds/NDBERsn8II82Z9nQR/openapi.json
