> ## Documentation Index
> Fetch the complete documentation index at: https://docs.social-api.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Facebook publishing

> Full reference for publishing Facebook Page posts via SocialAPI, including text, link preview, photo, video, Reel, and Story posts.

## 1. Overview

Facebook publishing uses the Meta Graph API directly against your connected Facebook Page. SocialAPI selects the correct Graph API endpoint based on the content type: text and link posts go to `/{page-id}/feed`, single-photo posts to `/{page-id}/photos`, single-video posts to `/{page-id}/videos`, and multi-photo albums use a two-step upload-then-feed flow. Two richer formats are opted into explicitly with `platform_data.content_type`: Reels (`content_type: "reel"`) publish through the Reels API, and 24-hour Stories (`content_type: "stories"`) publish through the photo or video Stories API. Every format is described below.

| Field                  | Value                                                                       |
| ---------------------- | --------------------------------------------------------------------------- |
| Platform slug          | `facebook`                                                                  |
| Auth type              | OAuth 2.0 (Meta)                                                            |
| API                    | Meta Graph API (Facebook Pages)                                             |
| Create post            | Yes                                                                         |
| Update post            | Yes (message text only)                                                     |
| Delete post            | Yes                                                                         |
| Schedule               | Yes (deferred publish via `scheduled_at`)                                   |
| First comment          | Yes (best-effort)                                                           |
| Carousel (multi-photo) | Yes (2 to 10 photos; images only)                                           |
| Reel                   | Yes (`content_type: "reel"`; one vertical video)                            |
| Story                  | Yes (`content_type: "stories"`; one photo or video, expires after 24 hours) |

***

## 2. Supported media

| Content type      | How to trigger                                                                                         | Notes                                                                                                                                                                                                                                         |
| ----------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Text only         | No `media_ids`; no `link` in `platform_data`                                                           | Publishes a plain text status update                                                                                                                                                                                                          |
| Link preview      | No `media_ids`; include `link` in the target's `platform_data`                                         | Facebook generates an Open Graph preview card                                                                                                                                                                                                 |
| Single photo      | One image entry in `media_ids`                                                                         | Routes to `/{page-id}/photos`; `platform_data` is ignored                                                                                                                                                                                     |
| Single video      | One video entry in `media_ids` (`.mp4`, `.mov`, `.avi`, `.mkv`, `.webm`)                               | Routes to `/{page-id}/videos`; only the `title` platform\_data field is forwarded                                                                                                                                                             |
| Reel              | One vertical video entry in `media_ids` plus `content_type: "reel"` in the target's `platform_data`    | Routes to the Reels API (`/{page-id}/video_reels`). Unlike standard videos, `platform_data` IS honored (`content_type`, `share_to_feed`).                                                                                                     |
| Story             | One photo or video entry in `media_ids` plus `content_type: "stories"` in the target's `platform_data` | Publishes a Page Story that expires after 24 hours. Photos publish via `/{page-id}/photo_stories`; videos run the three-phase `/{page-id}/video_stories` flow. Captions, links, and audience targeting are ignored.                           |
| Multi-photo album | 2 to 10 image entries in `media_ids`                                                                   | Each photo is uploaded with `published=false`, then a single `/{page-id}/feed` call attaches all `media_fbid` values via `attached_media[]`. The target's `platform_data` fields (`link`, CTA, age targeting) are forwarded to the feed call. |

Route selection is mostly automatic, driven by how many `media_ids` you pass:

* `media_ids` is empty → text or link post on `/{page-id}/feed`
* `media_ids` has one entry → photo or video endpoint based on the file extension
* `media_ids` has 2 to 10 entries → multi-photo album

Reels and Stories override that extension-based routing. Set `content_type` in the target's `platform_data` to `"reel"` (one vertical video) or `"stories"` (one photo or video), and that intent takes precedence over the rules above.

**Caption limit:** Follows Facebook's standard post message limit (approximately 63,206 characters, though shorter posts perform better).

