Platforms

Facebook

Connect a Facebook Page, publish text, photo, album, video, Reel and Story posts, manage comments and Messenger conversations, and read post and Page analytics.

Publish to a Page, answer comments and Messenger, and read Page analytics.

Quick Reference

PropertyValue
Account typesFacebook Pages
Connection methodOAuth, one account per Page
Post typestext, image, multi_image, video, reel, stories
Max media per post10
Text limit63,206 characters, title 100 (Reels)
ThreadsNo
QuotesNo
Remote delete
Comments
Likes
RepostsNo
BookmarksNo
Direct messagesMessenger
Post analytics
Account analyticsStats and insights

Each account is one Page. Profiles and Groups cannot connect. Posts made on the Page show up on their own, usually within a minute. Older ones need a sync.

Connect

Connect a Page in Dashboard → Connections, or with a connect session.

Each Page is its own account. Your Facebook role on the Page sets what works.

FeaturePage access needed
PublishingContent
InboxMessages
AnalyticsInsights

Facebook shows a Page picker during connect. To show your own picker instead, add headless: true. See Pick a Page for the steps.

Permissions

ScopeUsed for
pages_show_list, business_managementListing your Pages
pages_manage_postsPublishing and deleting
pages_read_engagementReading posts and counts
pages_read_user_contentComments and recommendations
pages_manage_engagementCommenting, hiding and liking
read_insightsAnalytics
pages_messagingMessenger and private replies
pages_manage_metadataWebhooks for comments, messages and posts
public_profileFacebook sign-in

Staying Connected

Access renews itself, so canAutoRenew is true.

Access ends on a password change, an app removal or a lost Page role. Then reconnect. The id stays the same. Do the same on a *_PERMISSION code.

Publish

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Our spring menu is live.",
    "accounts": [
      {
        "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
        "facebook": {
          "link": "https://example.com/spring-menu"
        }
      }
    ]
  },
});
console.log(response);
{
  "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
  "data": [
    {
      "id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
      "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
      "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
      "platform": "facebook",
      "source": "onepostly",
      "status": "publishing",
      "text": "Our spring menu is live.",
      "mediaKind": "text",
      "media": [],
      "thread": [],
      "scheduledFor": null,
      "timezone": null,
      "options": {
        "facebook": {
          "link": "https://example.com/spring-menu"
        }
      },
      "platformPostId": null,
      "url": null,
      "error": null,
      "publishedAt": null,
      "metrics": null,
      "createdAt": "2027-03-15T09:00:00.000Z",
      "updatedAt": "2027-03-15T09:00:00.000Z"
    }
  ]
}

Once published, url links to the post. Stories have no url.

Post Types

mediaKindPublishesMedia
textFeed postNone, text or link required
imagePhoto post1 image
multi_imageAlbum or link carousel2 to 10 images
videoFeed video1 video
reelReel1 vertical video
storiesStory, for 24 hours1 image or video
  • One video without mediaKind is a feed video. Set reel or stories.
  • Text goes up to 63,206 characters. Stories show no text.
  • An album with a video fails with INVALID_MEDIA.

A feed video is published once Facebook accepts it. Its platformPostId and url later switch to the post, with a post.updated.

Reel

facebook.reelTitle adds a title, apart from the caption.

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Behind the counter on a Saturday morning.",
    "mediaKind": "reel",
    "media": [
      {
        "url": "https://cdn.onepostly.com/media/saturday.mp4"
      }
    ],
    "accounts": [
      {
        "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
        "facebook": {
          "reelTitle": "Saturday rush"
        }
      }
    ]
  },
});
console.log(response);
{
  "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
  "data": [
    {
      "id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
      "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
      "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
      "platform": "facebook",
      "source": "onepostly",
      "status": "publishing",
      "text": "Behind the counter on a Saturday morning.",
      "mediaKind": "reel",
      "media": [
        {
          "url": "https://cdn.onepostly.com/media/saturday.mp4"
        }
      ],
      "thread": [],
      "scheduledFor": null,
      "timezone": null,
      "options": {
        "facebook": {
          "reelTitle": "Saturday rush"
        }
      },
      "platformPostId": null,
      "url": null,
      "error": null,
      "publishedAt": null,
      "metrics": null,
      "createdAt": "2027-03-15T09:00:00.000Z",
      "updatedAt": "2027-03-15T09:00:00.000Z"
    }
  ]
}

A Story takes no options, not even firstComment or geoRestriction. Sending either one on a Story returns 400.

Options

Options go under facebook. On the wrong media kind, they return 400. The exceptions are carouselCards, carouselLink and textFormatPresetId, which are checked when the post publishes.

Prop

