Facebook Graph API Guide 2026: How to Post to a Page with Tokens, Permissions, Photos, Videos, and Reels
How to post to a Facebook Page with the Graph API in 2026: Page access tokens that never expire, the pages_manage_posts permission set, /feed, /photos, /videos, and /video_reels with Node and Python examples, scheduling, rate limits, error codes, and app review.
TLDR: To post to a Facebook Page with the Graph API you need a Business-type app, a Page access token minted from a long-lived user token (it never expires on its own), and three permissions: pages_manage_posts, pages_read_engagement, and pages_show_list. Text and links go to /{page-id}/feed, photos to /{page-id}/photos (10 MB each, upload unpublished then attach for multi-photo posts), videos to /{page-id}/videos, and Reels through the three-phase /{page-id}/video_reels flow (9:16, 3 to 90 seconds, 30 per day). Scheduling is published=false plus a timestamp 10 minutes to 75 days out. Your own Pages work without review; other people’s Pages need Business Verification and App Review. The current version is v26.0, and each version lives about two years. Checked against developers.facebook.com on September 11, 2026.
What is the Facebook Graph API and can it post to a Page?
The Graph API is the single HTTP API for everything on Facebook: Pages, posts, photos, videos, comments, insights, ads, and the linked Instagram account. Every object has an ID, every relationship is an edge, and publishing is a POST to an edge such as /{page-id}/feed. Requests go to https://graph.facebook.com/v26.0/... with an access token as a parameter or bearer header.
For posting, the important constraint is that the API publishes to Pages only. There is no endpoint that posts to a personal profile, a Group, or an Event on behalf of a user. If your product needs to post to Facebook, it needs its users to have a Page and to give you a Page token. That matches how PostZen and every other scheduler work, and it is why the rest of this guide is about Pages.
The publishing surface is four edges:
| Edge | Publishes | Notes |
|---|---|---|
POST /{page-id}/feed |
Text, links, multi-photo posts | message or link required; attached_media for photos |
POST /{page-id}/photos |
One photo, or an unpublished photo for later use | 10 MB max; JPEG, BMP, PNG, GIF, TIFF |
POST /{page-id}/videos |
Feed video | file_url or the start/transfer/finish resumable flow |
POST /{page-id}/video_reels |
Reels | Three-phase upload; 9:16; 3 to 90 seconds |
Stories have their own edges (/photo_stories and /video_stories), comments are POST /{post-id}/comments, and deleting is DELETE /{post-id}.
Which Facebook API permissions do you need to post to a Page?
Permissions are requested in the login dialog and granted per user. Publishing needs three, and the other Pages permissions come up as soon as you read engagement or reply to comments.
| Permission | What it grants | Needed for |
|---|---|---|
pages_manage_posts |
Create, edit, and delete Page posts, photos, and videos | Every publish call |
pages_read_engagement |
Read the Page’s posts, followers, profile picture, and metadata | Dependency of pages_manage_posts |
pages_show_list |
List the Pages the user manages | Dependency; needed to call /me/accounts |
pages_manage_engagement |
Create, edit, and delete comments; like posts as the Page | First comments, replies |
pages_read_user_content |
Read comments, ratings, and other user content on the Page | Comment inboxes |
pages_manage_metadata |
Subscribe to Page webhooks and update Page settings | Webhooks for comments and mentions |
business_management |
Read and write Business Manager assets | System user tokens; needs Business Verification |
Two things trip people up. First, permissions are granted to the user, but publishing is authorised by the user’s task on the Page. The token holder needs the CREATE_CONTENT task (or full admin) on that specific Page, and MODERATE to comment. A user who only has the ANALYZE or ADVERTISE task will hold a valid token with pages_manage_posts granted and still get (#200) Permissions error on publish.
Second, permissions have two access levels. Standard Access is automatic and works only for Pages managed by people with a role on your app. Advanced Access is what you need to post to anyone else’s Page, and it requires Business Verification plus App Review per permission. Details are in the review section below.
How do Facebook Page access tokens work?
There are three token types on the way to a Page post, and getting the sequence right is most of the work.
- Short-lived user token. What Facebook Login returns. Lives a few hours.
- Long-lived user token. Exchange the short-lived token server-side with your app secret. Lives about 60 days.
- Page access token. Read from
/me/accountsusing the long-lived user token. When minted this way it has no expiry date.
The exchange and the Page lookup:
# 1. Short-lived → long-lived user token (server-side only; the app secret is here)
curl "https://graph.facebook.com/v26.0/oauth/access_token?grant_type=fb_exchange_token&client_id=$APP_ID&client_secret=$APP_SECRET&fb_exchange_token=$SHORT_LIVED_TOKEN"
# 2. Pages this user manages, with a Page token for each
curl "https://graph.facebook.com/v26.0/me/accounts?fields=id,name,access_token,tasks&access_token=$LONG_LIVED_USER_TOKEN"
The second call returns one entry per Page with access_token and the tasks array the user holds on it. Store the Page token against the Page ID. It does not expire on a timer, but it is invalidated when the user changes their password, removes your app, loses their Page role, or when Meta revokes it during a security event. So never assume it is alive; check it:
curl "https://graph.facebook.com/v26.0/debug_token?input_token=$PAGE_TOKEN&access_token=$APP_ID|$APP_SECRET"
The response carries is_valid, expires_at (0 for a non-expiring token), scopes, and granular_scopes listing the Page IDs each permission applies to. Run it before a scheduled publish, and treat error code 190 on any call as “reconnect required” rather than retryable. PostZen does exactly this: a Page whose token fails validation flips to disconnected in the accounts list and the user is asked to reconnect.
For server-to-server integrations with no human login, Business Manager system user tokens exist: an admin system user gets access to every asset in the business portfolio, an employee system user only to assets explicitly assigned. Meta’s Facebook Login for Business product is the newer path for tech providers and issues business-integration system user tokens through an embedded signup flow. Both need the business_management permission and a verified business.
How to create a Facebook app for Page posting
In the Meta for Developers dashboard, create an app and choose the Business type. The type is fixed at creation and Consumer apps cannot request the Pages permissions, so a wrong choice means a new app. Add the Facebook Login product, set your OAuth redirect URI, and request the three publishing permissions in the login dialog’s scope parameter.
While the app is in development mode, everyone with an app role (admin, developer, tester) can log in and publish to Pages they manage. That is Standard Access, and it is the sanctioned way to run an internal tool, a company Page bot, or a proof of concept without ever submitting for review. The ceiling is hard: the moment a user without an app role logs in, the Pages permissions are not granted.
How to post to a Facebook Page with the Graph API: text and link posts
A text post is one call to /feed with message. Add link for a link post with a preview card. The response is the post ID.
curl -X POST "https://graph.facebook.com/v26.0/$PAGE_ID/feed" \
-d "message=Registration is open for our September workshop." \
-d "link=https://example.com/workshop" \
-d "access_token=$PAGE_TOKEN"
# {"id":"1234567890_9876543210"}
In Node, with the built-in fetch:
const GRAPH = 'https://graph.facebook.com/v26.0';
async function graphPost(path, params, token) {
const body = new URLSearchParams({ ...params, access_token: token });
const res = await fetch(`${GRAPH}${path}`, { method: 'POST', body });
const json = await res.json();
if (!res.ok || json.error) {
const e = json.error ?? {};
throw new Error(`Graph ${e.code ?? res.status}${e.error_subcode ? `/${e.error_subcode}` : ''}: ${e.message ?? 'request failed'}`);
}
return json;
}
const post = await graphPost(
`/${process.env.PAGE_ID}/feed`,
{ message: 'Registration is open for our September workshop.', link: 'https://example.com/workshop' },
process.env.PAGE_TOKEN,
);
console.log(post.id);
And in Python with requests, since Meta’s only maintained Python package is the Marketing-oriented facebook_business SDK and the old facebook-sdk package has not shipped a release since November 2018:
import os
import requests
GRAPH = "https://graph.facebook.com/v26.0"
PAGE_ID = os.environ["PAGE_ID"]
PAGE_TOKEN = os.environ["PAGE_TOKEN"]
def graph_post(path: str, **params):
r = requests.post(f"{GRAPH}{path}", data={**params, "access_token": PAGE_TOKEN}, timeout=30)
body = r.json()
if "error" in body:
err = body["error"]
raise RuntimeError(f"Graph {err.get('code')}/{err.get('error_subcode')}: {err.get('message')}")
return body
post = graph_post(
f"/{PAGE_ID}/feed",
message="Registration is open for our September workshop.",
link="https://example.com/workshop",
)
print(post["id"])
The message field supports Page mentions in the form @[page-id]. The post limit is 63,206 characters, a number that has been stable for over a decade and that Facebook folds behind “See more” after a few hundred characters. Emoji are fine.
How to post photos to a Facebook Page
A single photo with a caption is one call to /photos with url (a public URL Facebook fetches) or source (multipart upload). The response has both the photo ID and the post ID.
curl -X POST "https://graph.facebook.com/v26.0/$PAGE_ID/photos" \
-d "url=https://cdn.example.com/workshop.jpg" \
-d "caption=Seats are filling up." \
-d "access_token=$PAGE_TOKEN"
# {"id":"111","post_id":"1234567890_222"}
Files are capped at 10 MB and Meta recommends keeping PNGs under 1 MB to avoid pixelation. Accepted formats are JPEG, BMP, PNG, GIF, and TIFF.
A multi-photo post is a two-step dance: upload each photo with published=false, then create one feed post that attaches them by ID.
async function postPhotos(pageId, token, message, urls) {
const ids = [];
for (const url of urls) {
const { id } = await graphPost(`/${pageId}/photos`, { url, published: 'false' }, token);
ids.push(id);
}
const params = { message };
ids.forEach((id, i) => {
params[`attached_media[${i}]`] = JSON.stringify({ media_fbid: id });
});
return graphPost(`/${pageId}/feed`, params, token);
}
Unpublished photos live in a temporary state for about 24 hours; if nothing attaches them by then Facebook deletes them. This is the exact flow PostZen runs for a multi-image Facebook post, and it is why an image post with ten pictures takes eleven Graph calls.
How to post a video or Reel to a Facebook Page
Feed videos go to /videos. The simplest form passes file_url and lets Facebook fetch the file:
video = graph_post(
f"/{PAGE_ID}/videos",
file_url="https://cdn.example.com/workshop-recap.mp4",
title="Workshop recap",
description="Highlights from last week's session.",
)
print(video["id"])
For local files or large uploads use the resumable protocol on the same edge: upload_phase=start with file_size returns an upload_session_id and byte offsets, upload_phase=transfer sends video_file_chunk at start_offset until the offsets meet, and upload_phase=finish publishes with the title and description. file_url and upload_phase cannot be combined in one request. Meta’s reference page no longer prints a fixed size or duration ceiling for feed video, so treat your own tests as the source of truth; PostZen enforces 1 GB per video on the Facebook target.
Reels are a separate edge with a three-phase flow and strict specs:
// 1. Start: get a video id and an upload URL
const start = await graphPost(`/${pageId}/video_reels`, { upload_phase: 'start' }, token);
// 2. Upload the bytes to rupload.facebook.com (hosted file variant)
await fetch(`https://rupload.facebook.com/video-upload/v26.0/${start.video_id}`, {
method: 'POST',
headers: { Authorization: `OAuth ${token}`, file_url: 'https://cdn.example.com/reel.mp4' },
});
// 3. Finish: publish with a description
await graphPost(
`/${pageId}/video_reels`,
{ upload_phase: 'finish', video_id: start.video_id, video_state: 'PUBLISHED', description: 'Three tips from the workshop.' },
token,
);
Reel requirements as documented: MP4 with H.264 or H.265, 9:16 aspect ratio, 1080×1920 recommended and 540×960 minimum, 3 to 90 seconds, 24 to 60 fps, and a cap of 30 published Reels per rolling 24 hours per Page. Stories use the same shape on /video_stories, and photo Stories are an unpublished photo ID posted to /photo_stories; a file that was already used in a published post cannot be reused for a Story.
How to schedule a Facebook Page post
The API schedules natively. Set published=false and scheduled_publish_time to a Unix timestamp; Facebook publishes it at that moment.
curl -X POST "https://graph.facebook.com/v26.0/$PAGE_ID/feed" \
-d "message=Doors open at 9." \
-d "published=false" \
-d "scheduled_publish_time=$(date -v+1d +%s)" \
-d "access_token=$PAGE_TOKEN"
The window is 10 minutes to 75 days from the time of the request; older guides say 30 days, and the reference page today says 75. Scheduled photos follow the same rule on /photos (but not temporary=true photos, which cannot carry a schedule). Scheduled posts show up in GET /{page-id}/feed with is_published=false and can be deleted before they go live.
Native scheduling is fine for one Page. It stops being fine when you need to reschedule, reorder, or coordinate the same content across networks, which is where a queue in your own system or a tool like PostZen’s social media scheduler earns its place: the post lives in your system until publish time and one edit changes it everywhere.
Facebook Graph API rate limits and error codes
Two budgets apply to Page publishing, both documented on Meta’s rate limiting page:
| Budget | Formula | Window |
|---|---|---|
| Page level (Business Use Case) | 4,800 × engaged users of the Page | Rolling 24 hours |
| App level | 200 × daily active users of the app | Rolling 1 hour |
Every response carries X-App-Usage and X-Business-Use-Case-Usage headers with the percentage of each budget consumed, and the business header includes estimated_time_to_regain_access once you are throttled. Meta’s guidance is blunt: when you hit the limit, stop calling and watch the headers, rather than retrying in a loop.
The error codes you will actually see on a publishing integration:
| Code | Meaning | What to do |
|---|---|---|
| 4 | App-level rate limit | Back off until X-App-Usage drops |
| 17 | User-level rate limit | Back off; spread users’ calls out |
| 10 | Permission denied | The token lacks a permission or the user lacks the Page task |
| 200 | Permissions error | Same causes as 10; most often a missing CREATE_CONTENT task |
| 190 | Token invalid or expired | Reconnect the user; do not retry |
| 100 | Invalid parameter | A bad field, a rejected URL, or a media file Facebook could not fetch |
| 368 | Temporarily blocked for policy reasons | Wait; repeated hits mean the content pattern looks like spam |
| 382 | Video file too small | Re-encode; the upload was truncated or empty |
Codes 4, 17, and 368 are retry-later. Codes 10, 200, and 190 are not retryable and should surface to the user as a reconnect or a permissions fix. PostZen’s Facebook publisher classifies them the same way: codes 4 and 17 become transient retries with backoff, while 190 and the 401 and 403 responses are treated as authentication failures that mark the account disconnected and fire an account webhook.
Facebook app review and business verification for pages_manage_posts
If your app posts to Pages owned by people without a role on the app, you need Advanced Access for each Pages permission, and that has two prerequisites:
- Business Verification of the Meta Business Manager account that owns the app. Required for Advanced Access since February 1, 2023. You submit business name, address, phone, website, and a document such as a registration certificate.
- App Review per permission. The reviewer watches a screencast of your real product exercising the permission and reads a use-case justification. Reviewers do not explore the app themselves, so the screencast is the evidence; a missing step or a mocked screen is a rejection.
Timelines are not published. Practitioners report a few business days for a clean single-permission submission and weeks for a multi-permission app with a rejection or two, so submit pages_show_list, pages_read_engagement, and pages_manage_posts together with one screencast that connects a Page and publishes a post. Add pages_manage_engagement only if you comment or reply. Our experience getting the Instagram publishing permissions through the same review process applies directly: reviewers judge the video, not the code.
Meta’s tech provider programme and Facebook Login for Business are the newer packaging of the same requirements for companies building on behalf of many businesses. They do not skip review; they change the token type you receive.
Posting to a Facebook Page through PostZen
Building the above is a few hundred lines once you add the token exchange, the 24-hour unpublished-photo dance, the resumable video protocol, per-code error handling, and the rate-limit bookkeeping, and then you repeat it for the other nine networks. PostZen wraps it in one request. The user connects a Page through a hosted OAuth flow that requests the five Pages permissions and stores the non-expiring Page token; you send:
curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Registration is open for our September workshop.",
"publishNow": true,
"platforms": [
{
"platform": "facebook",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"link": "https://example.com/workshop",
"firstComment": "Seats are limited to 40."
}
}
]
}'
Add mediaItems for photo or video posts (up to 10 images at 30 MB each, or one video up to 1 GB) and PostZen picks the right Graph edge: /feed for text and links, /photos for one image, unpublished photos plus attached_media for several, /videos for video. The firstComment setting posts a comment right after publishing, which needs pages_manage_engagement and is why PostZen requests it. Reels and Stories are not published by PostZen today; the full field reference and error table are in the Facebook platform docs, and the same target sits alongside nine other platforms in a single request, as shown in the guide to posting to all social media at once.
The same Page token unlocks the linked Instagram business account through the Page’s instagram_business_account field, which is why connecting Facebook and Instagram in most tools is one login. The Threads API is the exception in Meta’s family: it has its own host, its own tokens, and its own review.
Facebook Graph API changes in 2025 and 2026
Meta ships a Graph API version roughly every four to five months and guarantees each one for about two years after the next version’s release. The current table:
| Version | Released | Available until |
|---|---|---|
| v26.0 | July 29, 2026 | Not yet announced |
| v25.0 | February 18, 2026 | July 29, 2028 |
| v24.0 | October 8, 2025 | February 18, 2028 |
| v23.0 | May 29, 2025 | October 8, 2027 |
| v22.0 | January 21, 2025 | May 20, 2027 |
Pin a version in your base URL. A call to an expired version is not rejected; it is silently served by the next oldest live version, which is how integrations break without an error. PostZen’s Facebook publisher pins v23.0 and bumps on a schedule.
Changes that matter for a publishing integration:
- v22.0: Page ratings and recommendations endpoints removed (
/{page-id}/ratingsnow returns error 12). - v24.0: Messenger lead forms via API deprecated.
- v25.0: a large Page and post insights deprecation.
page_impressions_unique,page_posts_impressions_*,page_video_views_unique, and the post-level impression metrics are replaced bypage_media_view,page_total_media_view_unique,post_media_view, andpost_total_media_view_unique. This is why PostZen’s Facebook analytics no longer report reach and impressions, and it mirrors the impressions-to-views shift covered in the Instagram insights explainer. - New Pages Experience: Pages on the new experience cannot be written to with a user token; you need a real Page token from
/me/accounts. Any tutorial that posts with a user token is describing classic Pages.
If you are choosing which Meta surface to build first, the comparison of social media APIs puts Facebook next to the other nine in one table. Facebook is the most forgiving of Meta’s three on limits and the least forgiving on review, and the Page token model, once you have it, is the most stable thing in the family.
Frequently asked questions
Can the Facebook Graph API post to a Page?
Yes. A Page access token with the pages_manage_posts, pages_read_engagement, and pages_show_list permissions can publish text and link posts to /{page-id}/feed, photos to /{page-id}/photos, videos to /{page-id}/videos, and Reels to /{page-id}/video_reels. The API cannot post to personal profiles.
Which permissions does the Facebook API need to post to a Page?
Publishing needs pages_manage_posts plus its dependencies pages_read_engagement and pages_show_list, and the person whose token you use must hold the CREATE_CONTENT task on the Page. Posting a comment needs pages_manage_engagement. Webhooks need pages_manage_metadata.
Do Facebook Page access tokens expire?
A Page token obtained from a long-lived user token has no expiry date. It is invalidated when the user changes their password, removes the app, loses their Page role, or when Meta revokes it for security reasons, so check it with /debug_token rather than assuming it lives forever.
Do I need app review to post to my own Facebook Page?
No. Standard Access lets anyone with a role on your app (admin, developer, or tester) use the Pages permissions on the Pages they manage without review. You need Business Verification and App Review for Advanced Access only when your app posts to Pages owned by people who have no role on it.
How far in advance can you schedule a Facebook Page post through the API?
Set published to false and scheduled_publish_time to a Unix timestamp between 10 minutes and 75 days after the request. Facebook publishes it at that time; a scheduled post can be read back through the Page feed before it goes live.
What is the Facebook API rate limit for Pages?
Page-level calls are limited to 4,800 multiplied by the number of engaged users in a rolling 24-hour window, and app-level calls to 200 multiplied by daily active users per hour. The X-App-Usage and X-Business-Use-Case-Usage response headers report how much of each budget you have used; error codes 4, 17, and 368 mean stop and wait.



