Skip to main content
DocsPlatformsAPI ReferenceAI & Integrations

Facebook

Publish posts, photos, videos, Stories, and link previews to Facebook Pages via the PostPeer API.

Overview

Publish text, images, videos, Stories, and link-preview posts to Facebook Pages via the Graph API. PostPeer handles OAuth, the long-lived token exchange, and the per-Page token model that Meta uses.

Pages only. Posting to personal Facebook profiles via the Graph API was deprecated by Meta years ago and is unavailable to most apps. If you need to post to a personal account, you'll have to do it manually in the Facebook UI.

Quick Start

1. Connect Facebook Pages

curl https://api.postpeer.dev/v1/connect/facebook \
  -H "x-access-key: YOUR_API_KEY"

Response:

{
	"url": "https://www.facebook.com/v23.0/dialog/oauth?..."
}

Redirect the user to that URL. On Meta's consent screen, they choose which Pages to grant PostPeer access to.

After Meta authorization:

  • If one usable Page is available, PostPeer connects it automatically.
  • If multiple Pages are available, PostPeer shows a Page-selection screen.
  • PostPeer creates one integration for each Page selected on that screen.

The Meta consent screen controls which Pages PostPeer is allowed to access. The PostPeer selection screen controls which of those Pages are saved as integrations.

Build your own Page-selection screen

By default, PostPeer hosts the Page-selection screen. To keep the entire selection experience in your app, start OAuth with both headless=true and a redirectUri:

curl "https://api.postpeer.dev/v1/connect/facebook?headless=true&redirectUri=https%3A%2F%2Fyourapp.com%2Ffacebook%2Fcallback" \
  -H "x-access-key: YOUR_API_KEY"

If the user has multiple Pages, PostPeer redirects to your redirectUri with these query parameters:

https://yourapp.com/facebook/callback?status=selection_required&platform=facebook&selectionToken=...

Use selectionToken to finish the connection:

  1. Call Get pending Facebook Page choices and display the returned accounts.
  2. Let the user choose one or more Pages. Keep each selected account's id value, such as page:123456789.
  3. Send those IDs as selectedAccountIds to Save selected Facebook Pages.
  4. Use the returned integrations[].id values as accountId when publishing.

The selection token is short-lived and can be submitted only once. If only one usable Page is available, PostPeer connects it automatically and redirects to your redirectUri without status=selection_required.

2. Find your account IDs

A user with three Pages will have three integrations after connecting once. List them:

curl "https://api.postpeer.dev/v1/connect/integrations?platform=facebook" \
  -H "x-access-key: YOUR_API_KEY"

Each entry has:

{
	"id": "abc123",
	"platform": "facebook",
	"platformUserId": "104251038...",
	"displayName": "Acme Co",
	"imageUrl": "https://assets.postpeer.dev/...",
	"createdAt": "..."
}

The id is what you pass as accountId on a post. You'd typically pair this with profiles so you can group all of an end-user's Pages together.

3. Publish a post

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New product drop today.",
    "platforms": [
      { "platform": "facebook", "accountId": "abc123" }
    ],
    "publishNow": true
  }'
import PostPeer from '@postpeer/node';

const client = new PostPeer();
const { data } = await client.posts.create({
	body: {
		content: 'New product drop today.',
		platforms: [{ platform: 'facebook', accountId: 'abc123' }],
		publishNow: true,
	},
});
from postpeer import PostPeer

with PostPeer() as client:
    post = client.posts.create(
        content="New product drop today.",
        platforms=[{"platform": "facebook", "accountId": "abc123"}],
        publish_now=True,
    )

Response:

{
	"success": true,
	"status": "published",
	"platforms": [
		{
			"platform": "facebook",
			"success": true,
			"platformPostId": "104251038_998877665",
			"platformPostUrl": "https://www.facebook.com/104251038/posts/998877665"
		}
	]
}

Features

Text-only posts

Just content. Goes straight to the Page's feed.

Pass a URL via platformSpecificData.link and Facebook auto-fetches that URL's Open Graph metadata to render a preview card.

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Read our latest case study:",
    "platforms": [
      {
        "platform": "facebook",
        "accountId": "abc123",
        "platformSpecificData": {
          "link": "https://yourapp.com/case-studies/acme"
        }
      }
    ],
    "publishNow": true
  }'

Single image

Attach one image mediaItem. The content becomes the photo caption.

{
	"content": "Behind the scenes today",
	"platforms": [{ "platform": "facebook", "accountId": "abc123" }],
	"mediaItems": [
		{ "type": "image", "url": "https://cdn.example.com/photo.jpg" }
	]
}

Attach 2–10 image mediaItems. PostPeer uploads each as unpublished, then creates a single feed post that bundles them all.

{
	"content": "Recap of yesterday's launch event",
	"platforms": [{ "platform": "facebook", "accountId": "abc123" }],
	"mediaItems": [
		{ "type": "image", "url": "https://cdn.example.com/1.jpg" },
		{ "type": "image", "url": "https://cdn.example.com/2.jpg" },
		{ "type": "image", "url": "https://cdn.example.com/3.jpg" }
	]
}

Video

Attach one video mediaItem. PostPeer asks Facebook's servers to fetch the URL directly — works for hosted MP4s up to a few hundred MB. The content becomes the video description.

