# Facebook Page Post Tracker: AI Winning Posts & Offers (`rich_minds/fb-page-post-tracker`) Actor

First 25 posts free. Facebook page post tracker: pay per qualified post. Monitor competitor Facebook pages, find top performing Facebook posts by engagement, track promotions, launches with AI analysis. Free demo on any plan; live search runs on your Apify account, any plan that runs Store Actors.

- **URL**: https://apify.com/rich_minds/fb-page-post-tracker.md
- **Developed by:** [Rich Minds](https://apify.com/rich_minds) (community)
- **Categories:** Social media, Marketing, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.30 / 1,000 qualified post (rules only)s

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?

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 Page Post Tracker: AI Winning Posts & Offers

**New winning Facebook posts and offers from pages you track, in your inbox weekly.**

⚡ First **25 posts free** · 💵 **$0.0043** per qualified post · 🤖 AI ≈ **$0.030** per post all in, on plans with model access or your key · ⏱️ demo in seconds

![Qualified Facebook posts ranked by how far they beat their page's median — type, reason and the angle to copy](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/shortlist.svg)

> **Try it in 30 seconds.** Click **Try it** — the form is pre-filled with 8 sample posts from 3 pages; 4 qualify in
> seconds, nothing charged. Paste page links or pick a **preset** to search live; the first **25 posts** are free.
> → [What a run costs](#-pricing--what-a-run-really-costs) · [What the AI adds](#-what-the-ai-tier-adds) · [Run it weekly](#-run-it-weekly)

### ⚡ At a glance

| | |
|---|---|
| **What you get** | Only the posts worth your time — winners vs their page's own median, offers, launches, complaints — ranked, with the reason and where to reach the poster |
| **You provide** | 3–10 public Facebook page links — or only a goal, and keywords are derived from it |
| **Output** | a ranked sheet, best first — Excel, CSV or Google Sheets — plus an e-mail digest of what is new each run |
| **Typical run** | 4 pages × 20 posts = 80 scanned → ≈ 28 qualified (≈ 35 %, estimate until a live run is measured) |
| **Cost of that run** | $0.60 rules only (≈ $0.021 per post all in) · $0.84 with AI (≈ $0.030 per post all in) — the $0.48 source scan on your account included |
| **Free tier** | first 25 qualified posts per account |
| **Keys / setup** | AI on your plan: with Apify model access, every post, no key · any other plan: your first 5 real posts live on us, then a free Gemini / Groq key = 1–3 AI verdicts per run, a paid key every post |
| **Languages** | Free rules read English, Spanish, Portuguese, French and German; other languages: leave the AI on — it reads any language |
| **Which plan** | Free demo and your own list (list mode, [template](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/list-template.json)): any plan · live search: a plan that runs Store Actors |
| **Works with** | Schedules, webhooks, Sheets, Slack, Teams, Google Chat, Discord, Telegram, Make / n8n / Zapier, MCP & AI agents |

### 🎯 What this Actor does

Only the posts that pass your filters are charged:

- **A fair winner test.** Every post against *its own page's* median engagement: "5.6× this page's median".
- **What kind of post it is.** Promotion, launch, hiring, event, complaint, question or buying intent — confirmed by quotes with AI.
- **The offer, extracted.** Discount, price, deadline and promo code, as written.
- **A reply and a way to reach them.** A drafted reply for buying-intent posts; every row links the poster's page.
- **Which page is winning.** A page leaderboard in every report; opt-in running ads and niche insights.
- **Only new posts.** Never delivered or charged twice.

#### 🏆 Find top performing Facebook posts

Outlier multiple (default 2×), "Only outlier posts", minimum reactions and a post window — free filters, applied before any charge.

#### 🔭 Monitor competitor Facebook pages

Paste page links (20 posts per page, ≈ $0.006 each on your account) and schedule it weekly: from week 2 only new posts are read.

#### 📊 Facebook competitor analysis: leaderboard, ads, what wins

Every `REPORT` ranks the tracked pages: posts per week, median engagement, winners, offers, change since last run. Opt-in: each page's **top 3 running ads** ($0.10 per page with ads) and **what wins in your niche** — media, weekday, hour, length, type ($0.05 a run).

#### 🛒 Spot people asking where to buy (test)

Type keywords (e.g. "need a wedding photographer"): posts asking where to buy get `buying_intent` and a reply draft. The search returns mostly page posts (no groups or comments); the *Buying intent* preset caps its first scan at $0.15.

### 🚀 How to use it

1. **Click `Try it`** — the form is pre-filled with the free demo on sample posts; it works as is.
2. **Type your page links and what you are looking for** — they replace the sample and start the live search, capped by "Max source spend". Leave "AI assessment" on: your first 5 posts get live AI verdicts on any plan; all 25 free ones get them with Apify model access or a paid own key ([Keys / setup](#-at-a-glance)).
3. **Press Start and open the dataset** — qualified posts land best-first; export them or wire a webhook.
4. **Schedule it weekly** (Actions → Schedule) with your address in "E-mail me the new posts" — only new posts are delivered and charged.

#### 📦 Already using a Facebook posts scraper? Rank your Facebook scraper data

Paste the dataset ID of your last Facebook posts scraper run into **Dataset ID** — no second scrape, rules only by default, you pay only for the qualified posts ([after every scrape](#-integrations-automation-and-api)).

![Offers and launches — discount, price, deadline and promo code per post](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/offers.svg)

📚 **Learn more:** [Tutorial — track competitor Facebook pages step by step](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/tutorial.html)

### 🆚 Instead of a raw Facebook profile posts scraper?

That scraper returns raw rows; the median, sorting and offer are yours to do. Of the demo's 8 posts, 4 were dropped before any charge here (a live funnel follows 2026-11-01).

| | [Facebook Profile & Posts Scraper (`cleansyntax/facebook-profile-posts-scraper`)](https://apify.com/cleansyntax/facebook-profile-posts-scraper) | **This Actor** |
|---|---|---|
| **Your time per 100 posts** | ≈ 10 h sorting (our estimate, 6 min each, not timed) | 0 h — ranked, typed, explained |
| **What you pay for** | every *row scraped*, empty and duplicate ones too | only *qualified posts* — the rest costs **$0 here** |
| **Offers and intent** | "common fields (may vary)" | post type, offer, reply draft |
| **Repeat runs** | the same posts again, billed again | only new posts read and charged |
| **Same 100 qualified posts, all in** | ≈ $1.72 for ≈ 286 unsorted raw rows | $2.15 rules only ($0.43 here + the same $1.72 scan) · $3.02 with AI |
| **Price** | $0.006 per row · 435 users · 3 % of runs failed (30 days) | from $0.0043 per qualified post, first 25 free |

#### vs other Facebook post scrapers and free tools

| | Price | What it gives you | What is missing |
|---|---|---|---|
| **This Actor** | $0.0043 per qualified post · 100 posts ≈ $2.15 all in (AI ≈ $3.02) | page median, type, offer, reply, memory | comments (2026-11-05) |
| [`thedoor/facebook-page-scraper`](https://apify.com/thedoor/facebook-page-scraper) | $0.0025 per result · 279 users · ≈ $0.72 for 286 rows | page posts and comments, raw | page median, offers, memory |
| [`scraper_one/facebook-posts-scraper`](https://apify.com/scraper_one/facebook-posts-scraper) | $0.0025 per result + $0.0025 per run · 209 users · ≈ $0.72 | posts, raw | page median, offers, memory |
| [`pratikdani/facebook-post-scraper`](https://apify.com/pratikdani/facebook-post-scraper) | $0.025 per post + $0.002 per run · 127 users · 17 % of runs failed | one post per URL, raw | pages, ranking, offers |
| Meta Business Suite — Pages to Watch (free) | $0 | official numbers for a few watched pages | no API, no scheduled export, no AI tagging |

Prices, users / 30 days, failed runs: Store listings, 2026-10-05.

### 💵 Pricing — what a run really costs

Pay per result: **one event per qualified post**, plus one per tracked page on scheduled runs.

| Event | When it is charged | Price |
|---|---|---|
| `free-tier` | your first 25 qualified posts on this Actor, in any mode | **$0.00** |
| `qualified-post-basic` | AI off — page baseline, post type, offer, rule score | **$0.0043** |
| `qualified-post-ai` | AI on — plus quotes, fit score and reason, reply draft, next step | **$0.01** |
| `page-monitored` | scheduled runs: per tracked page with a new post; first 8 free | **$0.02** |
| `competitor-brief` | opt-in `includeAds`: per page whose running ads joined the report (ad search capped at $0.30) | **$0.10** |
| `niche-insights` | opt-in `insights`: once per run with 10+ posts read and 3+ winners | **$0.05** |

**Never charged by us:** posts below `minScore`, empty posts, reshares without text, posts outside your window, your exclude list, posts you already received, a page with nothing new. The source's scan bills your Apify account per post read, capped by `maxDiscoveryChargeUsd`.

**How that compares** — $0.0043 is 0.72× one raw row of the source scraper ($0.006) and level with the $0.00425 median row price of Facebook post scrapers; one qualified post replaces ≈ 2.9 raw rows ($0.0015 per raw-row equivalent), typed and ranked.

**Worked example** — 80 posts scanned → ≈ 28 qualified (≈ 35 %, estimate): source $0.48 on your account + 28 × $0.0043 = **$0.60, about $0.021 per qualified post all in** with rules only. With AI: $0.48 + 28 × $0.01 + ≈ $0.08 tokens = **$0.84, about $0.030 per post all in**. Your first 25 posts are free, so run 1 costs the $0.48 scan plus 3 × $0.0043.

**At volume** — a marketing team on 20 pages weekly (spend cap $3): week 1 ≈ $2.40 source; then ≈ $0.96 source + ≈ 50 posts × $0.0043 + 20 × $0.02 monitored pages ≈ $1.58 a week, ≈ $7 a month (estimate).

**AI tier all in, per post:** ≈ $0.013 ($0.01 + ≈ $0.003 tokens, estimate); ≈ $0.030 with the source scan.

### 🤖 What the AI tier adds

The same buying-intent post (“Can anyone recommend a good bike shop in Austin for a kids balance bike? Budget is around $120.”), AI **off**:

```json
{"postType": "buying_intent", "matchedSignals": ["Can anyone recommend"], "ruleScore": 50, "label": "warm",
 "reason": "buying-intent post: “Can anyone recommend”", "replyDraft": null, "assessmentSource": "rules"}
```

AI **on** (`qualified-post-ai`, $0.01) — judged against *your* goal, with a reply; the real row of our own-key AI run of the example input, 2026-10-16, `aiModel` verbatim:

```json
{"fitScore": 85, "score": 85, "label": "hot", "postType": "buying_intent",
 "matchedSignals": ["Can anyone recommend a good bike shop in Austin for a kids balance bike?", "Budget is around $120."],
 "reason": "Matches buying intent by asking for a good bike shop for a kids balance bike with a $120 budget.",
 "replyDraft": "Check out Trek Bicycle Austin or Bicycle Sport Shop—they both usually have a great selection of kids' balance bikes around that price range!",
 "assessmentSource": "ai", "aiModel": "google:gemini-3.5-flash-lite"}
```

Only the AI scores it against your goal, cites the post's words and writes the reply — about 6 minutes each by hand on 3 sample posts (our estimate, 2026-10-05; not yet timed).

**On your plan** (the same answer as [Keys / setup](#-at-a-glance)): Apify model access → every post, no key; any other plan → your first 5 real posts get **live AI verdicts on us**, then a free Gemini key ([aistudio.google.com/apikey](https://aistudio.google.com/apikey)) gives 1–3 per run, a paid key every post.

### ⚙️ Input

<details><summary>Main fields and their defaults</summary>

| Field | Type | Default | What it does |
|---|---|---|---|
| `pageUrls`, `keywords` | string\[] | — | Page links, else a keyword search (IDs: `pageIds`); typing them starts the live search |
| `goalDescription` | string | — | What a good post is for you; the AI scores against it. Alone, it derives 3–5 keywords |
| `maxPostsPerPage` | integer | `20` | Latest posts read per page or keyword — about $0.006 each on your account |
| `startDate`, `endDate` | `YYYY-MM-DD` | — | Post window; a scheduled run sets the start itself |
| `postTypes`, `targetFlags` | pick lists | — | A post of these types always qualifies; flags (outlier, promo, launch, buying intent, offer …) filter |
| `onlyOutliers`, `outlierMultiple` | boolean, number | `false`, `2` | Only posts at this many times their page's median |
| `minScore` | 0–100 | `50` | Decides what you pay: below this = not delivered, not charged |
| `maxQualified` | integer | `100` | Hard cap on posts (and on our charges) |
| `maxDiscoveryChargeUsd` | number | `0.5` | Stops the source run once it has cost this much |
| `enableAi` | boolean | `true` | AI assessment on / off |

Also: `includeAds`, `insights`, `sourceMode`, `minReactions`, `skipReshares`, `dedupeTtlDays`, `notifyEmail`, `sendAlertsTo`. API only: `slackWebhookUrl`, `discordWebhookUrl`, `teamsWebhookUrl`, `googleChatWebhookUrl`, `telegramUrl`, `webhookUrl`, `webhookHeaders`, `webhookBatchSize`, `webhookSecret`, `aiCandidateMultiplier`, `discoveryInput`, `maxToProcess`, `discoveryTimeoutSecs`.

</details>

### 📤 Output

One item per qualified post. Views: `Shortlist` (best first), `Offers & launches`, `Buying intent & replies` (leads with "Reach them"), `Engagement by page vs page median`, `Score breakdown`. The Shortlist ranks hot → warm → cold, AI-assessed rows first, one score with **Scored by** (`assessmentSource`). Sort by `score`; `ruleScore` is the rule part and `fitScore` the AI's — threshold and trend across runs on `ruleScore` (the Score breakdown view has the parts).

<details><summary>An example row (free demo, AI off, <code>demo</code>)</summary>

```json
{"url": "https://www.facebook.com/brightsidecoffee/posts/1001", "pageName": "Brightside Coffee Roasters",
 "engagement": 2258, "pageBaseline": 404.5, "outlierMultiple": 5.58, "isOutlier": true, "postType": "promo",
 "reason": "5.58x its page's median engagement (2258 vs 404.5); promotion: “sale”",
 "offer": {"discount": "30% off", "price": "$40", "deadline": "until Sunday", "code": "SPRING30"},
 "contactRoute": "https://www.facebook.com/brightsidecoffee", "score": 95, "label": "hot",
 "chargedEvent": "demo", "dedupeKey": "id:1001"}
```

</details>

#### Output fields

<details><summary>Every field of a row</summary>

Every row: `itemId`, `name`, `url`, `postId`, `sourceType`, `pageName`, `pageId`, `pageUrl`, `queriedTarget`, `matchedKeyword`, `message`, `publishedAt`, `isReshare`, `externalUrl`, `reactionsCount`, `commentsCount`, `sharesCount`, `reactionBreakdown`, `mediaType`, `imageUrl`, `videoUrl`, `thumbnailUrl`, `engagement`, `pageBaseline`, `outlierMultiple`, `isOutlier`, `baselineWeak`, `postType`, `matchedSignals`, `reason`, `offer`, `replyDraft`, `contactRoute` (the poster's page, else the post — on every row), `assessmentSource`, `flags`, `targetFlagsMatched`, `ruleScore`, `fitScore`, `score`, `label`, `scoreReasons`, `summary`, `generatedText`, `recommendedNextStep`, `aiModel`, `chargedEvent`, `billedAs`, `dedupeKey`, `source`, `scrapedAt`. Unreported counts are `null`, never 0; `score` is the AI fit where `assessmentSource` is `ai` (or `sample`), else the rule score. Each field, one full item and the `OUTPUT` keys: [API reference](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/api-reference.md).

</details>

**Upsert key for a CRM or Sheet:** `dedupeKey` — `id:` plus the Facebook post id, one row per post across runs; `itemId` hashes the same key.

A **`DIGEST`** (new and hot counts, **the change since the last run** — "vs last run: qualified 41 → 55" — the top 10) and a client-ready **`REPORT`** page (`OUTPUT.reportUrl`: posts, page leaderboard, ads, insights) sit next to `OUTPUT`.

⭐ **Found it useful? A review on the Store helps others find it.**

### 🔁 Run it weekly

1. **Open the saved task and click Schedule** after your first real run (its status links it, `OUTPUT.scheduleTaskUrl`), with your address in `notifyEmail`: each run with new posts mails the digest and **the trend vs the last run**.
2. Leave `dedupeAcrossRuns: true` — the next run charges **only what is new**; give each client its own `dedupeStoreName`.
3. Paste a Slack, Teams, Google Chat or Discord webhook — or your Telegram bot's send link — into **Send alerts to** (one per line, detected by address) for the digest; a Make / n8n / Zapier hook there gets every new post as JSON and updates your [tracker sheet](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/tracker.csv) on `dedupeKey` — no manual step.
4. From run 2, `OUTPUT.history` and the `REPORT` show every run; `historyDatasetName` keeps all posts in one dataset.

Weekly cost: from week 2 only new posts are read, ≈ 8 per page instead of 20. 4 pages → ≈ $0.19 source + ≈ 10 posts × $0.0043 + 4 × $0.02 monitored pages ≈ $0.31 a week (estimate), the first 8 monitored pages free.

#### 🎯 Try it for your niche

Pick one under **Start from a ready niche search** and press Start; each first scan is capped at $0.15 of source spend.

| Niche | What it looks for | Start |
|---|---|---|
| Competitor offers & launches | 3 café / fast-food pages, promo and launch posts always kept | [preset](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/preset-competitor-offers.json) |
| Buying intent (test) | public posts asking for a wedding photographer (groups and comments not searched) | [preset](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/preset-buying-intent.json) |
| Launch watch | 3 sports-brand pages, outlier posts only | [preset](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/preset-launch-watch.json) |

### 🔌 Integrations, automation and API

**After every `cleansyntax/facebook-profile-posts-scraper` run:** in that Actor's Integrations tab add "Run succeeded → start this Actor" with `sourceMode: dataset`, `datasetId` = the finished run's `resource.defaultDatasetId` and `enableAi: false` — every scrape you already pay for is ranked, rules only.

#### Run outcomes — what your integration sees

<details><summary>Every outcome: run status, dataset, <code>OUTPUT</code> field, charged?, what to do</summary>

| Outcome | Run status | Dataset | `OUTPUT` | Charged? | What to do |
|---|---|---|---|---|---|
| Free demo | SUCCEEDED | sample posts | `mode: demo` | no | type page links |
| Success | SUCCEEDED | qualified posts | funnel, `chargedEvents` | per qualified post | — |
| Nothing matched | SUCCEEDED | empty | `outcome: empty` | not by us (the source scan ran) | loosen `minScore` |
| Demo text still in place | SUCCEEDED | empty | `inputNeeded` | no | replace the field named |
| Invalid input | FAILED | empty | — | no | fix the field named |
| Your plan cannot run the source Store Actor | SUCCEEDED — the same for the Console, the API and a schedule; check `OUTPUT.ok` | empty | `ok: false`, `outcome: source_refused`, `sourceRefused: plan` | no | paste [list-mode rows](#-list-mode-rows--what-a-pasted-record-needs) |
| Source failed (crash, timeout) | FAILED | empty | `ok: false`, `outcome: source_failed`, `sourceError` | no | follow the status |
| Source partial (timed out after scraping) | SUCCEEDED | posts scraped | `sourceStatus.partial: true` | per qualified post | raise `discoveryTimeoutSecs` |
| AI unavailable | SUCCEEDED | rule-scored posts | `aiError`, `aiCoverage` | free tier, then basic | `llmProvider: byok` |

</details>

- **Webhook** — `{"event": "post.qualified", "post": {…}, "runId"}` per post (`posts.qualified` per batch), a 429 / 5xx retried once; a POST that still fails is in `OUTPUT.webhook.failedKeys`, not re-sent. `webhookSecret` signs each body (`X-Signature: sha256=…`). HubSpot / Pipedrive: a Make / Zapier step, upsert on `dedupeKey`.
- **Which call runs what** — `OUTPUT.mode`: `demo` (never charged), `live`, `list` or `dataset`.
- **AI agents / MCP** — plain typed inputs; MCP server `https://mcp.apify.com/?actors=rich_minds/fb-page-post-tracker`. The snippets run the **real search**, capped; `{}` runs the free demo.

<details><summary>API snippets — Python and JavaScript</summary>

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("rich_minds/fb-page-post-tracker").call(
    run_input={
        # free first call instead: {} (the demo) or {"sourceMode": "list", "itemsList": [...]}
        "sourceMode": "actor",
        "pageUrls": ["https://www.facebook.com/starbucks", "https://www.facebook.com/dunkin"],
        "goalDescription": "Competitor posts that announce an offer or a launch",
        "maxPostsPerPage": 20,
        "maxQualified": 50,
        "maxDiscoveryChargeUsd": 0.5,  # source spend cap
    },
    timeout_secs=3600,  # paid own key at the defaults ≈ 3240 s — FAQ "How long does a run take?"
    # build="0.1.4",  # pin the build you tested with (Changelog); omit for the latest
)
out = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("OUTPUT")["value"]
if out.get("sourceError"):  # a refused (ok: false) or failed source — never read it as "nothing new"
    raise RuntimeError(out["sourceError"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["url"], item["score"], item["label"])
```

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('rich_minds/fb-page-post-tracker').call({
    sourceMode: 'actor',
    pageUrls: ['https://www.facebook.com/starbucks', 'https://www.facebook.com/dunkin'],
    goalDescription: 'Competitor posts that announce an offer or a launch',
    maxPostsPerPage: 20,
    maxQualified: 50,
    maxDiscoveryChargeUsd: 0.5, // source spend cap
}, { timeout: 3600 /* , build: '0.1.4' — pin the build you tested with */ });
const out = (await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT')).value;
if (out.sourceError) throw new Error(out.sourceError);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => [i.url, i.score, i.label]));
```

**Minimal agent input** — every other input keeps its default. It turns the cross-run memory off and reads each page's latest 20 posts, so the same question asked twice returns the top posts both times:

```json
{"pageUrls": ["https://www.facebook.com/starbucks"], "goalDescription": "posts that announce an offer", "maxPostsPerPage": 20, "maxQualified": 50, "maxDiscoveryChargeUsd": 0.5, "dedupeAcrossRuns": false}
```

</details>

#### 🧾 List-mode rows — what a pasted record needs

<details><summary>Fields a pasted post can carry</summary>

`sourceMode: "list"` runs on any plan: replace the 2 rows of the [template](https://api.apify.com/v2/key-value-stores/aSEcCF8IpaBv6b3HC/records/list-template.json) with posts you have (any Facebook posts scraper's JSON export) and paste them into `itemsList`.

| Field | Needed? | What it enables |
|---|---|---|
| `post_id` | required (or `postId`, `id`, `url`) | identity, dedupe key |
| `message` | for type, offer and AI | post type, offer, AI |
| `reactions_count`, `comments_count`, `reshare_count` | for the baseline | engagement, page median |
| `author` | needed (`name`, `id`, `url`) | the page it is compared with, `contactRoute` |
| `timestamp`, `type`, `image`, `video`, `external_url`, `keyword` | optional | date window, media, flags (aliases: API reference) |

</details>

### 👥 Who is it for?

| You are… | You run it to… | Start with |
|---|---|---|
| Social media manager tracking 5–30 competitor pages | see which rival posts won this week and their offers | preset *Competitor offers & launches*, weekly |
| Local business owner watching rivals | catch every rival discount, deadline and promo code | 3–5 rival page links, post type *Promotion* |
| Growth marketer or founder testing demand | find public posts asking where to buy | preset *Buying intent* (test) |
| AI agent or n8n workflow | typed posts with scores, offers and reasons | the minimal agent input above |

### 🧠 How the AI works

- **Grounded.** One post at a time: a type from a fixed list, quotes from the post, a fit score with a reason, the offer as written, a reply only for buying-intent and question posts.
- **Your goal.** The fit score measures the post against your `goalDescription`; only the best rule-scored candidates get the AI.
- **No key where your Apify plan includes model access** — otherwise your own key (`llmProvider: "byok"`). Default: `anthropic/claude-haiku-4.5`; an own Gemini key: `google:gemini-3.5-flash-lite`.
- **Fallback.** If the model fails, the post keeps its rule score, type and offer at the basic price.

### 🔒 Data, compliance and limits

- Public Facebook pages and posts only, read without a login. Follow Facebook's terms and applicable law (GDPR, CCPA); no unsolicited mass messaging.
- A count the source does not return stays `null`. A page needs 5 posts for its own baseline (fewer is `weak_baseline`; a scheduled run reuses the last median).
- Rule types are keyword-based in English, Spanish, Portuguese, French and German; the AI reads any language.

### ❓ FAQ

**What will my first real search cost?** 4 pages × 20 posts ≈ $0.48 on your account (capped at $0.50) and ≈ 28 qualified posts: the first 25 are free, the other 3 cost $0.0043 each.

**How do I track competitor promotions?** Paste the rivals' page links, pick *Promotion* under "Always qualify these post types", schedule it weekly: every discount, price, deadline and code lands in the Offers view.

**Can it be my Facebook post engagement tracker?** Yes — every post's engagement (reactions + 2 × comments + 3 × shares) is compared with its page's median, and the REPORT tracks each page from run to run.

**Which Facebook competitor posts are worth copying?** The ones far above their page's median and the ones naming an offer or a launch (Shortlist and Offers views); *What wins in your niche* names the pattern.

**Do I still need the scraper it runs?** No — it runs the source scraper on your account and adds the baseline, type, offer, AI and memory; `list` mode never runs it.

**Why pay when Meta Business Suite is free?** Its Pages to Watch is free, official and enough to eyeball a few pages — use it for that. It has no API, no scheduled export, no AI tagging, offers or reply drafts.

**Will I be charged for the same post twice?** No, with `dedupeAcrossRuns` on (default): every charged post is remembered by its post id. Adding a page or keyword keeps that memory; each `dedupeStoreName` and each `postTypes` / `targetFlags` filter set keeps its own — but a post already charged under one of them is delivered to the other as `already-charged`, never charged again.

<details><summary>More questions: replies, run time, plans, free units</summary>

**How do I send the replies safely?** Post them yourself, a few a day from an established account or page, with no links or off-platform contact details in a first reply — never mass-message strangers. The Actor sends nothing to Facebook.

**How long does a run take?** Size `timeoutSecs` from it (source + AI + rate-limit budget):

| Mode | Time | `timeoutSecs` to pass |
|---|---|---|
| Free demo (8 sample posts) | under 1 s (measured, `OUTPUT.timing`) | 120 |
| Your own posts (`list` / `dataset`), rules only | seconds (estimate) | 300 |
| Live search, rules only | 1–2 min for 80 posts (estimate) | 600 |
| AI via Apify's model access | ≈ 3–5 min per 100 posts, 8 calls at a time (estimate) | 900 |
| AI with a free own key | measured 2026-10-16: a free Gemini key ≈ 9 posts per minute (6.8 s each); rate-limited, 1–3 posts, then 60 s of waits | 600 |
| AI with a paid own key | 240 s source + 100 posts × 2 candidates × ≈ 30 s ÷ 2 calls ≈ 3240 s (estimate) | 3600 |

Worst case per 100 posts, rate-limited own key: the source's read + 60 s of waits + a 60 s retry pass, all rows pushed before the 3600 s timeout.

**Which Apify plan do I need?** Any plan runs the free demo and `list` mode; the live search runs on a plan that runs Store Actors. Which AI you get on your plan: [Keys / setup](#-at-a-glance).

**Do rule-scored posts use my free ones?** Yes, while the AI is unavailable — except the last 5, kept for your first AI-assessed posts (`OUTPUT.chargeSplit.freeKeptForAi`).

**What happens if nothing matches my filters?** The run succeeds with 0 rows, nothing charged here.

</details>

### 🧩 More Actors from the same developer

Same buyers, same pay-per-result model:

- **[Facebook Ad Library Winning Ads Finder](https://apify.com/rich_minds/fb-ad-library-winning-ads)** — Your competitors' longest-running Facebook ads — hook, offer and angle broken down, one row each.
- **[Instagram Competitor Content](https://apify.com/rich_minds/ig-competitor-winning-posts)** — Instagram posts that beat their account's median, in your inbox weekly.
- **[Twitter Lead Generation](https://apify.com/rich_minds/twitter-lead-generation-ai)** — People on X asking for a tool like yours — with a reply draft, every week.
- **[Website Change Monitor](https://apify.com/rich_minds/website-change-monitor-ai)** — Every real change on your competitors' pages, explained, in your inbox every week.
- **[YouTube Outlier Videos Finder](https://apify.com/rich_minds/youtube-video-research-ai)** — YouTube videos that beat their channel's usual views, scored against your brief with AI takeaways.

### 🆘 Support

Something missing or wrong? Open an issue on the Actor's page.

### 📝 Changelog

- **0.1.4** (2026-10-16) — One "Send alerts to" field takes your Slack, Discord, Teams, Google Chat, Telegram or Zapier / Make link, and the form is shorter; Your own Gemini key now gets AI verdicts without picking a model, and a post you already paid for under another watch list is delivered again but never charged twice; If your Apify plan can't run the source scraper, the run now ends successfully with a clear "use list mode" next step instead of failing; the page also shows how to rank a Facebook scraper dataset you already have, after every scrape.
- **0.1.3** (2026-10-09) — page leaderboard in every report; opt-in running ads and niche insights; pick lists for post types and flags; signed webhooks; es / pt / fr / de rules.
- **0.1.1–0.1.2** (2026-10-09) — initial release (winners vs each page's median, types, offers, replies, AI, memory, digest); weekly runs read only new posts; `page-monitored`.

# Actor input Schema

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

Public Facebook page links, one per line, e.g. <code>https://www.facebook.com/brightsidecoffee</code>. Each post is compared with <b>its own page's</b> median. Typing links starts the live search: it runs a Store Actor on your Apify plan, spend-capped below (no such plan: paste posts in list mode). Empty = free demo; only a goal = keywords derived from it.

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

Search public Facebook posts by keyword, e.g. <code>kids balance bike</code>, one per line — finds people asking where to buy. Used when no page links are given. Affects what you pay: each post bills $0.006 on your Apify account, up to <b>Posts per page or keyword</b> per line. The live search runs a Store Actor on your Apify plan.

## `goalDescription` (type: `string`):

Describe the ideal post in plain words (an offer, a launch, someone asking where to buy). The AI scores every post against this and explains why; empty = the posts that beat their page's usual engagement. Non-English pages: leave the AI on — the free rules read English, Spanish, Portuguese, French and German.

## `preset` (type: `string`):

Pick a niche and press Start: it fills the search fields you left empty and runs the live search — each preset's first scan is capped at $0.15 of source spend (8–10 posts per page or keyword) until its yield is measured. Your own values always win. <b>Custom</b> = fill the fields yourself.

## `enableAi` (type: `boolean`):

Score each post against your goal, explain why and write a takeaway. <b>On = $0.01 per qualified post + AI tokens (≈ $0.013 incl. tokens); off = $0.0043</b> (rule-based flags and scores only). Your first 5 posts get live AI verdicts on any plan; every one with Apify model access or a paid own key. Non-English pages: leave it on.

## `notifyEmail` (type: `string`):

After every run with at least one new post, the run's digest (how many are new, the top 10 with links) is e-mailed here through Apify's send-mail Actor. Schedule the run weekly (Actions → Schedule) and only the new posts come to your inbox — no Slack or Zapier needed. Never sent for the free demo.

## `minScore` (type: `integer`):

The field that decides what you pay: posts scoring below this are discarded and <b>not charged</b> — except a post of one of your post types, which always qualifies. A post at twice its page's median scores about 55. Raise it to pay only for the strongest, lower it to see more.

## `maxQualified` (type: `integer`):

Hard cap on results — and the ceiling of what <b>this Actor</b> charges: at most this many × $0.01 with AI on (× $0.0043 with AI off), e.g. 100 × $0.01; the source's own spend has its cap right below. The best-scoring posts are output first.

## `maxDiscoveryChargeUsd` (type: `number`):

Hard cap on what the source Actor run may charge your account in <b>actor</b> mode — $0.50 in the form and for an API or agent call that leaves it out, so a first real search can never cost more. Raise it for bigger searches.

## `pageIds` (type: `array`):

Numeric page or profile IDs, one per line (the source's <code>profile_posts</code> endpoint) — the same as page links when you only have the IDs. Used when no page links are given. The live search runs a Store Actor on your Apify plan.

## `maxPostsPerPage` (type: `integer`):

How many of the latest posts the source reads per page link, ID or keyword. Affects what you pay: each post bills about $0.006 on your Apify account, so 20 posts × 5 pages ≈ $0.60 (capped by <b>Max source spend</b>). The page's baseline is the median of these posts — below 5 posts a page has a weak baseline and no outliers.

## `startDate` (type: `string`):

<code>YYYY-MM-DD</code>. Passed to the source (it bills fewer rows) and applied as a free filter. The page's baseline is then built from the posts inside the window. Empty = the latest posts.

## `endDate` (type: `string`):

<code>YYYY-MM-DD</code>. Same as above for the end of the window. Empty = up to today.

## `postTypes` (type: `array`):

Pick the types that always qualify, whatever their score — e.g. <b>Buying intent</b> to never miss someone asking where to buy. Empty = the score and outliers decide. Over the API send the values (<code>promo</code>, <code>buying_intent</code> …); an unknown one stops the run with the list.

## `onlyOutliers` (type: `boolean`):

On: drop every post that does not beat its page's median engagement by the multiple below — before any AI or charge. Off (default): the score decides.

## `outlierMultiple` (type: `number`):

A post is an outlier when its engagement (reactions + 2 × comments + 3 × shares) is at least this many times its page's median — and the page has 5 or more posts.

## `minReactions` (type: `integer`):

Free filter: drop posts with fewer reactions. 0 = off.

## `skipReshares` (type: `boolean`):

On (default): a reshare that carries no text of its own is dropped for free — it is someone else's post.

## `targetFlags` (type: `array`):

Only deliver posts that have at least one of these flags, and rank those with more of them higher. Leave empty to keep everything. Over the API send the flag names, e.g. <code>offer_detected</code> (<code>offerDetected</code> works too); an unknown name stops the run at once with the list of valid ones.

## `suppressionList` (type: `array`):

Names, URLs or ids to never output. Suppressed posts are skipped before any processing and never charged.

## `includeAds` (type: `boolean`):

Off by default. On: the same pages are also checked in the Meta Ad Library (our Facebook Ad Library Winning Ads Finder, rules only) and each page's top 3 running ads join the REPORT next to its winning posts. Page links only, live search only. Affects what you pay: <b>$0.10 per page with ads found</b> (competitor-brief) + the ad search on your account, capped at $0.30.

## `insights` (type: `boolean`):

Off by default. On: the REPORT and <code>OUTPUT.insights</code> compare the winners with the rest by media type, weekday, hour, post length and post type — the pattern behind the winners. Affects what you pay: <b>$0.05 per run</b> (niche-insights), only when at least 10 posts were read and 3 winners found.

## `llmProvider` (type: `string`):

<b>Apify (no keys)</b> — the AI runs through Apify's built-in OpenRouter proxy; tokens are billed to your Apify account at OpenRouter's rates. <b>My own key</b> — use your OpenAI / Anthropic / Gemini / Groq key instead; a free-tier key assesses only a few posts per run — expect 1–3 AI checks (2 calls at a time, one after a rate limit, then the 3 borderline posts retried one by one) — and the rest keep their rule score; a paid Groq / Gemini key (billing on) assesses every post.

## `llmModel` (type: `string`):

Leave empty for the default (<code>anthropic/claude-haiku-4.5</code>). Apify mode takes an OpenRouter slug such as <code>openai/gpt-4.1-mini</code>; own-key mode takes <code>provider:model</code>, e.g. <code>anthropic:claude-haiku-4-5-20251001</code> — left empty there, your key's vendor picks it (a Gemini key: <code>google:gemini-3.5-flash-lite</code>).

## `llmApiKey` (type: `string`):

Required when <b>AI model access</b> is <i>My own API key</i>. Stored encrypted by Apify, never logged.

## `aiCandidateMultiplier` (type: `integer`):

API only — hidden on the form. How many of the best rule-scored candidates get the AI pass, as a multiple of <b>Max qualified</b>. Higher = more thorough, slower, more tokens.

## `sendAlertsTo` (type: `string`):

Paste one or more URLs, one per line — the service is detected from the address: a Slack or Discord channel webhook, a Teams Workflows or Google Chat space webhook, your Telegram bot's send link (<code>https://api.telegram.org/bot\<token>/sendMessage?chat_id=@yourchannel</code>) or any other https URL (Zapier, Make, n8n, your CRM), which gets the posts as JSON. After every run with at least one new post, the digest (how many are new, the top 10 with links) is posted there. Never sent for the free demo. Webhook headers, batching and signing are API-only inputs (<code>webhookHeaders</code>, <code>webhookBatchSize</code>, <code>webhookSecret</code>).

## `dedupeAcrossRuns` (type: `boolean`):

Remembers every post you were charged for (in a named key-value store on your account) and skips it in future runs.

## `dedupeStoreName` (type: `string`):

Key-value store used for cross-run memory. Left at the default, the memory is kept per search (source and search terms): rewording <code>goalDescription</code> keeps it, so a scheduled run never re-delivers what you already have, and another search starts its own list. Running it for several clients on one account, type one name per client.

## `dedupeTtlDays` (type: `integer`):

How long a delivered post stays remembered. 0 = never deliver or charge it again. A number of days lets a post that comes back (a seasonal peak, a relisted item) reach you again after that many days — charged again as a new post.

## `historyDatasetName` (type: `string`):

Optional. Type a name (e.g. <code>fb-page-post-tracker-seen-log</code>) and every run appends its new posts with the date to that named dataset on your account — one list across months to connect to Google Sheets or your BI tool once. Empty = off. Costs nothing extra.

## `sourceMode` (type: `string`):

<b>actor</b> — run <code>cleansyntax/facebook-profile-posts-scraper</code> on your account, billed separately. <b>dataset</b> — an existing Apify dataset. <b>list</b> — records you paste in. <b>Bring your own data:</b> rows or a dataset ID, and you pay only for the analysis — the source Actor is never run. The mode you choose always runs as chosen; only the untouched sample records next to a search you typed switch to the live search (spend-capped). <code>{}</code> over the API runs the free demo — nothing charged.

## `autoSwitchToLive` (type: `boolean`):

On (default): search terms typed next to the untouched sample records start the live search (spend-capped; <code>OUTPUT.switchedToLive</code> names them). Off: the mode you send is the mode that runs — the sample stays the free demo and the status names the terms it did not search. API pipelines that must never start a paid read on their own: send <code>false</code>.

## `datasetId` (type: `string`):

Only for <b>dataset</b> mode. Pick the dataset so the Actor is granted read access to it.

## `itemsList` (type: `array`):

Only for <b>list</b> mode. JSON array of Facebook posts, one object each, with at least <code>post_id</code> (or <code>url</code> or <code>message</code>) and ideally <code>message</code>, <code>timestamp</code>, <code>reactions_count</code>, <code>comments_count</code>, <code>reshare_count</code> and <code>author</code> (<code>name</code>, <code>url</code>) — the fields <code>cleansyntax/facebook-profile-posts-scraper</code> returns. Pasted posts are compared with the other posts of their page, so paste several per page.

## `discoveryActorId` (type: `string`):

Actor used in <b>actor</b> mode. Any Actor whose output has the fields listed in the README works.

## `discoveryInput` (type: `object`):

API only — hidden on the form. Only for <b>actor</b> mode. Extra input passed to the source Actor as-is, field by field over what the form fields above build (<code>endpoint</code>, <code>urls_text</code>, <code>ids_text</code>, <code>keywords_text</code>, <code>max_posts</code>, <code>start_date</code>, <code>end_date</code>). See the source Actor's input schema. The live search runs that Store Actor on your Apify plan — if your plan cannot run Store Actors, paste your own records in <b>list</b> mode (any plan); <b>dataset</b> only for a dataset you already have from another account or tool.

## `maxToProcess` (type: `integer`):

API only — hidden on the form. Upper bound on how many posts are scored in one run (controls run time). Default = 3 × max qualified.

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

API only — hidden on the form. Qualified posts are POSTed here as JSON (Zapier, Make, n8n, your CRM). For Google Sheets / Slack you can also use Apify's built-in Integrations tab. Stored encrypted — Zapier / Make hook URLs carry their secret in the path.

## `webhookHeaders` (type: `object`):

API only — hidden on the form. Extra HTTP headers for the webhook, e.g. <code>{"Authorization": "Bearer …"}</code>. Stored encrypted by Apify, never logged.

## `webhookBatchSize` (type: `integer`):

API only — hidden on the form. 1 = one POST per post the moment it is ready. Higher = one POST per N posts.

## `webhookSecret` (type: `string`):

API only — hidden on the form. Any random string. Every webhook POST then carries <code>X-Signature: sha256=\<HMAC-SHA256 of the raw body with this secret></code>, so your endpoint can verify it came from this Actor. Stored encrypted by Apify, never logged.

## `discoveryTimeoutSecs` (type: `integer`):

API only — hidden on the form. How long the source Store Actor may run before its partial results are used. Raise it when the status says the source stopped early.

## `slackWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Slack incoming-webhook URL for the digest.

## `discordWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Discord channel webhook URL for the digest.

## `teamsWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Teams Workflows webhook URL for the digest.

## `googleChatWebhookUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Google Chat space webhook URL for the digest.

## `telegramUrl` (type: `string`):

API only — hidden on the form, where <code>sendAlertsTo</code> takes it. The bot's sendMessage link with its chat_id.

## Actor input object example

```json
{
  "goalDescription": "Posts that beat their page's usual engagement, announce an offer or a launch, or show someone asking where to buy",
  "preset": "custom",
  "enableAi": true,
  "minScore": 50,
  "maxQualified": 100,
  "maxDiscoveryChargeUsd": 0.5,
  "maxPostsPerPage": 20,
  "onlyOutliers": false,
  "outlierMultiple": 2,
  "minReactions": 0,
  "skipReshares": true,
  "includeAds": false,
  "insights": false,
  "llmProvider": "apify",
  "aiCandidateMultiplier": 2,
  "dedupeAcrossRuns": true,
  "dedupeStoreName": "fb-page-post-tracker-seen",
  "dedupeTtlDays": 0,
  "historyDatasetName": "",
  "sourceMode": "list",
  "autoSwitchToLive": true,
  "itemsList": [
    {
      "post_id": "1001",
      "type": "photo",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1001",
      "external_url": "https://brightsidecoffee.example/shop/spring-sale",
      "message": "Spring sale! 30% off all whole-bean bags with code SPRING30 until Sunday. Free shipping over $40.",
      "timestamp": 1759060800,
      "reactions_count": 1240,
      "comments_count": 188,
      "reshare_count": 214,
      "image": "https://example.com/img/1001.jpg",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1002",
      "type": "photo",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1002",
      "message": "Good morning from the roastery!",
      "timestamp": 1759147200,
      "reactions_count": 210,
      "comments_count": 14,
      "reshare_count": 3,
      "image": "https://example.com/img/1002.jpg",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1003",
      "type": "photo",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1003",
      "external_url": "https://brightsidecoffee.example/shop/ethiopia-guji",
      "message": "Introducing our new Ethiopia Guji single origin - now available online and in store.",
      "timestamp": 1759233600,
      "reactions_count": 640,
      "comments_count": 52,
      "reshare_count": 61,
      "image": "https://example.com/img/1003.jpg",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1004",
      "type": "video",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1004",
      "message": "Behind the scenes: roasting day.",
      "timestamp": 1759320000,
      "reactions_count": 180,
      "comments_count": 9,
      "reshare_count": 2,
      "video": "https://example.com/vid/1004.mp4",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1005",
      "type": "status",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1005",
      "message": "We're hiring a barista for our downtown cafe! Apply now at the counter or send us a message.",
      "timestamp": 1759406400,
      "reactions_count": 95,
      "comments_count": 31,
      "reshare_count": 18,
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1006",
      "type": "status",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1006",
      "message": "What's your go-to brew method? Tell us below!",
      "timestamp": 1759492800,
      "reactions_count": 260,
      "comments_count": 142,
      "reshare_count": 6,
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "2001",
      "type": "status",
      "url": "https://www.facebook.com/northwindbikes/posts/2001",
      "message": "Our shop is closed Monday for inventory. Back Tuesday at 9.",
      "timestamp": 1759579200,
      "reactions_count": 12,
      "comments_count": 1,
      "reshare_count": 0,
      "author": {
        "name": "Northwind Bikes",
        "id": "100090000000002",
        "url": "https://www.facebook.com/northwindbikes"
      }
    },
    {
      "post_id": "3001",
      "type": "status",
      "url": "https://www.facebook.com/groups/austinparents/posts/3001",
      "message": "Anyone know where I can buy a kids balance bike under $120 near Austin? Looking for recommendations!",
      "timestamp": 1759665600,
      "reactions_count": 9,
      "comments_count": 14,
      "reshare_count": 0,
      "keyword": "kids balance bike",
      "author": {
        "name": "Austin Parents Group",
        "id": "100090000000003",
        "url": "https://www.facebook.com/groups/austinparents"
      }
    }
  ],
  "discoveryActorId": "cleansyntax/facebook-profile-posts-scraper",
  "webhookBatchSize": 1,
  "discoveryTimeoutSecs": 3600
}
```

# Actor output Schema

## `qualified` (type: `string`):

All qualified posts as JSON, best-scoring first.

## `sheet` (type: `string`):

The same posts as a spreadsheet.

## `runSummary` (type: `string`):

JSON record with the funnel (loaded -> filtered -> AI-assessed -> qualified), per-reason skip counts, charged events by type, free-tier posts used and remaining, whether a budget limit was reached, webhook delivery counts and the dedupe store size.

# 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 = {
    "goalDescription": "Posts that beat their page's usual engagement, announce an offer or a launch, or show someone asking where to buy",
    "maxDiscoveryChargeUsd": 0.5,
    "maxPostsPerPage": 20,
    "sourceMode": "list",
    "itemsList": [
        {
            "post_id": "1001",
            "type": "photo",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1001",
            "external_url": "https://brightsidecoffee.example/shop/spring-sale",
            "message": "Spring sale! 30% off all whole-bean bags with code SPRING30 until Sunday. Free shipping over $40.",
            "timestamp": 1759060800,
            "reactions_count": 1240,
            "comments_count": 188,
            "reshare_count": 214,
            "image": "https://example.com/img/1001.jpg",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee"
            }
        },
        {
            "post_id": "1002",
            "type": "photo",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1002",
            "message": "Good morning from the roastery!",
            "timestamp": 1759147200,
            "reactions_count": 210,
            "comments_count": 14,
            "reshare_count": 3,
            "image": "https://example.com/img/1002.jpg",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee"
            }
        },
        {
            "post_id": "1003",
            "type": "photo",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1003",
            "external_url": "https://brightsidecoffee.example/shop/ethiopia-guji",
            "message": "Introducing our new Ethiopia Guji single origin - now available online and in store.",
            "timestamp": 1759233600,
            "reactions_count": 640,
            "comments_count": 52,
            "reshare_count": 61,
            "image": "https://example.com/img/1003.jpg",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee"
            }
        },
        {
            "post_id": "1004",
            "type": "video",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1004",
            "message": "Behind the scenes: roasting day.",
            "timestamp": 1759320000,
            "reactions_count": 180,
            "comments_count": 9,
            "reshare_count": 2,
            "video": "https://example.com/vid/1004.mp4",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee"
            }
        },
        {
            "post_id": "1005",
            "type": "status",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1005",
            "message": "We're hiring a barista for our downtown cafe! Apply now at the counter or send us a message.",
            "timestamp": 1759406400,
            "reactions_count": 95,
            "comments_count": 31,
            "reshare_count": 18,
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee"
            }
        },
        {
            "post_id": "1006",
            "type": "status",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1006",
            "message": "What's your go-to brew method? Tell us below!",
            "timestamp": 1759492800,
            "reactions_count": 260,
            "comments_count": 142,
            "reshare_count": 6,
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee"
            }
        },
        {
            "post_id": "2001",
            "type": "status",
            "url": "https://www.facebook.com/northwindbikes/posts/2001",
            "message": "Our shop is closed Monday for inventory. Back Tuesday at 9.",
            "timestamp": 1759579200,
            "reactions_count": 12,
            "comments_count": 1,
            "reshare_count": 0,
            "author": {
                "name": "Northwind Bikes",
                "id": "100090000000002",
                "url": "https://www.facebook.com/northwindbikes"
            }
        },
        {
            "post_id": "3001",
            "type": "status",
            "url": "https://www.facebook.com/groups/austinparents/posts/3001",
            "message": "Anyone know where I can buy a kids balance bike under $120 near Austin? Looking for recommendations!",
            "timestamp": 1759665600,
            "reactions_count": 9,
            "comments_count": 14,
            "reshare_count": 0,
            "keyword": "kids balance bike",
            "author": {
                "name": "Austin Parents Group",
                "id": "100090000000003",
                "url": "https://www.facebook.com/groups/austinparents"
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rich_minds/fb-page-post-tracker").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 = {
    "goalDescription": "Posts that beat their page's usual engagement, announce an offer or a launch, or show someone asking where to buy",
    "maxDiscoveryChargeUsd": 0.5,
    "maxPostsPerPage": 20,
    "sourceMode": "list",
    "itemsList": [
        {
            "post_id": "1001",
            "type": "photo",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1001",
            "external_url": "https://brightsidecoffee.example/shop/spring-sale",
            "message": "Spring sale! 30% off all whole-bean bags with code SPRING30 until Sunday. Free shipping over $40.",
            "timestamp": 1759060800,
            "reactions_count": 1240,
            "comments_count": 188,
            "reshare_count": 214,
            "image": "https://example.com/img/1001.jpg",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee",
            },
        },
        {
            "post_id": "1002",
            "type": "photo",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1002",
            "message": "Good morning from the roastery!",
            "timestamp": 1759147200,
            "reactions_count": 210,
            "comments_count": 14,
            "reshare_count": 3,
            "image": "https://example.com/img/1002.jpg",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee",
            },
        },
        {
            "post_id": "1003",
            "type": "photo",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1003",
            "external_url": "https://brightsidecoffee.example/shop/ethiopia-guji",
            "message": "Introducing our new Ethiopia Guji single origin - now available online and in store.",
            "timestamp": 1759233600,
            "reactions_count": 640,
            "comments_count": 52,
            "reshare_count": 61,
            "image": "https://example.com/img/1003.jpg",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee",
            },
        },
        {
            "post_id": "1004",
            "type": "video",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1004",
            "message": "Behind the scenes: roasting day.",
            "timestamp": 1759320000,
            "reactions_count": 180,
            "comments_count": 9,
            "reshare_count": 2,
            "video": "https://example.com/vid/1004.mp4",
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee",
            },
        },
        {
            "post_id": "1005",
            "type": "status",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1005",
            "message": "We're hiring a barista for our downtown cafe! Apply now at the counter or send us a message.",
            "timestamp": 1759406400,
            "reactions_count": 95,
            "comments_count": 31,
            "reshare_count": 18,
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee",
            },
        },
        {
            "post_id": "1006",
            "type": "status",
            "url": "https://www.facebook.com/brightsidecoffee/posts/1006",
            "message": "What's your go-to brew method? Tell us below!",
            "timestamp": 1759492800,
            "reactions_count": 260,
            "comments_count": 142,
            "reshare_count": 6,
            "author": {
                "name": "Brightside Coffee Roasters",
                "id": "100090000000001",
                "url": "https://www.facebook.com/brightsidecoffee",
            },
        },
        {
            "post_id": "2001",
            "type": "status",
            "url": "https://www.facebook.com/northwindbikes/posts/2001",
            "message": "Our shop is closed Monday for inventory. Back Tuesday at 9.",
            "timestamp": 1759579200,
            "reactions_count": 12,
            "comments_count": 1,
            "reshare_count": 0,
            "author": {
                "name": "Northwind Bikes",
                "id": "100090000000002",
                "url": "https://www.facebook.com/northwindbikes",
            },
        },
        {
            "post_id": "3001",
            "type": "status",
            "url": "https://www.facebook.com/groups/austinparents/posts/3001",
            "message": "Anyone know where I can buy a kids balance bike under $120 near Austin? Looking for recommendations!",
            "timestamp": 1759665600,
            "reactions_count": 9,
            "comments_count": 14,
            "reshare_count": 0,
            "keyword": "kids balance bike",
            "author": {
                "name": "Austin Parents Group",
                "id": "100090000000003",
                "url": "https://www.facebook.com/groups/austinparents",
            },
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("rich_minds/fb-page-post-tracker").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 '{
  "goalDescription": "Posts that beat their page'\''s usual engagement, announce an offer or a launch, or show someone asking where to buy",
  "maxDiscoveryChargeUsd": 0.5,
  "maxPostsPerPage": 20,
  "sourceMode": "list",
  "itemsList": [
    {
      "post_id": "1001",
      "type": "photo",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1001",
      "external_url": "https://brightsidecoffee.example/shop/spring-sale",
      "message": "Spring sale! 30% off all whole-bean bags with code SPRING30 until Sunday. Free shipping over $40.",
      "timestamp": 1759060800,
      "reactions_count": 1240,
      "comments_count": 188,
      "reshare_count": 214,
      "image": "https://example.com/img/1001.jpg",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1002",
      "type": "photo",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1002",
      "message": "Good morning from the roastery!",
      "timestamp": 1759147200,
      "reactions_count": 210,
      "comments_count": 14,
      "reshare_count": 3,
      "image": "https://example.com/img/1002.jpg",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1003",
      "type": "photo",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1003",
      "external_url": "https://brightsidecoffee.example/shop/ethiopia-guji",
      "message": "Introducing our new Ethiopia Guji single origin - now available online and in store.",
      "timestamp": 1759233600,
      "reactions_count": 640,
      "comments_count": 52,
      "reshare_count": 61,
      "image": "https://example.com/img/1003.jpg",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1004",
      "type": "video",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1004",
      "message": "Behind the scenes: roasting day.",
      "timestamp": 1759320000,
      "reactions_count": 180,
      "comments_count": 9,
      "reshare_count": 2,
      "video": "https://example.com/vid/1004.mp4",
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1005",
      "type": "status",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1005",
      "message": "We'\''re hiring a barista for our downtown cafe! Apply now at the counter or send us a message.",
      "timestamp": 1759406400,
      "reactions_count": 95,
      "comments_count": 31,
      "reshare_count": 18,
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "1006",
      "type": "status",
      "url": "https://www.facebook.com/brightsidecoffee/posts/1006",
      "message": "What'\''s your go-to brew method? Tell us below!",
      "timestamp": 1759492800,
      "reactions_count": 260,
      "comments_count": 142,
      "reshare_count": 6,
      "author": {
        "name": "Brightside Coffee Roasters",
        "id": "100090000000001",
        "url": "https://www.facebook.com/brightsidecoffee"
      }
    },
    {
      "post_id": "2001",
      "type": "status",
      "url": "https://www.facebook.com/northwindbikes/posts/2001",
      "message": "Our shop is closed Monday for inventory. Back Tuesday at 9.",
      "timestamp": 1759579200,
      "reactions_count": 12,
      "comments_count": 1,
      "reshare_count": 0,
      "author": {
        "name": "Northwind Bikes",
        "id": "100090000000002",
        "url": "https://www.facebook.com/northwindbikes"
      }
    },
    {
      "post_id": "3001",
      "type": "status",
      "url": "https://www.facebook.com/groups/austinparents/posts/3001",
      "message": "Anyone know where I can buy a kids balance bike under $120 near Austin? Looking for recommendations!",
      "timestamp": 1759665600,
      "reactions_count": 9,
      "comments_count": 14,
      "reshare_count": 0,
      "keyword": "kids balance bike",
      "author": {
        "name": "Austin Parents Group",
        "id": "100090000000003",
        "url": "https://www.facebook.com/groups/austinparents"
      }
    }
  ]
}' |
apify call rich_minds/fb-page-post-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rich_minds/fb-page-post-tracker"
        }
    }
}
```

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/1rUQfHX2vapIRlgdb/builds/ujac9httnWprH1qYY/openapi.json