Type

If Facebook refuses firstComment, the post still publishes.

carouselCards gives each image of a multi_image post its own link.

Send one card per image, in media order. Each card's name and description can be up to 255 characters long.

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "New arrivals this week.",
    "mediaKind": "multi_image",
    "media": [
      {
        "url": "https://cdn.onepostly.com/media/chair.jpg"
      },
      {
        "url": "https://cdn.onepostly.com/media/lamp.jpg"
      }
    ],
    "accounts": [
      {
        "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
        "facebook": {
          "carouselLink": "https://example.com/new",
          "carouselCards": [
            {
              "link": "https://example.com/new/chair",
              "name": "Oak chair",
              "description": "Solid oak, hand finished"
            },
            {
              "link": "https://example.com/new/lamp",
              "name": "Brass lamp"
            }
          ]
        }
      }
    ]
  },
});
console.log(response);
{
  "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
  "data": [
    {
      "id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
      "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
      "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
      "platform": "facebook",
      "source": "onepostly",
      "status": "publishing",
      "text": "New arrivals this week.",
      "mediaKind": "multi_image",
      "media": [
        {
          "url": "https://cdn.onepostly.com/media/chair.jpg"
        },
        {
          "url": "https://cdn.onepostly.com/media/lamp.jpg"
        }
      ],
      "thread": [],
      "scheduledFor": null,
      "timezone": null,
      "options": {
        "facebook": {
          "carouselLink": "https://example.com/new",
          "carouselCards": [
            {
              "link": "https://example.com/new/chair",
              "name": "Oak chair",
              "description": "Solid oak, hand finished"
            },
            {
              "link": "https://example.com/new/lamp",
              "name": "Brass lamp"
            }
          ]
        }
      },
      "platformPostId": null,
      "url": null,
      "error": null,
      "publishedAt": null,
      "metrics": null,
      "createdAt": "2027-03-15T09:00:00.000Z",
      "updatedAt": "2027-03-15T09:00:00.000Z"
    }
  ]
}

A wrong media kind or card count fails with INVALID_CAROUSEL. Without cards, carouselLink is ignored and the post is a plain album.

Text Background

textFormatPresetId shows a text post as large text on a color. On other kinds, the post fails with TEXT_FORMAT_UNSUPPORTED.

Geo Restriction

geoRestriction shows the post only in the listed countries. It works on text and multi_image. Other kinds return 400.

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Free shipping this week in the US and Canada.",
    "mediaKind": "multi_image",
    "media": [
      {
        "url": "https://cdn.onepostly.com/media/ship-1.jpg"
      },
      {
        "url": "https://cdn.onepostly.com/media/ship-2.jpg"
      }
    ],
    "accounts": [
      {
        "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
        "facebook": {
          "geoRestriction": [
            "US",
            "CA"
          ]
        }
      }
    ]
  },
});
console.log(response);
{
  "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
  "data": [
    {
      "id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
      "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
      "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
      "platform": "facebook",
      "source": "onepostly",
      "status": "publishing",
      "text": "Free shipping this week in the US and Canada.",
      "mediaKind": "multi_image",
      "media": [
        {
          "url": "https://cdn.onepostly.com/media/ship-1.jpg"
        },
        {
          "url": "https://cdn.onepostly.com/media/ship-2.jpg"
        }
      ],
      "thread": [],
      "scheduledFor": null,
      "timezone": null,
      "options": {
        "facebook": {
          "geoRestriction": [
            "US",
            "CA"
          ]
        }
      },
      "platformPostId": null,
      "url": null,
      "error": null,
      "publishedAt": null,
      "metrics": null,
      "createdAt": "2027-03-15T09:00:00.000Z",
      "updatedAt": "2027-03-15T09:00:00.000Z"
    }
  ]
}

Media Requirements

These are Facebook's limits, on top of the Media URL Requirements.

MediaFormatsMax size
ImagesJPEG, PNG and GIF4 MB
VideosMP4, MOV, AVI and WebM1 GB
ReelsMP4, MOV, AVI and WebM4 GB
Story videosMP4, MOV, AVI and WebM100 MB

A file outside these formats and sizes is refused. See Platform Limits.

Images

PropertyRequirement
Per post1, or 2 to 10 on multi_image

Videos

PropertyFeed videoReelStory
DurationFacebook's limit3 to 90 s3 to 60 s
Aspect ratioAny9:169:16
ResolutionAny1080 × 1920, min 540 × 960Same
Frame rateAny24 to 60 fps24 to 60 fps

A URL ending in .mp4, .mov, .m4v, .webm or .avi is a video.

Scheduling, Editing and Deleting

