Skip to main content
Before you start. Facebook publishing happens against a Page (not a personal profile). The connect flow uses Facebook Login for Business, then discovers Pages via GET /me/accounts — one letmepost row per Page. If the user manages no Pages, the connect fails with facebook.pages.none.

Quick reference

Limit / capabilityValue
Character limit63,206 graphemes
Images per postup to 10 (multi-photo via attached_media)
Image formatsjpeg, png, gif, webp
Image size4,000,000 bytes (4 MB)
Video formatsmp4, mov
Video size4,000,000,000 bytes (4 GB)
Mixed image + videoRejected — one media kind per post
Post types supportedtext, single image, multi-image, single video, link share
SchedulingYes (via scheduledAt)
Reply / thread supportNot supported in v1
First commentNot supported in v1
Inbox / DMNot supported in v1
AnalyticsNot supported in v1

Connect an account

POST /v1/accounts/connect/facebook. Standard Facebook Login for Business OAuth. After complete, the provider calls GET /me/accounts and creates one platform_accounts row per Page the user manages, each carrying its own Page Access Token (the non-expiring token used for publishing, distinct from the User Access Token). If the user manages no Pages: facebook.pages.none. Personal profiles cannot publish via the Graph API and are out of scope for v1.

Scopes

pages_show_list             — list Pages the user manages
pages_manage_posts          — create posts on Pages
pages_read_engagement       — pre-req for posts on some apps
business_management         — Pages connected via Business
extended adds insights / engagement scopes (pages_read_user_content, pages_manage_engagement).

Token lifecycle

The User Access Token from the OAuth handshake is short-lived; the provider swaps it for a long-lived (~60-day) user token, then derives a Page Access Token per Page via /me/accounts. Page Access Tokens are non-expiring as long as the user token stays fresh; refresh runs on schedule. token.expiring fires before expiry on the user token.

Post types

Text post

text.json
{
  "targets": [{ "accountId": "..." }],
  "text": "Page update"
}
Internally this routes to POST /{page-id}/feed with message.

Single image

single-image.json
{
  "targets": [{ "accountId": "..." }],
  "text": "with a photo",
  "media": [{ "kind": "image", "mediaId": "med_…" }]
}
Internally: POST /{page-id}/photos with caption.

Multi-image (up to 10)

multi-image.json
{
  "targets": [{ "accountId": "..." }],
  "text": "three photos",
  "media": [
    { "kind": "image", "mediaId": "med_a…" },
    { "kind": "image", "mediaId": "med_b…" },
    { "kind": "image", "mediaId": "med_c…" }
  ]
}
The publisher stages each photo as published=false via POST /{page-id}/photos, then creates the wall post with POST /{page-id}/feed carrying attached_media: [{ media_fbid }].

Single video

video.json
{
  "targets": [{ "accountId": "..." }],
  "text": "demo clip",
  "media": [{ "kind": "video", "mediaId": "med_…" }]
}
Routes to POST /{page-id}/videos with the media URL. Only one video per post; mixing image + video on the same post is rejected upfront.

Wisdom (platform-specific things that bite)

  • The Page Access Token is what publishes — not the User Access Token. The provider derives it at connect; revoking the user’s session in Facebook also kills the Page token even though the Page token itself has no documented expiry.
  • Multi-photo posts on Pages are a two-step dance: upload N photos unpublished, then attach the resulting media_fbids to one /feed call. The publisher hides this; if any single photo upload fails, the whole post fails (no half-posted state).
  • FB’s hard cap is 63,206 codeunits; we pre-check at 63,206 graphemes, which is strictly tighter — a payload that passes preflight never trips Meta’s server-side check.
  • The response id comes back as {page-numeric-id}_{post-numeric-id}; the publisher derives a clean https://www.facebook.com/{pageId}/posts/{postId} permalink for the response uri.
  • Link previews are auto-rendered by Facebook when a URL appears in message — no separate field is required; passing link is for explicit-share semantics only and is honored only on text-only posts.
  • Page-level rate limits scale with Page size and prior engagement; bursty publish patterns trigger platform_unavailable (mapped from Meta code: 4).

Common errors

Error ruleWhat it meansHow to fix
facebook.text.requiredEmpty text and no mediaProvide text or attach media[].
facebook.text.max_graphemesMessage > 63,206 graphemesTrim under the cap.
facebook.media.image_video_exclusiveAttached both images and a videoSplit into separate posts.
facebook.media.count_maxMore than one video, or more than 10 photosOne video per post; ≤10 photos per multi-image post.
facebook.media.mime_allowedUnsupported image/video mimeImages: jpeg/png/gif/webp. Videos: mp4/mov.
facebook.media.image_size_maxImage > 4 MBRe-encode under 4,000,000 bytes.
facebook.media.video_size_maxVideo > 4 GBCompress under 4,000,000,000 bytes.
facebook.pages.noneConnect-time: user manages no PagesThe user must own/manage a Page in Business Manager before connecting.

What you can’t do (yet)

  • Posting to a personal profile (Meta’s Graph API doesn’t expose write access to user feeds).
  • First comment / threaded replies as the Page.
  • Story posts, Reels (Facebook Reels live behind a separate publishing surface not in v1).
  • Tagging users or co-authors.
  • Targeted post audience (Facebook geo/demographic targeting).
  • Reading comments, reactions, or insights (those scopes are extended and the read APIs aren’t wired in v1).

API reference