# Meta Ads Presence Enricher (`cranberry_bedrock/meta-ads-presence-enricher`) Actor

Check any list of local businesses for live Facebook and Instagram ads. A name and a city is enough: the actor finds the website, resolves the Facebook page, and checks the Ad Library by page ID. Returns true/false verdicts, landing domains, and phone numbers. $0.04 per answered lead.

- **URL**: https://apify.com/cranberry\_bedrock/meta-ads-presence-enricher.md
- **Developed by:** [Kyle Riccardi](https://apify.com/cranberry_bedrock) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $40.00 / 1,000 ads scrapeds

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/platform/actors/running/actors-in-store#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

## Know who's already spending before you pitch

Paste in a list of local businesses. A name and a city is enough. The actor works
out which ones are running Facebook or Instagram ads right now and gives you the
evidence: the page it found, how many ads it saw, and where those ads send people.

No list yet? Type a niche and a metro instead, something like "med spa" in
"Austin, TX", and it builds the list from Google Maps first. Phone numbers come
along for free.

### Who gets value out of this

Agencies and freelancers use it to split a market into two pitch lists. Businesses
already buying ads have budget, so you sell them better creative or management.
The verified non-advertisers get the "your competitors are running ads" pitch. The
`discovered_domain` column shows you what funnel an advertiser runs before you
ever get them on a call.

Cold email and lead-gen shops use the verdict to segment copy. "Noticed your
Facebook ads" is a very different opener than "nobody in your area is advertising
yet." When the actor builds the list itself you also get phone numbers, so one run
can feed email, SMS, and dialer campaigns at the same time.

If you sell software, payments, insurance, or anything else to small businesses,
treat `runs_meta_ads` as a budget signal. A business paying Meta every month
spends money to grow. Sort by `ads_sampled` and the biggest marketing budgets
float to the top of your call list.

Local owners run it on their own niche to see which competitors advertise and
where their ads point.

Researchers can measure ad adoption for any vertical in any metro with a single
run. Each row carries `resolution_method`, so you can audit how every answer was
reached.

And if you live in Clay, a CRM, or a spreadsheet: this is the enrichment step for
lists that have no domain column. Name and city in; page ID, verdict, landing
domain, and phone out. Export is plain CSV or JSON.

### Why not just search the Ad Library yourself?

Because keyword search misses. We tested it against a hand-checked set of
businesses we knew were advertising, and search failed to surface them again and
again. Tools like Adyntel need a website or page URL for every row, which local
lists rarely have. This actor resolves identity on its own: it finds the website,
pulls the Facebook page off it, and reads the Ad Library by numeric page ID.
That lookup is deterministic, not a guess.

Every lead comes back one of three ways:

- `true` or `false` means the page was positively identified and its Ad Library
  was checked. Both are real answers.
- `"unknown"` means identity couldn't be resolved. Those rows never pay the
  answer fee, and a failed lookup is never dressed up as "not advertising."

There's also an optional keyword fallback for unresolved leads. It uses a
name-and-geography matcher we hand-verified at 91.7% precision on a 215-business
pilot in Phoenix.

### How to use it

Have a list already? Open the Input tab and paste it into the Leads field as JSON.
Include a domain when you know it, since that skips a lookup and gets you a faster
answer:

```json
[
  { "name": "Chas Roberts A/C & Plumbing", "city": "Phoenix", "state": "AZ" },
  { "name": "Parker & Sons", "city": "Phoenix", "state": "AZ", "domain": "parkerandsons.com" },
  { "name": "Sunset Plumbing", "city": "Tempe", "state": "AZ" }
]
```

If your list lives in a CSV or a spreadsheet, any converter will turn
`name,city,state` rows into that shape.

Don't have a list? Leave Leads empty, fill in a search term and a location, and
the actor builds one from Google Maps before enriching it.

Hit Start, then open the Dataset tab when the run finishes. Every lead has a
verdict row you can export as CSV, Excel, or JSON.

A few things people figure out after a run or two: sorting by `ads_sampled`
descending puts the heaviest advertisers first. When `discovered_domain` doesn't
match the business website, they're running dedicated landing pages, which
usually means serious spend. And the keyword fallback option squeezes some extra
matches out of leads that couldn't be resolved.

### Output (per lead)

| Field | Meaning |
|---|---|
| `runs_meta_ads` | `true` / `false` / `"unknown"` |
| `page_id` / `matched_page` | The advertiser's Facebook page, ID and name |
| `ads_sampled` | Active ads seen in the sample, capped by the sampling limit |
| `discovered_domain` | Where their ads send traffic. Often not their website |
| `phone` | Business phone, filled in when the list comes from Maps |
| `match_confidence` | 1.0 for page-ID resolution, a 0 to 1 score on the fallback lane |
| `resolution_method` | `site-link+page-id`, `keyword-match`, `site-link-only`, or `none` |

### Pricing

$0.04 per definitive answer, and $0.01 per lead when we have to find the
business's website for you (leads submitted without a domain). Unresolved leads
never pay the answer fee. A 200-lead run comes in around $8 to $10, which is less
than most people pay for a single verified lead.

# Actor input Schema

## `leads` (type: `array`):

Business leads as JSON objects: {"name": "Acme Plumbing", "city": "Phoenix", "state": "AZ", "domain": "acme.example" (optional), "phone": "..." (optional)}. Name + city is enough — no domain needed.

## `searchTerm` (type: `string`):

Leave `leads` empty and set this + Location to build the lead list automatically from Google Maps first (e.g. "plumber", "med spa", "roofing").

## `location` (type: `string`):

Metro for the automatic lead list, e.g. "Phoenix, AZ". Used only with Search term.

## `maxLeads` (type: `integer`):

Cap on leads processed. Each definitive yes/no answer is charged; leads submitted without a domain also incur a small website-lookup fee. Unresolved leads never pay the answer fee.

## `confidenceCutoff` (type: `number`):

Identity-match threshold (0-1) for the optional keyword-fallback lane. 0.7 is the hand-verified default (91.7% precision on that lane). Page-ID resolved leads don't use it.

## `keywordFallback` (type: `boolean`):

When a lead's Facebook page can't be resolved, also try Ad Library keyword search with a calibrated name/geo matcher. Finds some extra advertisers; a keyword miss still returns "unknown" (never a false negative) and is never charged.

## Actor input object example

```json
{
  "leads": [
    {
      "name": "Acme Plumbing",
      "city": "Phoenix",
      "state": "AZ"
    }
  ],
  "searchTerm": "plumber",
  "location": "Phoenix, AZ",
  "maxLeads": 200,
  "confidenceCutoff": 0.7,
  "keywordFallback": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing all enriched leads

# 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 = {
    "leads": [
        {
            "name": "Acme Plumbing",
            "city": "Phoenix",
            "state": "AZ"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cranberry_bedrock/meta-ads-presence-enricher").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 = { "leads": [{
            "name": "Acme Plumbing",
            "city": "Phoenix",
            "state": "AZ",
        }] }

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

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

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

```

## CLI example

```bash
echo '{
  "leads": [
    {
      "name": "Acme Plumbing",
      "city": "Phoenix",
      "state": "AZ"
    }
  ]
}' |
apify call cranberry_bedrock/meta-ads-presence-enricher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=cranberry_bedrock/meta-ads-presence-enricher",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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