# Facebook Public Follower & Following Snapshot Tool (`nimbryxo-studio/facebook-public-follower-and-following-snapshot`) Actor

Get the public preview of a Facebook Page or Profile's followers, following, or friends, plus aggregate follower/following counts and parsed location, workplace, or school details.

- **URL**: https://apify.com/nimbryxo-studio/facebook-public-follower-and-following-snapshot.md
- **Developed by:** [Nimbryxo-studio](https://apify.com/nimbryxo-studio) (community)
- **Categories:** Social media, Automation, Lead generation
- **Security:** Limited permissions
- **Stats:** 3 total users, 0 monthly users, 95.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Facebook Public Follower & Following Snapshot Tool

Collect who publicly follows a Facebook Page or Profile — and who that Page or Profile follows — without a login, cookies, or proxy setup. The actor reads Facebook's own public collection pages, follows their pagination to the end (or to your row limit), and reports the real collection name, the reported list size, and whether the list was exhausted or cut short.

Facebook shows logged-out visitors an 8-row preview on each collection page. This actor does not stop at that preview: it keeps requesting the next public page until the list ends or `maxItems` is reached. Large accounts can still have slices that Facebook does not expose publicly, so `listSize` and `paginationComplete` are reported beside every result.

### What this actor does

- **Four modes:** `followers`, `following`, `both` (followers + following), and `friends` (a legacy alias that returns Followers — see the FAQ)
- **Real pagination:** returns more than the 8-row preview, up to `maxItems` (default 50), with per-target and global caps
- **In-list search:** `searchQuery` uses Facebook's own name filter and paginates through the matches
- **List transparency:** every target gets a summary with `collectionName`, `listSize`, `hasNextPage`, `paginationComplete`, `pagesFetched`, and `paginationStoppedReason`
- **Stable identities:** `profileId`, the raw Relay `profileTypename`, and a 1-based `position` on every row
- **Optional enrichment:** `includeProfileDetails` adds row-level follower/following counts plus About category, email, phone, website, address, work, education, current city, and hometown when Facebook exposes them (two opt-in GraphQL requests per row, with a profile-HTML fallback)
- **URL forms:** `/people/<name>/<id>/followers|following|friends|both` is rewritten to the working `?sk=` form; `m`/`mobile`/`touch` hosts are normalized to `www`; `mbasic` is rejected as login-gated with a clear status
- **Batch input:** any number of Page/Profile URLs or bare usernames per run; duplicates and invalid URLs are skipped
- **No login, no cookies, no proxy setup** — local direct, cloud residential last resort
- Empty fields are omitted, not returned as `null` or placeholder text

### Output

Each target produces follower/following row records plus one target summary record per collection. If a collection cannot be downloaded at all, it produces a `targetError` record for that collection instead.

#### Row fields

- `recordType` — `follower` or `following` for the actual collection; `friend` for the legacy `friends` alias
- `name` — display name
- `profileUrl` — direct profile/Page URL when Facebook provides one (omitted when the source returns no URL)
- `profileId` — Facebook account ID (numeric or `pfbid...`)
- `profileTypename` — the raw `__typename` Facebook returns on the collection edge. It is `User` for Pages too, so it is not a reliable Page/User discriminator; the actor does not claim a semantic type it cannot verify. (The old `profileType` field was removed for this reason; it always said `User`, including for Pages.)
- `imageUrl` — profile picture URL
- `isVerified` — whether Facebook shows a verification badge (`true`/`false`)
- `position` — 1-based position in the returned collection
- `collectionName` — the actual collection Facebook served: `Followers` or `Following`
- `requestedMode` — the mode that produced this row: `followers`, `following`, `both`, or `friends`
- `isFallback` — `true` only when the requested list was served from a different collection (currently `friends` → Followers)
- `location` — parsed location when Facebook's subtitle line clearly shows one
- `worksAt` — parsed workplace when confidently recognizable
- `studiedAt` — parsed school when confidently recognizable
- `subtitleRaw` — the raw subtitle text, kept even when it cannot be parsed
- `profileCategory` — Page/Profile category (only with `includeProfileDetails=true`)
- `profileDelegateId` — canonical Page/Profile ID from the profile page (only with `includeProfileDetails=true`)
- `profileFollowersCount` / `profileFollowersCountFormatted` — row-level follower count and Facebook's abbreviated form (only with `includeProfileDetails=true`; omitted when the header query has no social context)
- `profileFollowingCount` / `profileFollowingCountFormatted` — row-level following count and its abbreviated form (only with `includeProfileDetails=true`)
- `profileEmail`, `profilePhone`, `profileWebsite`, `profileAddress` — About contact fields when Facebook exposes them (only with `includeProfileDetails=true`)
- `profileWork`, `profileEducation`, `profileCurrentCity`, `profileHometown` — About text fields when Facebook exposes them (only with `includeProfileDetails=true`)
- `accessState` — the target's access state (`results` for rows)
- `sourceUrl` — the exact followers/following URL that was fetched
- `targetUrl` — the original URL/username from your input
- `scrapedAt` — ISO 8601 timestamp

#### Target summary fields (`recordType: "targetSummary"`)

- `targetUrl` — the original input
- `mode` — the requested mode
- `requestedMode` — the requested mode (same as `mode`; kept explicitly for `both` and `friends`)
- `accessState` — `results`, `empty_valid`, `private_or_restricted`, `not_found`, `login_required`, `checkpoint_or_challenge`, `rate_limited`, or `schema_failure`
- `returnedCount` — rows actually emitted for this collection (can exceed 8)
- `followersCount` / `followersCountFormatted` — headline follower count, parsed and as Facebook's abbreviated string
- `followingCount` / `followingCountFormatted` — headline following count
- `collectionName` — the actual collection served
- `listSize` — Facebook's reported size for that collection. It is approximate, not a hard cap; use `hasNextPage` to know whether more remained
- `hasNextPage` — whether Facebook still reported more rows when the actor stopped
- `paginationComplete` — `true` when the source was exhausted or genuinely empty; `false` when stopped by `maxItems`, a block, an unavailable/private target, or an error
- `pagesFetched` — number of pagination pages fetched for this collection
- `searchQuery` — the applied search filter (omitted when empty)
- `isFallback` — `true` for the legacy `friends` alias
- `paginationStoppedReason` — `source_exhausted`, `max_items`, `max_items_per_target`, `rate_limited`, `graphql_error`, `graphql_unavailable`, `no_collection`, or `empty`
- `scrapedAt` — ISO 8601 timestamp
- `recordType` — always `targetSummary`

#### Target error record (`recordType: "targetError"`)

When every download attempt for a collection fails, the dataset gets one record with `targetUrl`, `mode`, `accessState: "fetch_failed"`, `errorMessage`, and `scrapedAt`. A failed collection never stops the rest of the run.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | – | Facebook Page/Profile URLs, `profile.php?id=...`, `/people/<name>/<id>/` (plus `/followers`, `/following`, `/friends`, or `/both` suffixes), `/p/...`, or bare usernames. URLs ending in `/followers`, `/following`, `/friends`, or `?sk=...` are accepted. `m`/`mobile`/`touch` hosts are normalized to `www`; `mbasic` is rejected as login-gated. Bare usernames are accepted by the actor; the Console URL-list field itself expects a full URL. |
| `mode` | string | `followers` | `followers`, `following`, `both`, or `friends` (legacy alias → Followers). |
| `maxItems` | integer | `50` | Total row cap across all targets (1–10,000). The daily prefill uses 8. |
| `maxItemsPerTarget` | integer | `0` | Optional per-target cap (0 = no cap). |
| `searchQuery` | string | `""` | Keep accounts whose name contains this text. Empty or one character means no filter. |
| `includeProfileDetails` | boolean | `false` | Add row-level counts and About category/contact/work/education/current-city/hometown fields. Two opt-in GraphQL requests per row plus a profile-HTML fallback; absent fields are omitted. |
| `deduplicateRows` | boolean | `false` | Emit each `profileId`/`profileUrl` once across all targets and modes. |

#### Example: followers of a Page

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nike" }],
  "mode": "followers",
  "maxItems": 100
}
```

#### Example: followers and following together

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nike" }],
  "mode": "both",
  "maxItems": 40
}
```

