# Facebook Ads Library Radar (`titan_coder/facebook-ads-library-radar`) Actor

Ads Library scrapers re-download a competitor's entire ad set every single run, so you diff it yourself and pay twice for ads you already had. This one tells you what is NEW since your last check — nothing else. Blocked and failed attempts are never billed.

- **URL**: https://apify.com/titan\_coder/facebook-ads-library-radar.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Marketing, Social media
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Facebook Ads Library Radar 📢

**Watch a Facebook Page or keyword search in the Ads Library and get paid-for alerts only when a
genuinely NEW ad appears — not a one-shot dump, and not a bill for every check.**

Every existing Facebook Ads Library Actor on the Store is a one-shot pull: run it, get today's ads, done.
This one remembers which ads it already delivered and only charges when a real, new ad shows up since the
last check.

### What you need

A **Bright Data Scraping Browser** endpoint (`wss://<customer>:<password>@brd.superproxy.io:9222`) —
Bright Data gives 5,000 free requests/month with no card. Facebook's Ads Library blocks plain automated
requests; this Actor connects through a real remote browser, which is what gets past that check.

### How it works

1. Give it a `monitorId` and a list of targets — a Facebook Page ID, or a keyword search with a country.
2. Each run opens every target through your Bright Data browser and compares the ads found against ads
   already delivered for that exact target.
3. **You pay for a confirmed check** (`target-check-confirmed`) only when the page genuinely loaded and
   data was extracted — a failed/blocked/timed-out attempt is never billed to you.
4. **You pay for a new ad** (`new-ad-detected`) once per ad that genuinely wasn't seen before for that
   target — checking a target with nothing new costs only the check fee.

### Input

| Field | What it does |
|---|---|
| `monitorId` | Names this monitor's durable history — required. |
| `targets` | 1–15 objects: `{"pageId": "123456789"}` or `{"query": "nike", "country": "US"}`. The query/country (or page ID) is locked once a target is first used — a later run with a different query under the same target fails loudly instead of silently mixing histories. |
| `cdpUrl` | Your Bright Data Scraping Browser endpoint. Required, never logged or stored in plain form. |
| `notifyOn` | `new_alerts` (default) fires the webhook only when paid new ads were delivered. |
| `webhookUrl` | HTTPS only; delivers a digest of paid new ads as JSON. |

### Output

- **Dataset** — one row per new ad: target, ad archive ID, page name, active status, start date, link to
  the ad in the Ads Library. Unchanged checks don't appear in the Dataset.
- **Run status** — clear reasons on bad input, a Bright Data connection failure, or a run that ran out of
  time.

### What this does NOT promise

**Known limitation: this is a top-N snapshot, not a full census of a target's active ads.** The Ads Library
page itself only ever renders a page's worth of results ordered by Facebook's own relevance/recency
ranking — there is no API count of "how many ads this Page or query really has active", and no pagination
in this Actor. Each run stabilizes and delivers whatever that single page shows *at the time of the run*
(see "How it works, in more detail" below for what "stabilizes" means); it cannot guarantee it has seen
*every* ad a target currently has running, only that what it did see was a settled, non-partial list for
that run. If a target is running more ads than fit on that first page, the ones pushed off the visible page
by newer/more-relevant ads are invisible to this Actor until they resurface — this is a property of what
Facebook's Ads Library page exposes, not something an Actor without server-side pagination or a
declared-total signal can close. In practice, for the common use case (watching a specific advertiser or
keyword for *new* ads as they launch) this is normally enough, because a newly launched ad tends to rank
high enough to appear on that first page — but it is not a guarantee, and a large volume spike between two
runs (more new ads appearing than fit on one page) is not guaranteed to be fully covered.

We do not trade this away for a fix that doesn't exist: Facebook's Ads Library has no `total_count` or
`declared_count` field to compare a snapshot against (unlike some other sources this Actor's sibling Actors
watch), so there is no way to *detect* "the page currently showing fewer ads than the target really has" —
only to make sure that what is shown has finished loading before it's trusted (see below).

### How it works, in more detail

Facebook renders the Ads Library page's results client-side, after the initial page load — the ad list
fills in asynchronously as the page hydrates. Catching that list too early is not a cosmetic issue: an
early, partially-hydrated read (say, 30 ads visible so far out of what would eventually settle at 150) that
gets treated as final would make that partial list a target's permanent "baseline" — every ad that finishes
loading a moment later would then look like a brand-new ad on the *next* run, and would be billed as one,
even though it existed all along. To avoid that, each run polls the page repeatedly and only accepts the ad
list once the exact same set of ad IDs is returned on two consecutive polls in a row. If that never happens
within the run's extraction budget, the run does **not** guess with whatever it has accumulated so far — it
fails that target's check for this run (uncharged) instead of risking a partial snapshot becoming a
watch's baseline or corrupting its "already seen" history. The next run tries again from a clean page load.

### A note on the source

Facebook shows a bot-detection challenge to plain automated requests. This Actor gets past it by connecting
to a real remote browser session (your Bright Data Scraping Browser) rather than sending a raw HTTP
request. Ad data comes from JSON embedded in the Ads Library page itself — not an official API; the page
structure could change at any time.

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars).

## `targets` (type: `array`):

1-15 objects: {"pageId": "123456789"} to watch one Facebook Page's ads, or {"query": "nike", "country": "US"} to watch a keyword search. New targets can be added later under the same monitorId; changing query/country under an existing target fails loudly instead of silently mixing histories.

## `cdpUrl` (type: `string`):

Optional. Leave EMPTY to use the built-in stealth anti-detect browser — no third-party account or key needed. Provide your own Bright Data Scraping Browser endpoint only if you prefer to route through your own browser. The backend is never switched automatically and is reported in the run's coverage.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when new paid ads were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (paid) new ads as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-brands",
  "targets": [
    {
      "query": "nike",
      "country": "US"
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: targetKey, adArchiveId, pageName, isActive, startDate, discoveredAt.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-target status and reason, rows delivered and rows billed. Enough to reconcile every charge against every row.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "my-brands",
    "targets": [
        {
            "query": "nike",
            "country": "US"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/facebook-ads-library-radar").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 = {
    "monitorId": "my-brands",
    "targets": [{
            "query": "nike",
            "country": "US",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/facebook-ads-library-radar").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 '{
  "monitorId": "my-brands",
  "targets": [
    {
      "query": "nike",
      "country": "US"
    }
  ]
}' |
apify call titan_coder/facebook-ads-library-radar --silent --output-dataset

```

## MCP server setup

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

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/FDUrgfezqZdpbF13d/builds/fV9Vbdu6jvXdbzNrm/openapi.json