Scheduling and editing work as usual. Published posts cannot be edited.

Deleting removes a post from the Page, including one made on Facebook. A post already gone counts as deleted.

A post deleted on the Page itself turns deleted and sends post.deleted, usually within a minute.

Engagement

Act as the Page. See Comments and Likes and Reposts.

ActionSupportedNotes
Like or unlike a postRepeats succeed
List commentsUp to 25 replies each. Not on Stories
Comment or replyUp to 8,000 characters
Delete a commentWhen Facebook allows it
Hide or unhideThe author and friends still see it
Like a commentRepeats succeed
Private replyOne per comment, 7 days, 2,000 characters
Repost, quote, bookmarkNoUNSUPPORTED_PLATFORM

Like a post by its pst_ id or Facebook id. Another Page's pst_ id fails.

username on a comment is the commenter's name, or null. A comment removed on Facebook sends a comment.deleted webhook.

Inbox

The Page's Messenger inbox. See Inbox for the requests.

ActionSupportedNotes
List and readmuted is always false
Start a conversationBy Page-scoped id (PSID)
Send textUp to 2,000 characters, within 24 hours
Rich messagesAttachments, quick replies, buttons, cards
Ice breakers and menuSee Messenger Setup
AutomationsUp to 20 rules per Page
Message tagsNoNo sends after 24 hours

Facebook has no username lookup. Find a PSID in members or a senderId.

import { Configuration, InboxApi } from "@onepostly/sdk";

const inboxApi = new InboxApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await inboxApi.createConversation({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  createConversationBody: {
    "recipient": "8261937452018734"
  },
});
console.log(response);
{
  "id": "cnv_9Xc4Vb7Nm2Lk5Jh8Gf1Ds6Az3Qw0Er9Ty4Ui7Op2As5Df8Gh1Jk6Lz3Xc0Vb9Nm4Qw7Er2Ty5Ui8Op1As6Df3Gh0Jk",
  "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  "members": [
    {
      "id": "104987654321098",
      "username": null,
      "displayName": null
    },
    {
      "id": "8261937452018734",
      "username": null,
      "displayName": null
    }
  ],
  "muted": false,
  "unreadCount": 0,
  "lastMessage": null
}

Until a new conversation has its first message, you get CONVO_NOT_FOUND.

With attachmentUrl or cards, no text shows. With buttons, text stops at 640. Inbox webhooks include message.delivered, but not unsent messages.

Messenger Setup

A Page can show up to 4 ice breakers and a menu of up to 3 items. See Messenger Profile for the full request and its fields.

import { Configuration, AccountsApi } from "@onepostly/sdk";

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.setMessengerProfile({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  setMessengerProfileBody: {
    "iceBreakers": [
      {
        "question": "What are your opening hours?",
        "payload": "HOURS"
      },
      {
        "question": "Do you deliver?",
        "payload": "DELIVERY"
      }
    ],
    "persistentMenu": [
      {
        "type": "web_url",
        "title": "Order online",
        "url": "https://example.com/order"
      }
    ]
  },
});
console.log(response);
{
  "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  "iceBreakers": [
    {
      "question": "What are your opening hours?",
      "payload": "HOURS"
    },
    {
      "question": "Do you deliver?",
      "payload": "DELIVERY"
    }
  ],
  "persistentMenu": [
    {
      "type": "web_url",
      "title": "Order online",
      "url": "https://example.com/order",
      "payload": null
    }
  ]
}

A tap on an ice breaker or postback item sends message.postback.

Analytics

Metrics

FieldFacebook metricNotes
impressionspost_media_view
reachpost_total_media_view_unique
viewspost_video_viewsOnly video and stories
likesLikesElse total reactions
commentsComments
sharesShares
savesNoneAlways null
engagementLikes + comments + sharesA count

Read Post Analytics

import { Configuration, AnalyticsApi } from "@onepostly/sdk";

const analyticsApi = new AnalyticsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await analyticsApi.getPostAnalytics({
  id: "pst_YOUR_FACEBOOK_POST",
});
console.log(response);
{
  "postId": "pst_YOUR_FACEBOOK_POST",
  "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  "platform": "facebook",
  "status": "ready",
  "metrics": {
    "impressions": 1234,
    "reach": 987,
    "likes": 123,
    "comments": 12,
    "shares": 4,
    "saves": null,
    "views": null,
    "engagement": 139
  },
  "fetchedAt": "2027-03-15T10:00:00.000Z",
  "error": null
}

A fresh post often reads pending. A deleted one reads unavailable, for good. Stories update about hourly while live, then keep their last values.

Account Stats

Get Account Stats returns live Page numbers. followerCount falls back to likes. followingCount and videoCount are null.