#### Example: search inside a large follower list

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nike" }],
  "mode": "followers",
  "searchQuery": "mar",
  "maxItems": 50
}
```

#### Example: several targets with a per-target cap and profile enrichment

```json
{
  "startUrls": [
    { "url": "https://www.facebook.com/nike" },
    { "url": "https://www.facebook.com/zuck" }
  ],
  "mode": "followers",
  "maxItems": 20,
  "maxItemsPerTarget": 10,
  "includeProfileDetails": true
}
```

### Use cases

- **Audience research** — collect and compare the public follower lists of related Pages
- **Influencer and partnership vetting** — sample follower identities, verification status, and Page categories
- **Competitive monitoring** — track how a Page's public follower/following lists change between scheduled runs
- **Lead enrichment** — add stable Facebook profile IDs plus optional row-level counts, category, and contact fields to a public audience list
- **Brand safety review** — inspect which public accounts a Page follows
- **Growth analysis** — combine `listSize`, `hasNextPage`, and stable `profileId` values across snapshots

### FAQ

**Can I get every follower of a very large account?**
You get every row Facebook exposes to a logged-out visitor, up to your `maxItems`. Facebook sometimes exposes only a slice of a large account's list. The summary tells you the reported `listSize` and whether `hasNextPage` remained `true`, so you can see when the source stopped before the reported total.

**Why does `returnedCount` differ from `listSize`?**
`listSize` is Facebook's own reported size for the collection. It can be approximate or stale (a Page may report 576 while 640 rows are still enumerable). `returnedCount` is what the actor actually emitted. Use `hasNextPage` and `paginationComplete` to judge whether more remained.

**How is `maxItems` shared in `both` mode?**
The remaining row budget is split across Followers and Following, so both lists contribute instead of one list consuming the whole cap. Rows left unused by a shorter list flow to the other list. If `maxItemsPerTarget` is reached in the first collection, that collection's rows stop at the cap but the other collection still emits its summary with `returnedCount: 0` and `paginationStoppedReason: "max_items_per_target"` (or `"max_items"` when the global cap was hit). Summary records do not count toward either row cap.

**What does `friends` mode return?**
It is a legacy alias. Facebook does not expose a Friends roster to logged-out visitors; the friends tab serves the Followers collection. The actor therefore returns Followers, sets `collectionName: "Followers"`, `isFallback: true`, and `requestedMode: "friends"`. Rows keep `recordType: "friend"` for backward compatibility.

**Do I need a Facebook account, cookies, or a proxy?**
No. The actor uses Facebook's public, logged-out pages. No login, cookies, or proxy configuration is required. It runs direct locally; on Apify cloud, cloud verification showed direct and AUTO GraphQL both rate-limited, so the actor uses a hardcoded residential last resort for reliable runs. Nothing is configured in the input, and no paid group is a schema default.

**What does `includeProfileDetails` return, and how reliable is it?**
It adds row-level follower/following counts from Facebook's header query and About category/contact/work/education/current-city/hometown fields from the light About query. In the Phase 6 cookieless probe, counts filled 30/30 row profiles, category 30/30, and at least one contact field 20/30. Coverage is conditional: private/hidden sources, `pfbid` rows that return empty About sections, and accounts without a field will omit it. The actor falls back to the profile HTML and never fabricates a missing value. This mode costs two extra GraphQL requests per row (plus a fallback request when needed), so it is off by default and capped by `maxItems`.

**Why is a location, workplace, or school missing on some rows?**
Facebook shows a subtitle line for only a minority of accounts (about 18–19% in the live sample). The actor parses it conservatively and always keeps `subtitleRaw`; it never fabricates values. `includeProfileDetails` can add structured About fields for some of the rows whose subtitle is empty.

**What is the difference between `private_or_restricted` and `not_found`?**
An HTTP 404 is `not_found`. A 200 page with Facebook's "This content isn't available at the moment / owner only shared it" message is `private_or_restricted`. Facebook serves the same 200 page for a private target and a nonexistent vanity, so the actor cannot distinguish those two cases; this is a documented limitation, not a missing retry.

**Why did a run stop with `paginationStoppedReason: "max_items"`?**
The row cap was reached before the source ended. Increase `maxItems` (or `maxItemsPerTarget`) to collect more, or keep the cap and use the summary's `hasNextPage` to confirm that more existed.

**Why are some profile or image URLs missing?**
Facebook occasionally returns a row with no profile URL. The row is kept with its `profileId` so you do not lose the account. `imageUrl` values are signed Facebook CDN links with `oh`/`oe` parameters; the observed expiry was about four days, and old links return 403. Rehost them immediately if you need durable images.

**What if a run is rate-limited or only partly complete?**
Facebook returns GraphQL error code `1675004` when it rate-limits the logged-out collection query. The actor treats that code as `rate_limited` regardless of the message, keeps every row already collected, preserves the preview's `hasNextPage`, and finishes the run with a status message that names the partial targets and the rate limit. On Apify cloud, direct and AUTO GraphQL were both rate-limited in verification, so the actor uses a hardcoded residential last resort; residential is never a schema default or an input field.

**Is this actor affiliated with Facebook or Meta?**
No. It is an independent third-party actor that reads publicly accessible Facebook pages. It is not affiliated with, endorsed by, or operated by Meta Platforms, Inc.

### Limitations (hard limits and honest coverage)

- **8 rows per request.** Facebook caps every public collection response at 8 rows regardless of the requested `count`; more rows require more requests. A 1,000-row run is roughly 125 collection requests. Cursor pagination is the mitigation, not a larger page.
- **No cookieless Friends roster.** Every logged-out Friends URL form, section ID, and collection ID returned Followers, an empty list, or a login wall. `friends` is a truthful legacy alias for Followers only.
- **Private vs nonexistent at HTTP 200.** Facebook serves the same "This content isn't available at the moment / owner only shared it" page for both. Only a real HTTP 404 is `not_found`; a 200 page stays `private_or_restricted`. The actor cannot distinguish the two without a 404.
- **`listSize` is approximate.** It is Facebook's reported collection count, not a stop signal or a promise of enumerable rows. Use `hasNextPage`, `paginationComplete`, and `paginationStoppedReason`.
- **Image URLs expire.** Facebook CDN links are signed (observed ~4-day expiry); old links return 403. Rehost immediately for durable media.
- **Subtitles are sparse.** Only about 18–19% of rows expose a subtitle line; missing location/work/school values are source sparsity, not parser loss. The parser is conservative and keeps `subtitleRaw`.
- **Counts and contact fields are conditional.** `includeProfileDetails` fills what Facebook exposes (30/30 counts and category, 20/30 any contact in the Phase 6 probe) and omits the rest. It never fabricates values.
- **`mbasic` is login-gated.** The normalizer rejects `mbasic.facebook.com` with a clear status; `m`/`mobile`/`touch` are normalized to `www`.
- Group members, private lists, and other login-gated surfaces are out of scope.
- The Console's URL-list editor requires a full URL. Bare usernames are accepted by the actor when supplied through the input JSON or API.

# Actor input Schema

## `startUrls` (type: `array`):

One or more Facebook Page or Profile targets. Accepts full URLs, profile.php?id=..., /people/<name>/<id>/ (plus /followers, /following, /friends, or /both suffixes), /p/..., and URLs already ending in /followers, /following, or ?sk=... . The m/mobile/touch hosts are normalized to www. mbasic is rejected because it is login-gated, and groups/events/marketplace/media pages are not supported. Bare usernames are accepted by the actor; the Console URL-list editor expects a full URL.

## `mode` (type: `string`):

Which public list(s) to collect. 'both' collects Followers and Following. 'friends' is a legacy alias: Facebook does not expose Friends to logged-out visitors, so it returns the Followers collection with isFallback=true.

## `maxItems` (type: `integer`):

Hard cap on row records across all targets. The default is 50; the daily prefill uses 8. Set 8 to reproduce the old 8-row preview behavior.

## `maxItemsPerTarget` (type: `integer`):

Optional cap per target (0 = no per-target cap). Use it to stop one large account from using the whole maxItems budget.

## `searchQuery` (type: `string`):

Keep only accounts whose name contains this text (Facebook's own in-list search, case-insensitive substring). Empty means no filter; one-character queries are treated as no filter because Facebook ignores them.

## `includeProfileDetails` (type: `boolean`):

Adds row-level follower/following counts, category, email, phone, website, address, work, education, current city, and hometown when Facebook exposes them. Uses two opt-in GraphQL requests per row (header + light About) plus a profile-HTML fallback, all capped by maxItems. Off by default; absent fields are omitted, never fabricated.

## `deduplicateRows` (type: `boolean`):

When enabled, each profileId/profileUrl is emitted once across all targets and modes (first occurrence wins). Per-target pagination duplicates are always removed.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/nike"
    },
    {
      "url": "https://www.facebook.com/zuck"
    }
  ],
  "mode": "followers",
  "maxItems": 8,
  "maxItemsPerTarget": 0,
  "searchQuery": "",
  "includeProfileDetails": false,
  "deduplicateRows": false
}
```