**Multi-photo album rules:**

* 2 to 10 photos per post. Passing more than 10 entries returns a validation error.
* Images only. Mixing images and videos in the same album is rejected with a validation error. Facebook's Graph API does not support mixed-media albums; publish separate posts or use a single Reel with image overlays.
* Each photo is uploaded as an unpublished photo, which Facebook retains for approximately 24 hours. The follow-up feed call happens immediately, so this expiry only matters if the feed call fails. In that case the uploaded photos are abandoned and you can safely retry the post.
* For best rendering, use consistent 1,080 by 1,080 px (1:1) images.

***

## 3. Create post

Use `POST /v1/posts` with `targets` targeting a Facebook Page account. The `text` field becomes the post message.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Exciting news from our team. Read the full announcement on our website.",
    "targets": [{
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform_data": {
        "link": "https://example.com/announcement"
      }
    }]
  }'
```

### Platform data fields

Pass these inside the target's `platform_data` object (keys are read directly, not nested under a `facebook` key). These fields apply to text, link posts, and multi-photo albums. Single-photo posts honor `alt_text`; single-video posts honor `title`. Other `platform_data` keys are silently ignored for those post types.

| Field      | Type   | Description                                                                                                    |
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| `link`     | string | URL to attach as a link preview card. Facebook generates the Open Graph preview automatically.                 |
| `cta_type` | string | Call-to-action button type (for example, `SHOP_NOW`, `LEARN_MORE`). Must be provided together with `cta_link`. |
| `cta_link` | string | Destination URL for the call-to-action button. Must be provided together with `cta_type`.                      |
| `min_age`  | number | Minimum age for audience targeting (13 to 65).                                                                 |
| `max_age`  | number | Maximum age for audience targeting (13 to 65). Must be greater than or equal to `min_age`.                     |

Only the documented fields above are forwarded to the Graph API; any additional keys in `platform_data` are silently ignored.

### Photo post

Provide a single image entry in `media_ids`. The `alt_text` platform\_data field is forwarded as `alt_text_custom` on the photo; all other `platform_data` keys are ignored for single-photo posts.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
    "text": "Our new product is here.",
    "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"]
  }'
```

### Multi-photo album

Provide 2 to 10 image entries in `media_ids` to publish a multi-photo album. SocialAPI handles the two-step Graph API flow internally: each photo is uploaded with `published=false` to obtain a `media_fbid`, then a single `/{page-id}/feed` call attaches all photo IDs via `attached_media[]` and sets the caption.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
    "text": "Highlights from this week.",
    "media_ids": [
      "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "8d5e9b2c-1a3f-4c6d-9e8b-2f7a4c1d6e0b",
      "3b1f7a90-6c2d-4e85-b9a1-0d4f8c3e2a17"
    ]
  }'
```

Unlike single-photo posts, the target's `platform_data` IS honored for albums. The `link`, `cta_type` plus `cta_link`, and age targeting fields are forwarded to the feed call:

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Shop the spring collection.",
    "media_ids": [
      "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "8d5e9b2c-1a3f-4c6d-9e8b-2f7a4c1d6e0b",
      "3b1f7a90-6c2d-4e85-b9a1-0d4f8c3e2a17",
      "5e2c8a14-7b9d-4f03-a6c1-9d8e0b3f4a26"
    ],
    "targets": [{
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform_data": {
        "cta_type": "SHOP_NOW",
        "cta_link": "https://example.com/spring"
      }
    }]
  }'
```

**Limits:**

* Minimum 2, maximum 10 photos per album.
* Images only. Including a video entry (any of `.mp4`, `.mov`, `.avi`, `.mkv`, `.webm`) in a 2+ item `media_ids` returns a validation error before any upload to Facebook.

### Video post

Provide a single video entry in `media_ids`. SocialAPI detects the video extension and routes to `/{page-id}/videos`. The `title` platform\_data field is forwarded as the video title; all other `platform_data` keys are ignored for single-video posts.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
    "text": "Watch our latest brand film.",
    "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"]
  }'
