# Facebook Ad Library Scraper - Competitor Ads by Keyword or Page (`tidytools/facebook-ads-library-scraper`) Actor

Find Facebook and Instagram ads by keyword or Facebook Page. Export Library ID, advertiser, start date, platforms, copy, CTA, creative links and landing URL. Failed searches are free.

- **URL**: https://apify.com/tidytools/facebook-ads-library-scraper.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** Marketing, Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 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

## Facebook Ad Library Scraper

### What it does

Search Meta's public Ad Library by keyword, Facebook Page URL, numeric Page ID or a copied Ad Library search URL and export one row per ad: advertiser, start and end dates, days running, copy, headline, display domain, landing link, CTA, creatives and platforms. The browser is rendered through the TidyTools Chrome pool with WARP egress requested. No Meta login is used. This Actor is available on the Apify Store.

### How to use: input example

```json
{"searchTerms":["nike"],"pageUrls":["https://www.facebook.com/profile.php?id=15087023444"],"country":"US","activeStatus":"all","adType":"all","mediaType":"all","startDateFrom":"2026-01-01","maxAdsPerQuery":50}
```

`searchTerms`, `pageUrls` and `adLibraryUrls` are lists; provide at least one. `pageUrls` accepts numeric IDs and public Facebook Page URLs, including vanity URLs when the page exposes its numeric ID in rendered HTML. `adLibraryUrls` takes search URLs copied from facebook.com/ads/library (with `q=`, `view_all_page_id=` or `id=`); each is rendered as-is and keeps its own country, status, ad type and media filters. An Ad Library URL pasted into `pageUrls` is handled the same way. `country` is a two-letter country code or `ALL` (default `US`); `activeStatus` is `active`, `inactive` or `all`; `adType` is `all` or `political`; `mediaType` is `all`, `image`, `video`, `meme`, `image_and_meme` or `none`; `startDateFrom` and `startDateTo` (YYYY-MM-DD) keep only ads that started running in that range; `maxAdsPerQuery` is 1–1,000 (default 50). Up to 100 queries per run.

### API example

Use an Apify API token from your own account. This example requests up to three ads and returns the dataset items:

```bash
curl -X POST 'https://api.apify.com/v2/acts/tidytools~facebook-ads-library-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"searchTerms":["nike"],"country":"US","activeStatus":"active","maxAdsPerQuery":3}'
```

### Output example

```json
{"input":"nike","searchType":"keyword","country":"US","libraryId":"100000000000000","advertiserName":"Nike","pageUrl":"https://www.facebook.com/nike/","pageId":null,"pageProfilePictureUrl":"https://cdn.example.com/nike-avatar.jpg","startedAt":"2026-06-20","endedAt":null,"activeDays":123,"active":true,"platforms":["Facebook","Instagram"],"adText":"Get the gear that goes hard on and off the field.","title":"Nike Tempo Shorts","linkDescription":null,"linkCaption":"NIKE.COM","linkUrl":"https://www.nike.com/running","cta":"Shop Now","displayFormat":"image","imageUrls":["https://cdn.example.com/ad.jpg"],"videoUrls":[],"variationCount":null,"hasMultipleVersions":true,"euTransparency":false,"spend":null,"impressions":null,"adUrl":"https://www.facebook.com/ads/library/?id=100000000000000","success":true,"errorType":null,"charged":true,"chargedEvents":["ad"],"scrapedAt":"2026-10-21T00:00:00.000Z"}
```

Each complete ad row has `input`, `searchType` (`keyword`, `page` or `url`), `country`, `libraryId`, `advertiserName`, `pageUrl`, `pageId`, `pageProfilePictureUrl`, `startedAt`, `endedAt`, `activeDays`, `active`, `platforms`, `adText`, `title`, `linkDescription`, `linkCaption`, `linkUrl`, `cta`, `displayFormat`, `imageUrls`, `videoUrls`, `variationCount`, `hasMultipleVersions`, `euTransparency`, `spend`, `impressions`, `adUrl`, `success`, `errorType`, `charged`, `chargedEvents` and `scrapedAt`.