# Actor output Schema

## `records` (type: `string`):

Dataset containing all scraped follower/following rows, per-target summary records, and per-target error records.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.facebook.com/nike"
        }
    ],
    "mode": "followers",
    "maxItems": 8,
    "maxItemsPerTarget": 0,
    "searchQuery": "",
    "includeProfileDetails": false,
    "deduplicateRows": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("nimbryxo-studio/facebook-public-follower-and-following-snapshot").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 = {
    "startUrls": [{ "url": "https://www.facebook.com/nike" }],
    "mode": "followers",
    "maxItems": 8,
    "maxItemsPerTarget": 0,
    "searchQuery": "",
    "includeProfileDetails": False,
    "deduplicateRows": False,
}

# Run the Actor and wait for it to finish
run = client.actor("nimbryxo-studio/facebook-public-follower-and-following-snapshot").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 '{
  "startUrls": [
    {
      "url": "https://www.facebook.com/nike"
    }
  ],
  "mode": "followers",
  "maxItems": 8,
  "maxItemsPerTarget": 0,
  "searchQuery": "",
  "includeProfileDetails": false,
  "deduplicateRows": false
}' |
apify call nimbryxo-studio/facebook-public-follower-and-following-snapshot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nimbryxo-studio/facebook-public-follower-and-following-snapshot"
        }
    }
}
```

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/ki8WyzulpUxTuJFkV/builds/Cd6CD1dQVf90O4egD/openapi.json