```

### Reel

Publish a short-form vertical video as a Page Reel by setting `content_type: "reel"` in the target's `platform_data`. Provide exactly one vertical video entry in `media_ids`. SocialAPI runs Facebook's Reels publishing flow (upload, processing, publish). The request blocks until the Reel is ready or published, polling for up to about 10 minutes (120 status checks at 5-second intervals). If the Reel does not finish processing within that window, the publish fails with `504 Gateway Timeout`.

The `platform_data` object lives on the target (inside the `targets` array), and its keys are read directly (do not nest them under a `facebook` key).

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Behind the scenes #reels",
    "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
    "targets": [{
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform_data": {
        "content_type": "reel",
        "share_to_feed": true
      }
    }]
  }'
```

Reel fields (inside the target's `platform_data`):

| Field           | Type    | Default  | Description                                                                                                  |
| --------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| `content_type`  | string  | `"feed"` | Set to `"reel"` to publish a Reel. Any other value (or omitting it) publishes a standard post or feed video. |
| `share_to_feed` | boolean | `true`   | When `true`, the Reel also appears in the Page feed, not just the Reels tab.                                 |

Requirements: exactly one video, vertical orientation (9:16), and a duration of 3 to 90 seconds. SocialAPI warns when the duration is outside that range; Facebook enforces the aspect ratio at publish time and rejects videos that do not comply.

### Story

Publish a 24-hour Page Story by setting `content_type: "stories"` in the target's `platform_data`. Provide exactly one photo or video entry in `media_ids`, and SocialAPI picks the right flow from the media type: a photo is uploaded as an unpublished photo and then published to `/{page-id}/photo_stories`, while a video runs Facebook's three-phase `/{page-id}/video_stories` flow (upload, processing, publish). For a video, the request blocks until processing finishes, polling for up to about 10 minutes (120 status checks at 5-second intervals); if it does not finish in that window, the publish fails with `504 Gateway Timeout`.

Stories carry no caption, link, call-to-action, or audience targeting, so any `text` or `platform_data` field other than `content_type` is ignored.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
    "targets": [{
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform_data": {
        "content_type": "stories"
      }
    }]
  }'
```

Story fields (inside the target's `platform_data`):

| Field          | Type   | Default  | Description                            |
| -------------- | ------ | -------- | -------------------------------------- |
| `content_type` | string | `"feed"` | Set to `"stories"` to publish a Story. |

Requirements and limits:

* Exactly one media item. Passing zero or more than one entry returns a validation error before any upload to Facebook.
* Photos: JPEG, BMP, PNG, GIF, or TIFF, up to 10 MB (keep PNGs under 1 MB).
* Videos: 9:16 vertical, 1080 by 1920 recommended (minimum 540 by 960), 3 to 90 seconds, 24 to 60 fps.
* The photo or video must not have been used in a previously published post. Facebook rejects a Story whose media already appeared elsewhere, so upload a fresh asset for each Story.
* Stories are a Pages-only format and expire after 24 hours (see [Stories expire](#stories-expire)).

### First comment

Set `first_comment` to a string to post an automated comment immediately after publish. This is best-effort: if the comment fails (for example due to a temporary rate limit), the post is still considered published.

```json theme={null}
{
  "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
  "text": "Exciting news from our team.",
  "first_comment": "Read the full announcement: https://example.com/blog"
}
```

### Scheduling

Set `scheduled_at` to an ISO 8601 timestamp (UTC) to defer publishing:

```json theme={null}
{
  "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }],
  "text": "Scheduled announcement.",
  "scheduled_at": "2026-04-15T10:00:00Z"
}
```

***

## 4. Update post

Facebook supports editing the message text of a post that was created by the same app. Media cannot be changed after publish. Use `PATCH /v1/posts/:pid` with a `text` field:

```bash theme={null}
curl -X PATCH https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Updated message text."
  }'
