Facebook Posts API: public page posts as JSON, without the Graph API
Meta's Graph API returns posts only for pages you manage, after app review. This endpoint returns the public posts of any page or profile, the way a signed-out visitor sees them, as JSON with reactions, comment and share counts, media and dates. It behaves like a REST API: one call in, records out, with an API token, pagination and webhooks.
What the Graph API gives you today
The /{page-id}/posts and /feed edges still exist, but reading them needs a Page access token for a page you administer, and reading other pages needs the Page Public Content Access feature, which is granted after app review and mostly to established apps. Comment counts and reactions come with more permissions still. For competitor pages, media pages or research across many pages, the Graph API is not an option in practice.
The endpoint
One HTTP call. The synchronous form waits for the run and returns the records in the same response (fine for up to a few minutes of work); the asynchronous form returns a run id at once and you read the dataset when it has finished, with webhooks if you want a callback.
Synchronous
curl -X POST "https://api.apify.com/v2/acts/alfalfa~facebook-posts-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN&format=json" \
-H 'Content-Type: application/json' \
-d '{"startUrls": [{"url": "https://www.facebook.com/humansofnewyork"}], "resultsLimit": 50, "onlyPostsNewerThan": "30 days"}'
Asynchronous
# start the run (returns immediately with the run id and dataset id)
curl -X POST "https://api.apify.com/v2/acts/alfalfa~facebook-posts-scraper/runs?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' -d '{"startUrls": [{"url": "https://www.facebook.com/humansofnewyork"}], "resultsLimit": 50, "onlyPostsNewerThan": "30 days"}'
# read the results when the run has finished (JSON, CSV or XLSX; paginate with offset and limit)
curl "https://api.apify.com/v2/datasets/<defaultDatasetId>/items?token=$APIFY_TOKEN&format=json&offset=0&limit=1000"
Python
from apify_client import ApifyClient # pip install apify-client
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("alfalfa/facebook-posts-scraper").call(run_input={
"startUrls": [
{
"url": "https://www.facebook.com/humansofnewyork"
}
],
"resultsLimit": 50,
"onlyPostsNewerThan": "30 days"
})
items = list(client.dataset(run.default_dataset_id).iterate_items())
print(len(items), items[0])
JavaScript
import { ApifyClient } from 'apify-client'; // npm install apify-client
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('alfalfa/facebook-posts-scraper').call({"startUrls": [{"url": "https://www.facebook.com/humansofnewyork"}], "resultsLimit": 50, "onlyPostsNewerThan": "30 days"});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);
Request fields
| Field | Type | Meaning |
|---|---|---|
startUrls | array | Page or profile URLs, as {"url": ...} objects or plain strings. |
resultsLimit | integer | Maximum posts per page. |
onlyPostsNewerThan | string | YYYY-MM-DD, ISO date-time, or relative such as 30 days; stops paging when older posts appear. |
onlyPostsOlderThan | string | Upper bound, same formats. |
captionText | boolean | Add the video transcript to video posts ($2 per 1,000 video posts looked up). |
maxConcurrency | integer | Pages processed in parallel, default 5. |
Response fields
One JSON object per record. Field names are the ones the official Apify Actor uses, so code written for it works unchanged.
| Field | Meaning |
|---|---|
postId, url, facebookUrl, pageName | Post ID and URL, the page it belongs to. |
time, timestamp | When it was posted, ISO 8601 and Unix time. |
text, link, previewTitle, previewDescription | The post text and the link preview when there is one. |
likes, comments, shares, reactionLikeCount ... reactionAngryCount | Engagement, with the reaction split. |
media, isVideo, viewsCount, captionText | Photos and videos with URLs, video views, optional transcript. |
topComments, sharedPost, user, collaborators, paidPartnership | Top comments, the original of a shared post, the author, co-authors, paid-partnership label. |
Limits, pagination, scheduling
- The synchronous endpoint cuts off after about five minutes; for large jobs use the asynchronous one and read the dataset in pages (
offsetandlimit, up to 1,000 per page, orformat=csvfor a file). - Runs can be scheduled on the platform (cron), triggered by webhook, and called from n8n, Make, Zapier or an AI agent through MCP.
- Set a maximum budget per run in the Actor settings; the run stops at the limit and never overshoots.
- Public data only: what a visitor without a Facebook account can see. Groups, Marketplace and private profiles are out of scope.
Pricing
$2 per 1,000 posts, no start fee, date filters free. Platform usage and residential proxies are included; you pay only for records written to the dataset. The Apify free plan includes $5 of usage a month, enough to try it properly.
Questions people ask
Does the Facebook Graph API still let me read posts of any page?
Only pages you administer, with a Page access token. Other pages need the Page Public Content Access feature after app review, which most developers do not get. This endpoint reads the public page instead and needs no Facebook account at all.
Is it a real API?
It is an Apify Actor called through the Apify REST API: POST the input, get JSON back, or start a run and read the dataset later. Tokens, pagination, webhooks and schedules work like any other API.
Can I get only new posts every day?
Yes. Set onlyPostsNewerThan to yesterday and schedule the run; the response contains only the posts published since then.
What does it cost?
$2 per 1,000 posts with platform usage included and no start fee; the official Apify posts scraper charges $5 plus a start fee and a fee per filter.