{
  "data": {
    "id": "Cd6CD1dQVf90O4egD",
    "actId": "ki8WyzulpUxTuJFkV",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-10-29T06:57:43.099Z",
    "finishedAt": "2026-10-29T06:57:52.422Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 9217,
      "runTimeSecs": 9.217,
      "computeUnits": 0.01024111111111111,
      "imageSizeBytes": 357977600
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"Facebook Followers & Following Scraper Input\",\n  \"description\": \"Collect the public followers and/or following lists of Facebook Pages and Profiles, including real cursor pagination, list-size transparency, search, and optional profile counts/contact/category enrichment. No login, cookies, or proxy setup required.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"startUrls\": {\n      \"title\": \"Facebook Page or Profile URLs\",\n      \"type\": \"array\",\n      \"description\": \"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.\",\n      \"editor\": \"requestListSources\",\n      \"items\": {\n        \"type\": \"object\",\n        \"required\": [\n          \"url\"\n        ],\n        \"properties\": {\n          \"url\": {\n            \"type\": \"string\",\n            \"title\": \"URL\",\n            \"description\": \"A Facebook Page or Profile URL, profile.php?id=..., /people/... URL, or bare username.\"\n          }\n        }\n      },\n      \"prefill\": [\n        {\n          \"url\": \"https://www.facebook.com/nike\"\n        }\n      ],\n      \"example\": [\n        {\n          \"url\": \"https://www.facebook.com/nike\"\n        },\n        {\n          \"url\": \"https://www.facebook.com/zuck\"\n        }\n      ]\n    },\n    \"mode\": {\n      \"title\": \"Mode\",\n      \"type\": \"string\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"followers\",\n        \"following\",\n        \"both\",\n        \"friends\"\n      ],\n      \"enumTitles\": [\n        \"Followers\",\n        \"Following\",\n        \"Followers + Following\",\n        \"Friends (legacy alias - returns Followers)\"\n      ],\n      \"default\": \"followers\",\n      \"prefill\": \"followers\",\n      \"description\": \"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.\"\n    },\n    \"maxItems\": {\n      \"title\": \"Maximum total rows\",\n      \"type\": \"integer\",\n      \"description\": \"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.\",\n      \"minimum\": 1,\n      \"maximum\": 10000,\n      \"default\": 50,\n      \"prefill\": 8\n    },\n    \"maxItemsPerTarget\": {\n      \"title\": \"Maximum rows per target\",\n      \"type\": \"integer\",\n      \"description\": \"Optional cap per target (0 = no per-target cap). Use it to stop one large account from using the whole maxItems budget.\",\n      \"minimum\": 0,\n      \"maximum\": 10000,\n      \"default\": 0,\n      \"prefill\": 0\n    },\n    \"searchQuery\": {\n      \"title\": \"Search inside the list\",\n      \"type\": \"string\",\n      \"editor\": \"textfield\",\n      \"description\": \"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.\",\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"includeProfileDetails\": {\n      \"title\": \"Include profile counts and contact details\",\n      \"type\": \"boolean\",\n      \"description\": \"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.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"deduplicateRows\": {\n      \"title\": \"Deduplicate rows across targets\",\n      \"type\": \"boolean\",\n      \"description\": \"When enabled, each profileId/profileUrl is emitted once across all targets and modes (first occurrence wins). Per-target pagination duplicates are always removed.\",\n      \"default\": false,\n      \"prefill\": false\n    }\n  },\n  \"required\": [\n    \"startUrls\"\n  ]\n}",
    "readme": "# Facebook Public Follower & Following Snapshot Tool\n\nCollect 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.\n\nFacebook 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.\n\n## What this actor does\n\n- **Four modes:** `followers`, `following`, `both` (followers + following), and `friends` (a legacy alias that returns Followers — see the FAQ)\n- **Real pagination:** returns more than the 8-row preview, up to `maxItems` (default 50), with per-target and global caps\n- **In-list search:** `searchQuery` uses Facebook's own name filter and paginates through the matches\n- **List transparency:** every target gets a summary with `collectionName`, `listSize`, `hasNextPage`, `paginationComplete`, `pagesFetched`, and `paginationStoppedReason`\n- **Stable identities:** `profileId`, the raw Relay `profileTypename`, and a 1-based `position` on every row\n- **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)\n- **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\n- **Batch input:** any number of Page/Profile URLs or bare usernames per run; duplicates and invalid URLs are skipped\n- **No login, no cookies, no proxy setup** — local direct, cloud residential last resort\n- Empty fields are omitted, not returned as `null` or placeholder text\n\n## Output\n\nEach 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.\n\n### Row fields\n\n- `recordType` — `follower` or `following` for the actual collection; `friend` for the legacy `friends` alias\n- `name` — display name\n- `profileUrl` — direct profile/Page URL when Facebook provides one (omitted when the source returns no URL)\n- `profileId` — Facebook account ID (numeric or `pfbid...`)\n- `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.)\n- `imageUrl` — profile picture URL\n- `isVerified` — whether Facebook shows a verification badge (`true`/`false`)\n- `position` — 1-based position in the returned collection\n- `collectionName` — the actual collection Facebook served: `Followers` or `Following`\n- `requestedMode` — the mode that produced this row: `followers`, `following`, `both`, or `friends`\n- `isFallback` — `true` only when the requested list was served from a different collection (currently `friends` → Followers)\n- `location` — parsed location when Facebook's subtitle line clearly shows one\n- `worksAt` — parsed workplace when confidently recognizable\n- `studiedAt` — parsed school when confidently recognizable\n- `subtitleRaw` — the raw subtitle text, kept even when it cannot be parsed\n- `profileCategory` — Page/Profile category (only with `includeProfileDetails=true`)\n- `profileDelegateId` — canonical Page/Profile ID from the profile page (only with `includeProfileDetails=true`)\n- `profileFollowersCount` / `profileFollowersCountFormatted` — row-level follower count and Facebook's abbreviated form (only with `includeProfileDetails=true`; omitted when the header query has no social context)\n- `profileFollowingCount` / `profileFollowingCountFormatted` — row-level following count and its abbreviated form (only with `includeProfileDetails=true`)\n- `profileEmail`, `profilePhone`, `profileWebsite`, `profileAddress` — About contact fields when Facebook exposes them (only with `includeProfileDetails=true`)\n- `profileWork`, `profileEducation`, `profileCurrentCity`, `profileHometown` — About text fields when Facebook exposes them (only with `includeProfileDetails=true`)\n- `accessState` — the target's access state (`results` for rows)\n- `sourceUrl` — the exact followers/following URL that was fetched\n- `targetUrl` — the original URL/username from your input\n- `scrapedAt` — ISO 8601 timestamp\n\n### Target summary fields (`recordType: \"targetSummary\"`)\n\n- `targetUrl` — the original input\n- `mode` — the requested mode\n- `requestedMode` — the requested mode (same as `mode`; kept explicitly for `both` and `friends`)\n- `accessState` — `results`, `empty_valid`, `private_or_restricted`, `not_found`, `login_required`, `checkpoint_or_challenge`, `rate_limited`, or `schema_failure`\n- `returnedCount` — rows actually emitted for this collection (can exceed 8)\n- `followersCount` / `followersCountFormatted` — headline follower count, parsed and as Facebook's abbreviated string\n- `followingCount` / `followingCountFormatted` — headline following count\n- `collectionName` — the actual collection served\n- `listSize` — Facebook's reported size for that collection. It is approximate, not a hard cap; use `hasNextPage` to know whether more remained\n- `hasNextPage` — whether Facebook still reported more rows when the actor stopped\n- `paginationComplete` — `true` when the source was exhausted or genuinely empty; `false` when stopped by `maxItems`, a block, an unavailable/private target, or an error\n- `pagesFetched` — number of pagination pages fetched for this collection\n- `searchQuery` — the applied search filter (omitted when empty)\n- `isFallback` — `true` for the legacy `friends` alias\n- `paginationStoppedReason` — `source_exhausted`, `max_items`, `max_items_per_target`, `rate_limited`, `graphql_error`, `graphql_unavailable`, `no_collection`, or `empty`\n- `scrapedAt` — ISO 8601 timestamp\n- `recordType` — always `targetSummary`\n\n### Target error record (`recordType: \"targetError\"`)\n\nWhen 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.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `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. |\n| `mode` | string | `followers` | `followers`, `following`, `both`, or `friends` (legacy alias → Followers). |\n| `maxItems` | integer | `50` | Total row cap across all targets (1–10,000). The daily prefill uses 8. |\n| `maxItemsPerTarget` | integer | `0` | Optional per-target cap (0 = no cap). |\n| `searchQuery` | string | `\"\"` | Keep accounts whose name contains this text. Empty or one character means no filter. |\n| `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. |\n| `deduplicateRows` | boolean | `false` | Emit each `profileId`/`profileUrl` once across all targets and modes. |\n\n### Example: followers of a Page\n\n```json\n{\n  \"startUrls\": [{ \"url\": \"https://www.facebook.com/nike\" }],\n  \"mode\": \"followers\",\n  \"maxItems\": 100\n}\n```\n\n### Example: followers and following together\n\n```json\n{\n  \"startUrls\": [{ \"url\": \"https://www.facebook.com/nike\" }],\n  \"mode\": \"both\",\n  \"maxItems\": 40\n}\n```\n\n### Example: search inside a large follower list\n\n```json\n{\n  \"startUrls\": [{ \"url\": \"https://www.facebook.com/nike\" }],\n  \"mode\": \"followers\",\n  \"searchQuery\": \"mar\",\n  \"maxItems\": 50\n}\n```\n\n### Example: several targets with a per-target cap and profile enrichment\n\n```json\n{\n  \"startUrls\": [\n    { \"url\": \"https://www.facebook.com/nike\" },\n    { \"url\": \"https://www.facebook.com/zuck\" }\n  ],\n  \"mode\": \"followers\",\n  \"maxItems\": 20,\n  \"maxItemsPerTarget\": 10,\n  \"includeProfileDetails\": true\n}\n```\n\n## Use cases\n\n- **Audience research** — collect and compare the public follower lists of related Pages\n- **Influencer and partnership vetting** — sample follower identities, verification status, and Page categories\n- **Competitive monitoring** — track how a Page's public follower/following lists change between scheduled runs\n- **Lead enrichment** — add stable Facebook profile IDs plus optional row-level counts, category, and contact fields to a public audience list\n- **Brand safety review** — inspect which public accounts a Page follows\n- **Growth analysis** — combine `listSize`, `hasNextPage`, and stable `profileId` values across snapshots\n\n## FAQ\n\n**Can I get every follower of a very large account?**\nYou 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.\n\n**Why does `returnedCount` differ from `listSize`?**\n`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.\n\n**How is `maxItems` shared in `both` mode?**\nThe 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.\n\n**What does `friends` mode return?**\nIt 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.\n\n**Do I need a Facebook account, cookies, or a proxy?**\nNo. 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.\n\n**What does `includeProfileDetails` return, and how reliable is it?**\nIt 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`.\n\n**Why is a location, workplace, or school missing on some rows?**\nFacebook 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.\n\n**What is the difference between `private_or_restricted` and `not_found`?**\nAn 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.\n\n**Why did a run stop with `paginationStoppedReason: \"max_items\"`?**\nThe 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.\n\n**Why are some profile or image URLs missing?**\nFacebook 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.\n\n**What if a run is rate-limited or only partly complete?**\nFacebook 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.\n\n**Is this actor affiliated with Facebook or Meta?**\nNo. 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.\n\n## Limitations (hard limits and honest coverage)\n\n- **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.\n- **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.\n- **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.\n- **`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`.\n- **Image URLs expire.** Facebook CDN links are signed (observed ~4-day expiry); old links return 403. Rehost immediately for durable media.\n- **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`.\n- **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.\n- **`mbasic` is login-gated.** The normalizer rejects `mbasic.facebook.com` with a clear status; `m`/`mobile`/`touch` are normalized to `www`.\n- Group members, private lists, and other login-gated surfaces are out of scope.\n- 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.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "facebook-public-follower-and-following-snapshot",
      "title": "Facebook Public Follower & Following Snapshot Tool",
      "description": "Collect public Facebook Page/Profile followers and following with real cursor pagination, search, list-size transparency, and optional profile counts/contact/category enrichment. No login, cookies, or proxy setup required.",
      "version": "1.1",
      "buildTag": "latest",
      "dockerfile": "./Dockerfile",
      "readme": "# Facebook Public Follower & Following Snapshot Tool\n\nCollect 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.\n\nFacebook 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.\n\n## What this actor does\n\n- **Four modes:** `followers`, `following`, `both` (followers + following), and `friends` (a legacy alias that returns Followers — see the FAQ)\n- **Real pagination:** returns more than the 8-row preview, up to `maxItems` (default 50), with per-target and global caps\n- **In-list search:** `searchQuery` uses Facebook's own name filter and paginates through the matches\n- **List transparency:** every target gets a summary with `collectionName`, `listSize`, `hasNextPage`, `paginationComplete`, `pagesFetched`, and `paginationStoppedReason`\n- **Stable identities:** `profileId`, the raw Relay `profileTypename`, and a 1-based `position` on every row\n- **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)\n- **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\n- **Batch input:** any number of Page/Profile URLs or bare usernames per run; duplicates and invalid URLs are skipped\n- **No login, no cookies, no proxy setup** — local direct, cloud residential last resort\n- Empty fields are omitted, not returned as `null` or placeholder text\n\n## Output\n\nEach 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.\n\n### Row fields\n\n- `recordType` — `follower` or `following` for the actual collection; `friend` for the legacy `friends` alias\n- `name` — display name\n- `profileUrl` — direct profile/Page URL when Facebook provides one (omitted when the source returns no URL)\n- `profileId` — Facebook account ID (numeric or `pfbid...`)\n- `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.)\n- `imageUrl` — profile picture URL\n- `isVerified` — whether Facebook shows a verification badge (`true`/`false`)\n- `position` — 1-based position in the returned collection\n- `collectionName` — the actual collection Facebook served: `Followers` or `Following`\n- `requestedMode` — the mode that produced this row: `followers`, `following`, `both`, or `friends`\n- `isFallback` — `true` only when the requested list was served from a different collection (currently `friends` → Followers)\n- `location` — parsed location when Facebook's subtitle line clearly shows one\n- `worksAt` — parsed workplace when confidently recognizable\n- `studiedAt` — parsed school when confidently recognizable\n- `subtitleRaw` — the raw subtitle text, kept even when it cannot be parsed\n- `profileCategory` — Page/Profile category (only with `includeProfileDetails=true`)\n- `profileDelegateId` — canonical Page/Profile ID from the profile page (only with `includeProfileDetails=true`)\n- `profileFollowersCount` / `profileFollowersCountFormatted` — row-level follower count and Facebook's abbreviated form (only with `includeProfileDetails=true`; omitted when the header query has no social context)\n- `profileFollowingCount` / `profileFollowingCountFormatted` — row-level following count and its abbreviated form (only with `includeProfileDetails=true`)\n- `profileEmail`, `profilePhone`, `profileWebsite`, `profileAddress` — About contact fields when Facebook exposes them (only with `includeProfileDetails=true`)\n- `profileWork`, `profileEducation`, `profileCurrentCity`, `profileHometown` — About text fields when Facebook exposes them (only with `includeProfileDetails=true`)\n- `accessState` — the target's access state (`results` for rows)\n- `sourceUrl` — the exact followers/following URL that was fetched\n- `targetUrl` — the original URL/username from your input\n- `scrapedAt` — ISO 8601 timestamp\n\n### Target summary fields (`recordType: \"targetSummary\"`)\n\n- `targetUrl` — the original input\n- `mode` — the requested mode\n- `requestedMode` — the requested mode (same as `mode`; kept explicitly for `both` and `friends`)\n- `accessState` — `results`, `empty_valid`, `private_or_restricted`, `not_found`, `login_required`, `checkpoint_or_challenge`, `rate_limited`, or `schema_failure`\n- `returnedCount` — rows actually emitted for this collection (can exceed 8)\n- `followersCount` / `followersCountFormatted` — headline follower count, parsed and as Facebook's abbreviated string\n- `followingCount` / `followingCountFormatted` — headline following count\n- `collectionName` — the actual collection served\n- `listSize` — Facebook's reported size for that collection. It is approximate, not a hard cap; use `hasNextPage` to know whether more remained\n- `hasNextPage` — whether Facebook still reported more rows when the actor stopped\n- `paginationComplete` — `true` when the source was exhausted or genuinely empty; `false` when stopped by `maxItems`, a block, an unavailable/private target, or an error\n- `pagesFetched` — number of pagination pages fetched for this collection\n- `searchQuery` — the applied search filter (omitted when empty)\n- `isFallback` — `true` for the legacy `friends` alias\n- `paginationStoppedReason` — `source_exhausted`, `max_items`, `max_items_per_target`, `rate_limited`, `graphql_error`, `graphql_unavailable`, `no_collection`, or `empty`\n- `scrapedAt` — ISO 8601 timestamp\n- `recordType` — always `targetSummary`\n\n### Target error record (`recordType: \"targetError\"`)\n\nWhen 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.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `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. |\n| `mode` | string | `followers` | `followers`, `following`, `both`, or `friends` (legacy alias → Followers). |\n| `maxItems` | integer | `50` | Total row cap across all targets (1–10,000). The daily prefill uses 8. |\n| `maxItemsPerTarget` | integer | `0` | Optional per-target cap (0 = no cap). |\n| `searchQuery` | string | `\"\"` | Keep accounts whose name contains this text. Empty or one character means no filter. |\n| `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. |\n| `deduplicateRows` | boolean | `false` | Emit each `profileId`/`profileUrl` once across all targets and modes. |\n\n### Example: followers of a Page\n\n```json\n{\n  \"startUrls\": [{ \"url\": \"https://www.facebook.com/nike\" }],\n  \"mode\": \"followers\",\n  \"maxItems\": 100\n}\n```\n\n### Example: followers and following together\n\n```json\n{\n  \"startUrls\": [{ \"url\": \"https://www.facebook.com/nike\" }],\n  \"mode\": \"both\",\n  \"maxItems\": 40\n}\n```\n\n### Example: search inside a large follower list\n\n```json\n{\n  \"startUrls\": [{ \"url\": \"https://www.facebook.com/nike\" }],\n  \"mode\": \"followers\",\n  \"searchQuery\": \"mar\",\n  \"maxItems\": 50\n}\n```\n\n### Example: several targets with a per-target cap and profile enrichment\n\n```json\n{\n  \"startUrls\": [\n    { \"url\": \"https://www.facebook.com/nike\" },\n    { \"url\": \"https://www.facebook.com/zuck\" }\n  ],\n  \"mode\": \"followers\",\n  \"maxItems\": 20,\n  \"maxItemsPerTarget\": 10,\n  \"includeProfileDetails\": true\n}\n```\n\n## Use cases\n\n- **Audience research** — collect and compare the public follower lists of related Pages\n- **Influencer and partnership vetting** — sample follower identities, verification status, and Page categories\n- **Competitive monitoring** — track how a Page's public follower/following lists change between scheduled runs\n- **Lead enrichment** — add stable Facebook profile IDs plus optional row-level counts, category, and contact fields to a public audience list\n- **Brand safety review** — inspect which public accounts a Page follows\n- **Growth analysis** — combine `listSize`, `hasNextPage`, and stable `profileId` values across snapshots\n\n## FAQ\n\n**Can I get every follower of a very large account?**\nYou 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.\n\n**Why does `returnedCount` differ from `listSize`?**\n`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.\n\n**How is `maxItems` shared in `both` mode?**\nThe 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.\n\n**What does `friends` mode return?**\nIt 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.\n\n**Do I need a Facebook account, cookies, or a proxy?**\nNo. 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.\n\n**What does `includeProfileDetails` return, and how reliable is it?**\nIt 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`.\n\n**Why is a location, workplace, or school missing on some rows?**\nFacebook 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.\n\n**What is the difference between `private_or_restricted` and `not_found`?**\nAn 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.\n\n**Why did a run stop with `paginationStoppedReason: \"max_items\"`?**\nThe 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.\n\n**Why are some profile or image URLs missing?**\nFacebook 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.\n\n**What if a run is rate-limited or only partly complete?**\nFacebook 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.\n\n**Is this actor affiliated with Facebook or Meta?**\nNo. 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.\n\n## Limitations (hard limits and honest coverage)\n\n- **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.\n- **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.\n- **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.\n- **`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`.\n- **Image URLs expire.** Facebook CDN links are signed (observed ~4-day expiry); old links return 403. Rehost immediately for durable media.\n- **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`.\n- **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.\n- **`mbasic` is login-gated.** The normalizer rejects `mbasic.facebook.com` with a clear status; `m`/`mobile`/`touch` are normalized to `www`.\n- Group members, private lists, and other login-gated surfaces are out of scope.\n- 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.\n",
      "input": {
        "title": "Facebook Followers & Following Scraper Input",
        "description": "Collect the public followers and/or following lists of Facebook Pages and Profiles, including real cursor pagination, list-size transparency, search, and optional profile counts/contact/category enrichment. No login, cookies, or proxy setup required.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "startUrls": {
            "title": "Facebook Page or Profile URLs",
            "type": "array",
            "description": "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.",
            "editor": "requestListSources",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL",
                  "description": "A Facebook Page or Profile URL, profile.php?id=..., /people/... URL, or bare username."
                }
              }
            },
            "prefill": [
              {
                "url": "https://www.facebook.com/nike"
              }
            ],
            "example": [
              {
                "url": "https://www.facebook.com/nike"
              },
              {
                "url": "https://www.facebook.com/zuck"
              }
            ]
          },
          "mode": {
            "title": "Mode",
            "type": "string",
            "editor": "select",
            "enum": [
              "followers",
              "following",
              "both",
              "friends"
            ],
            "enumTitles": [
              "Followers",
              "Following",
              "Followers + Following",
              "Friends (legacy alias - returns Followers)"
            ],
            "default": "followers",
            "prefill": "followers",
            "description": "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": {
            "title": "Maximum total rows",
            "type": "integer",
            "description": "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.",
            "minimum": 1,
            "maximum": 10000,
            "default": 50,
            "prefill": 8
          },
          "maxItemsPerTarget": {
            "title": "Maximum rows per target",
            "type": "integer",
            "description": "Optional cap per target (0 = no per-target cap). Use it to stop one large account from using the whole maxItems budget.",
            "minimum": 0,
            "maximum": 10000,
            "default": 0,
            "prefill": 0
          },
          "searchQuery": {
            "title": "Search inside the list",
            "type": "string",
            "editor": "textfield",
            "description": "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.",
            "default": "",
            "prefill": ""
          },
          "includeProfileDetails": {
            "title": "Include profile counts and contact details",
            "type": "boolean",
            "description": "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.",
            "default": false,
            "prefill": false
          },
          "deduplicateRows": {
            "title": "Deduplicate rows across targets",
            "type": "boolean",
            "description": "When enabled, each profileId/profileUrl is emitted once across all targets and modes (first occurrence wins). Per-target pagination duplicates are always removed.",
            "default": false,
            "prefill": false
          }
        },
        "required": [
          "startUrls"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Facebook Public Follower & Following Snapshot Tool Output",
        "description": "Facebook Page/Profile follower and following rows collected through public cursor pagination, plus per-target summaries with the actual collection name, reported list size, pagination completeness, access state, and follower/following counts. Optional includeProfileDetails enrichment adds row-level counts and About contact/category fields when Facebook exposes them.",
        "type": "object",
        "properties": {
          "records": {
            "type": "string",
            "title": "Followers / Following records",
            "description": "Dataset containing all scraped follower/following rows, per-target summary records, and per-target error records.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "categories": [
        "SOCIAL_MEDIA"
      ],
      "defaultRunOptions": {
        "memoryMbytes": 1024
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "views": {
            "overview": {
              "title": "Followers / Following",
              "transformation": {
                "fields": [
                  "recordType",
                  "name",
                  "profileId",
                  "profileTypename",
                  "position",
                  "collectionName",
                  "profileUrl",
                  "isVerified",
                  "location",
                  "accessState"
                ]
              },
              "display": {
                "component": "table",
                "properties": {}
              }
            }
          }
        }
      },
      "environmentVariables": {}
    },
    "buildNumber": "1.1.1",
    "imageDigest": "09526ee109e97b354aad127bf8cdc694024cdd38ecd2da9ada0d2cf77cda557e",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.1"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.01024111111111111
    }
  }
}