Page Reviews

List Page Reviews lists recommendations, newest first.

import { Configuration, AccountsApi } from "@onepostly/sdk";

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.listAccountReviews({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  limit: 20,
});
console.log(response);
{
  "data": [
    {
      "id": "1093847561029384",
      "recommendation": "positive",
      "rating": null,
      "text": "Friendly staff and the best sourdough in the neighborhood.",
      "reviewerName": "Jane Doe",
      "createdAt": "2027-03-14T16:20:00.000Z"
    },
    {
      "id": "1093847561027710",
      "recommendation": "negative",
      "rating": null,
      "text": "Waited 20 minutes for a coffee on Saturday.",
      "reviewerName": null,
      "createdAt": "2027-03-09T11:05:00.000Z"
    },
    {
      "id": "1093847561015526",
      "recommendation": null,
      "rating": 5,
      "text": null,
      "reviewerName": "Sam Lee",
      "createdAt": "2018-06-02T08:45:00.000Z"
    }
  ],
  "nextCursor": "QVFIUjRtNWdLbGd6UXBVZAk1jY0FfTnJ3dkx2S0RxZAXhOQmZAPNnV5dE1CSmVvUHg",
  "hasMore": true
}

recommendation is positive or negative, and rating is the legacy 1 to 5 stars. Any field can be null.

A new review sends a review.received webhook.

Account Insights

Get Account Analytics reads Page Insights live.

import { Configuration, AnalyticsApi } from "@onepostly/sdk";

const analyticsApi = new AnalyticsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await analyticsApi.getAccountAnalytics({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  metrics: "page_media_view",
  period: "day",
});
console.log(response);
{
  "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  "platform": "facebook",
  "metrics": [
    {
      "name": "page_media_view",
      "period": "day",
      "values": [
        {
          "value": 3412,
          "endTime": "2027-03-14T07:00:00.000Z"
        },
        {
          "value": 2987,
          "endTime": "2027-03-15T07:00:00.000Z"
        }
      ]
    }
  ],
  "demographics": {}
}
ParameterFacebook values
metricsUp to 20 Page Insights metric names
periodday (default), week, days_28, lifetime
since, untilA time window
  • The defaults are page_media_view, page_total_media_view_unique, page_post_engagements, page_follows and page_daily_follows_unique.
  • demographics is always empty. Instagram-only parameters are ignored.
  • A retired metric fails the read with 400. The message names it.

Recent Posts

List Account Media lists recent posts. Its metrics are null.

Limitations

Not available for Facebook through Onepostly.

  • Profiles and Groups
  • Editing published posts, sharing and reposting
  • Live and Events
  • Story captions, links, stickers and comments
  • Message tags after 24 hours
  • Saves and demographics

Errors

Breaking a rule on this page returns 400 VALIDATION_ERROR and creates nothing. Every code is listed in Errors, with its cause and fix.

Publish Errors

CodeCauseFix
TEXT_REQUIREDA text post with no text or linkAdd one
INVALID_MULTI_IMAGEOver 10 imagesSend 2 to 10
INVALID_MEDIAA video in an album, or a non-video ReelSee Media Requirements
INVALID_CAROUSELCards without multi_image, or a wrong countOne card per image
TEXT_FORMAT_UNSUPPORTEDA text background on a media postUse it on text posts
REEL_UPLOAD_FAILED, STORY_UPLOAD_FAILEDFacebook did not open the uploadPost again
PERMISSION_DENIEDNo publish permission or Page roleReconnect, or get content access
DELETE_PERMISSIONFacebook refused the deleteReconnect with every permission

Engagement and Inbox Errors

CodeCauseFix
POST_ACCOUNT_MISMATCHAnother Page's pst_ idUse the Facebook id
UNSUPPORTED_MEDIAComments on a StoryStories have none
COMMENTS_PERMISSIONThe Page may not act on the commentReconnect with every permission
PRIVATE_REPLY_FAILEDA second reply, or after 7 daysOne per comment, within 7 days
RECIPIENT_NOT_FOUNDNot a valid PSIDUse a numeric PSID
MESSAGE_WINDOW_EXPIREDOver 24 hours since their messageWait for them to write
INBOX_PERMISSIONNo messaging permission or roleReconnect, or get message access
PROFILE_FAILEDIce breakers or menu did not saveRead the message

Analytics Errors

CodeCauseFix
ANALYTICS_PENDINGNo metrics yetRead again later
POST_NOT_FOUNDDeleted on FacebookNone, this is final
ANALYTICS_PERMISSIONNo insights permission or roleReconnect, or get insights access
REVIEWS_FAILEDReviews did not loadRead again later