- `endedAt` is set for stopped ads, which the Ad Library shows as a date range; `activeDays` counts days from the start to the end date, or to the run date for running ads.
- `title`, `linkDescription` and `linkCaption` (the display domain, e.g. `NIKE.COM`) come from the landing-page link box. `adText` is the ad copy outside that box, with line breaks kept.
- `pageId` is filled when the advertiser link or the Page query has a numeric ID. `pageProfilePictureUrl` is the advertiser avatar; it is not repeated in `imageUrls`.
- `displayFormat` is `video` or `image` when a media URL is visible, otherwise `null`. `hasMultipleVersions` is true when the card says the ad has multiple versions. `euTransparency` is true when the card shows an EU transparency section.
- `spend` and `impressions` are the ranges printed on political and issue ad cards (for example `<$100`), otherwise `null`.

Fields not visible in the Ad Library can be `null`, `false` or empty arrays. `SUMMARY` reports query, saved ad, charged event and free error totals, ads skipped by the start-date range (`outsideDateRange`), the run's cost plan and any queries left after a charge limit. A result is complete only if its Library ID, advertiser and start date are present.

### Pricing

The current `ad` event price is **$0.0005 per saved ad** on FREE and BRONZE, **$0.00045** on SILVER, and **$0.0004** on GOLD, PLATINUM and DIAMOND; there is no Actor start fee. The Actor saves a complete ad before charging one `ad` event. Duplicates across queries are free, and ads outside `startDateFrom`/`startDateTo` are skipped without a charge. Empty results (`no_results`), Facebook verification or login (`blocked`), rendering service overload (`service_busy`), other backend failures (`backend_error`), Page URLs without a resolvable numeric ID (`not_found`), and invalid input (`invalid_input`) each produce a free explanation row with `charged: false`. A 503 `browser_busy` response is retried once after a few seconds at no extra charge. If a later render fails after ads were saved, those ads keep their charges and the Actor adds a free error row; `SUMMARY.failed` counts it. The Actor never solves a verification challenge.

### Control your cost

`maxAdsPerQuery` caps returned ads per query. The start status and `SUMMARY.costPlan` show the most ads and their estimated price. Apify's per-run maximum charge can stop the Actor early; `SUMMARY.notProcessed` lists untouched queries. Saved rows carry the planned charge marker, while `SUMMARY.charged` records the actual accepted `ad` events if the charge call fails after saving.

### Limitations

The pool scrolls to the bottom on each render. It can render again with a longer delay until the limit or no new Library IDs; renders have separate browser contexts, so a large result set may stop below `maxAdsPerQuery` when the site does not reveal more ads in one render. A returned WARP fallback may use the pool's home egress. Live Ad Library HTML changes can affect extraction. Rendering takes browser time even for free rows.

On 2026-10-21, three separate local runs used the live rendering backend and `maxAdsPerQuery: 3`. The local runner simulated Apify FREE-tier charging; these are measured samples, not a guarantee of future yield or speed:

| Input | Dataset | `SUMMARY` | Simulated `ad` events | Elapsed |
| --- | ---: | --- | ---: | ---: |
| `{"searchTerms":["nike"],"country":"US","maxAdsPerQuery":3}` | 3 complete ads | `SUCCEEDED`; ads 3, charged 3, failed 0 | 3 ($0.0015) | 5.0 s |
| `{"pageUrls":["15087023444"],"country":"US","maxAdsPerQuery":3}` | 3 complete ads | `SUCCEEDED`; ads 3, charged 3, failed 0 | 3 ($0.0015) | 4.6 s |
| `{"searchTerms":["nike"],"country":"GB","maxAdsPerQuery":3}` | 3 complete ads | `SUCCEEDED`; ads 3, charged 3, failed 0 | 3 ($0.0015) | 4.9 s |