```

SocialAPI calls `POST /{page-id}_{post-id}` with the new `message` value. Only the `text` field is forwarded; all other patch fields are ignored for Facebook targets.

**Limitation:** Only posts created by your app can be updated. Posts imported from the platform (via sync) may return a `400` from Facebook if the app does not own them.

***

## 5. Delete post

```bash theme={null}
curl -X DELETE https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Or, to delete only the Facebook target of a cross-platform post:

```bash theme={null}
curl -X DELETE "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J?platform=facebook" \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Deletion calls `DELETE /{page-id}_{post-id}` on the Graph API. If the post no longer exists on Facebook, the call returns success.

***

## 6. Retrieve posts

Use `GET /v1/posts` to list posts. Filter by platform or account:

```bash theme={null}
curl "https://api.social-api.ai/v1/posts?platform=facebook&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Each post includes a `targets` array with per-platform status and engagement metrics:

```json theme={null}
{
  "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J",
  "text": "Exciting news from our team.",
  "status": "published",
  "targets": [
    {
      "platform": "facebook",
      "platform_post_id": "123456789012345_987654321098765",
      "status": "published",
      "permalink": "https://www.facebook.com/permalink/123456789012345",
      "metrics": {
        "likes": 15,
        "comments": 3,
        "shares": 7,
        "saves": 0,
        "extra": null,
        "metrics_synced_at": "2026-04-10T12:00:00Z"
      }
    }
  ]
}
```

**Metrics notes:**

* `likes` and `comments` are synced periodically from the Graph API.
* `shares` is populated for Facebook posts. Facebook is the only platform where SocialAPI reports a non-zero share count.
* `saves` is not exposed by the Facebook Pages API and is always `0`.
* `metrics_synced_at` reflects when SocialAPI last refreshed the metrics from Facebook.

***

## 7. Quirks, errors, and recovery

### Page token lifetime

Facebook Page tokens derived from long-lived user tokens do not expire. Once an account is connected, the token remains valid indefinitely unless the user revokes app access or changes their password. Reconnection is only needed if you see an `auth` error.

### Multi-page OAuth

When a user completes the Facebook OAuth flow, SocialAPI connects all Facebook Pages that the user manages in a single OAuth exchange. Each page becomes a separate connected account entry. Users do not need to repeat the OAuth flow for additional pages.

### platform\_data forwarding rules

The target's `platform_data` block is forwarded to the Graph API for text/link posts and for multi-photo albums (both go through the `/{page-id}/feed` endpoint). For single-photo and single-video posts, most `platform_data` keys are ignored: `alt_text` is honored for single-photo posts and `title` is honored for single-video posts. For Reels, `content_type` and `share_to_feed` apply; for Stories, only `content_type` applies (Stories take no caption, link, or targeting). All other keys are silently dropped for those post types.

### Multi-photo album semantics

Multi-photo albums (2 to 10 image entries in `media_ids`) use a two-step flow: each photo is uploaded with `published=false`, then attached to a single feed post. The unpublished photos Facebook stores during step one expire after about 24 hours, so if the second step fails the photos are simply abandoned and you can retry the entire post. Mixing images and videos in the same album returns a validation error before any upload happens, and passing more than 10 entries also fails validation.

### First comment is non-blocking

If `first_comment` is set and the comment fails after publish, the post remains `published`. The comment failure is logged but does not roll back or change the post status.

### Stories expire

Facebook Page Stories are visible for 24 hours and then disappear from the Page. The post record in SocialAPI is not removed when the story expires on Facebook, so a `GET /v1/posts` result may still reference a story that is no longer live. Stories are also single-use per asset: the photo or video must not have appeared in a previously published post, or Facebook rejects the publish. Upload a fresh media item for each Story.

### Reviews deprecated

The Facebook Graph API v22.0 removed the reviews edge. `GET /v1/inbox/reviews` for a Facebook account returns `501 Not Implemented`. This is a platform-level deprecation and cannot be worked around.