{
	"content": "Quick walkthrough of the new dashboard",
	"platforms": [{ "platform": "facebook", "accountId": "abc123" }],
	"mediaItems": [
		{ "type": "video", "url": "https://cdn.example.com/walkthrough.mp4" }
	]
}

Custom video thumbnail (cover image)

By default Facebook auto-picks a frame from the video as its cover. To set a custom one, pass a public image URL in the video's thumbnail field. PostPeer downloads it and uploads it to Facebook as the video's thumbnail in a follow-up call after the video is published.

{
	"content": "Quick walkthrough of the new dashboard",
	"platforms": [{ "platform": "facebook", "accountId": "abc123" }],
	"mediaItems": [
		{
			"type": "video",
			"url": "https://cdn.example.com/walkthrough.mp4",
			"thumbnail": "https://cdn.example.com/cover.jpg"
		}
	]
}

For backward compatibility, platformSpecificData.videoThumbnailUrl is still supported as a Facebook-specific override and takes precedence over mediaItems[].thumbnail when both are provided.

Best-effort: if the thumbnail upload fails (broken URL, oversized image, network blip), the video still publishes — Facebook just auto-picks a frame instead. The failure is logged but doesn't fail the post. Ignored for non-video posts.

Reels are not currently supported. For Stories, use contentType: "story" as described below.

Video specifications

PostPeer publishes one video at a time to the Facebook Page Videos endpoint.

SpecificationFacebook recommendation or limit
ContainerMP4 or MOV recommended
Video and audioH.264 video with AAC audio recommended
Frame rate30 FPS or lower recommended
DimensionsMaximum 4,000 px wide; dimensions divisible by 16 recommended
Duration240 minutes maximum
File size4 GB maximum

See Meta's Video API publishing guide, Facebook's recommended video settings, and Facebook's upload limits.

Stories

Set platformSpecificData.contentType to "story" to publish a Facebook Page Story. Omit it to publish a feed post.

{
	"content": "",
	"mediaItems": [
		{ "type": "image", "url": "https://cdn.example.com/story.jpg" }
	],
	"platforms": [
		{
			"platform": "facebook",
			"accountId": "abc123",
			"platformSpecificData": { "contentType": "story" }
		}
	],
	"publishNow": true
}

Use exactly one image or video with a publicly accessible URL. For a video Story, use type: "video" and a direct video URL. Use JPEG or PNG for images and MP4 or MOV for videos; 1080 × 1920 is recommended. Meta validates the uploaded file's size, duration, and encoding.

Stories expire after 24 hours. Captions, link previews, and custom thumbnails are ignored, and interactive stickers are unavailable. published: false is rejected for Stories; you can still save a PostPeer draft or schedule a Story through PostPeer using scheduledFor and timezone.

The response includes the Story's platformPostId. platformPostUrl is included only when Facebook returns a matching Story URL; it may be unavailable immediately after publishing or after expiration.

Draft / unpublished mode

Pass published: false in platformSpecificData to create the post in draft state. Useful for staging or for letting your end-user finalize the post inside Facebook's own composer.

{
	"platformSpecificData": { "published": false }
}

Platform-Specific Data

Pass this object in platformSpecificData when posting to Facebook Pages.

FieldTypeRequiredValues and constraintsDescription
contentTypestringNostorySet to "story" to publish a Facebook Page Story with exactly one image or video. Omit for a feed post. Stories expire after 24 hours; captions, links, and custom thumbnails are ignored. published: false is not supported for Stories.
linkstringNoFormat: uriWhen provided on a text-only post, Facebook renders a link preview card from this URL's Open Graph tags. Ignored when mediaItems are attached.
publishedbooleanNoWhen false, the post is created on the Page in unpublished/draft state — useful for staging or scheduling via Facebook's own composer. Defaults to true.
videoThumbnailUrlstringNoFormat: uriVideo posts only. Optional Facebook-specific override for mediaItems[].thumbnail. Applied as a follow-up call after publish — if the upload fails, the post still succeeds (Facebook just auto-picks a frame). Ignored when no video is attached.

Limits

LimitValue
Text length63,206 characters (Meta's hard cap)
Images per post10
Videos per post1
Mixing video + imagesNot allowed in one post
GIFsNot supported — convert to image or video first

Tokens & reconnection

Page access tokens that PostPeer stores are derived from a long-lived user token, which makes them non-expiring under normal use. You typically only need to reconnect when:

  • The user revokes the app from their Facebook Settings
  • The user changes their Facebook password
  • Facebook flags the account with a security checkpoint
  • The user removes the integration on PostPeer's side and reconnects

When a posting call hits one of these, Facebook returns an authentication error (OAuthException, code 190). PostPeer detects it, marks the integration as needing re-authentication, and stops retrying that post. You can see this on the integration: tokenStatus.reconnectRequired becomes true with a reason. Surface a "reconnect Facebook" prompt and have the user go through /v1/connect/facebook again.

Security checkpoints (code 190, subcode 459). If Facebook returns "You cannot access the app till you log in to www.facebook.com and follow the instructions given", the account is checkpointed. Reconnecting alone will not fix it: the user must first log in to facebook.com, clear the security prompt, and then reconnect the Page.

On this page