All nine saved rows had a Library ID, advertiser, start date and charge marker; the listed output fields matched the rows. `variationCount` was null and `videoUrls` empty in all nine. No query was blocked (0/3), but this small, capped sample does not establish a general blocked rate or how many ads each query has in total. Keyword matches can include advertisers other than Nike. A 2026-10-20 Nike/US fixture contains 29 sanitized cards; its source render yielded 29 complete rows, but that earlier render was not part of these three runs.

On that fixture (Chinese-locale render, all 29 ads active and non-political), the parser fills: headline `title` 22/29, `linkCaption` 17/29, `linkDescription` 14/29, `hasMultipleVersions` 18/29, `euTransparency` 4/29, `pageProfilePictureUrl` 29/29, numeric `pageId` 5/29 (the others link to a vanity Page URL), `activeDays` 29/29. Cards without a headline or display domain had none on the page. `endedAt`, `spend` and `impressions` were 0/29 because no card there was stopped or political; their date-range and spend formats are covered by tests on constructed cards, not yet by a live render.

### FAQ

**Do blocked, empty, or failed searches cost an ad event?** No. `blocked` means Facebook asked for verification or login; `no_results` means no complete ads were found; `service_busy` means the rendering service was overloaded; `backend_error` means the service was unavailable or returned an error; `not_found` means a Page URL did not expose a numeric ID; and `invalid_input` means the input was invalid. Each is a free explanation row. Ads saved before a later failure retain their own charge.

**Can I paste a search URL from the Ad Library website?** Yes, in `adLibraryUrls` (or `pageUrls`). The URL is rendered unchanged, so its own country, status, ad type and media filters apply; `country`, `activeStatus`, `adType` and `mediaType` from the input are not added to it. The rows' `country` is the URL's `country=` value, or null when the URL has none. `startDateFrom`/`startDateTo` still filter the returned rows.

**Can I pass a vanity Page URL?** Yes when its public rendered page exposes a numeric Page ID. Otherwise the free `not_found` row asks you to enter the numeric ID directly.

### Local tests

`npm test` checks parsing, input URL construction and local charge/error behavior. The three measurements above used `webcapture/tools/local_run.mjs`; local charge events are simulated and do not verify Apify billing or a hosted run.

# Actor input Schema

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

Search public Ad Library ads by keyword; one phrase per line.

## `pageUrls` (type: `array`):

Numeric Facebook Page IDs or public Facebook Page URLs. Vanity URLs require one extra browser render to resolve their ID. An Ad Library search URL pasted here is treated like an entry in Ad Library URLs.

## `adLibraryUrls` (type: `array`):

Search URLs copied from facebook.com/ads/library (with q=, view_all_page_id= or id=). Each URL is rendered as-is and keeps its own country, status, ad type and media filters.

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

Two-letter country code, or ALL for every country.

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

Active ads only, inactive (stopped) ads only, or both.

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

All ads or only political and issue ads.

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

Ad Library media filter.

## `startDateFrom` (type: `string`):

Only ads that started running on or after this date (YYYY-MM-DD). Ads outside the range are skipped and not charged.

## `startDateTo` (type: `string`):

Only ads that started running on or before this date (YYYY-MM-DD).

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

Stop after this many complete ads per keyword, Page or Ad Library URL.

## Actor input object example

```json
{
  "searchTerms": [
    "nike"
  ],
  "country": "US",
  "activeStatus": "active",
  "adType": "all",
  "mediaType": "all",
  "maxAdsPerQuery": 50
}
```

# Actor output Schema

## `ads` (type: `string`):

No description

## `full` (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 = {
    "searchTerms": [
        "nike"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/facebook-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 = { "searchTerms": ["nike"] }

# Run the Actor and wait for it to finish
run = client.actor("tidytools/facebook-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 '{
  "searchTerms": [
    "nike"
  ]
}' |
apify call tidytools/facebook-ads-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/facebook-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/8kv78HtHXhbcyibyK/builds/dOmLppEU0nQcb0uzM/openapi.json