### DM 24-hour window

Facebook Messenger only allows pages to send outbound DMs within a 24-hour window after the user last messaged the page. Attempting to send outside this window returns a platform error. SocialAPI surfaces this as an `api_error`.

### Error shapes

When a publish fails, the post target's `error` field contains a structured error:

```json theme={null}
{
  "code": "platform.facebook.api_error",
  "message": "Invalid OAuth access token.",
  "category": "platform",
  "caused_by": "platform"
}
```

| Error code                     | Category     | Caused by  | Meaning                                                          |
| ------------------------------ | ------------ | ---------- | ---------------------------------------------------------------- |
| `platform.facebook.api_error`  | `platform`   | `platform` | The Graph API rejected the request. Check `message` for details. |
| `platform.facebook.rate_limit` | `rate_limit` | `platform` | Facebook rate limit hit. Retry after a delay.                    |
| `platform.facebook.auth`       | `auth`       | `platform` | Access token invalid or revoked. Reconnect the account.          |

### Recovery

* **Rate limit:** Retry the post via `POST /v1/posts/:pid/retry` after waiting. SocialAPI applies exponential backoff for scheduled retries.
* **Auth error:** Disconnect and reconnect the Facebook account through the OAuth flow to obtain a fresh token.
* **API error:** Check the `message` field for the Graph API error description. Common causes include missing page permissions, a post that no longer exists, or an invalid media URL.

***

## 8. Full worked example

The following example publishes a link preview post, then checks the metrics after publish.

**Step 1: Publish the post**

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/posts \
  -H "Authorization: Bearer $SOCAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "We just launched our spring collection. Shop now and get 20% off your first order.",
    "targets": [{
      "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J",
      "platform_data": {
        "link": "https://example.com/spring-collection"
      }
    }]
  }'
```

Response:

```json theme={null}
{
  "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J",
  "text": "We just launched our spring collection. Shop now and get 20% off your first order.",
  "status": "publishing",
  "targets": [
    {
      "platform": "facebook",
      "status": "publishing"
    }
  ],
  "created_at": "2026-04-10T09:00:00Z"
}
```

**Step 2: Check status**

```bash theme={null}
curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J" \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

Response once published:

```json theme={null}
{
  "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J",
  "text": "We just launched our spring collection. Shop now and get 20% off your first order.",
  "status": "published",
  "targets": [
    {
      "platform": "facebook",
      "platform_post_id": "123456789012345_987654321098765",
      "status": "published",
      "permalink": "https://www.facebook.com/permalink/123456789012345",
      "metrics": {
        "likes": 15,
        "comments": 3,
        "shares": 7,
        "saves": 0,
        "extra": null,
        "metrics_synced_at": "2026-04-10T12:00:00Z"
      }
    }
  ],
  "created_at": "2026-04-10T09:00:00Z",
  "published_at": "2026-04-10T09:00:12Z"
}
```

**Step 3: Refresh metrics on demand**

```bash theme={null}
curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J/metrics" \
  -H "Authorization: Bearer $SOCAPI_KEY"
```

***

## 9. Required OAuth scopes

SocialAPI's managed Meta App requests the following scopes on your behalf during the OAuth flow. This is informational, you do not need to configure anything.

| Scope                     | Purpose                                           |
| ------------------------- | ------------------------------------------------- |
| `pages_show_list`         | List the pages the user manages                   |
| `pages_read_engagement`   | Read likes, comments, and shares on page posts    |
| `pages_read_user_content` | Read posts and comments made by users on the page |
| `pages_manage_engagement` | Reply to and delete comments                      |
| `pages_manage_metadata`   | Access page metadata and settings                 |
| `pages_messaging`         | Send and receive Messenger DMs                    |
| `pages_manage_posts`      | Create, update, and delete page posts             |
| `public_profile`          | Access the user's basic public profile            |

To verify which scopes your connected account has granted, call `GET /v1/accounts/:id/limits`.
