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

# Facebook

> Schedule Facebook posts — Feed, Photo, Photo Carousel, Video, and Reels. Page accounts only.

## Quick reference

| Field               | Value                                     |
| ------------------- | ----------------------------------------- |
| Caption limit       | 63,206 characters                         |
| Photos per post     | 1–10                                      |
| Videos per post     | 1                                         |
| Mixed media         | ❌ Not on organic Page posts               |
| Image formats       | JPG, PNG, GIF, WebP, HEIC, HEIF           |
| Video formats       | MP4, MOV, AVI, WebM                       |
| Max image size      | 5 GB                                      |
| Max video size      | 5 GB                                      |
| Reel duration       | 3–90 seconds                              |
| Feed video duration | up to 240 minutes                         |
| Reels per Page      | 30 per 24 hours                           |
| Post types          | Feed, Photo, Photo Carousel, Video, Reel  |
| First comment       | ✅ Auto-posts after publish (not on Reels) |
| Account types       | Facebook Pages (not personal profiles)    |

<Note>
  The image/video size caps above are Postbreeze's server-side ingest
  limits. Facebook's own publish-time caps are tighter (Reels must be
  9:16 and 3–90s, max 30 Reels per Page per 24h, etc.) — exceed those
  and the publish step returns an error from Meta even though the
  upload succeeded.
</Note>

## Before you start

<Warning>
  You can only publish to Facebook **Pages** you administer. Personal-profile publishing isn't exposed by the Graph API. The OAuth flow enumerates the Pages on your account during connect — pick the Page you want to publish to from that list.
</Warning>

Required scopes:

* `pages_show_list` — read the list of Pages you administer.
* `pages_manage_posts` — create posts on your Pages.
* `pages_manage_engagement` + `pages_read_engagement` — first-comment + inbox replies.
* `pages_read_user_content` — inbox comments-on-your-posts feed.

Reels require **Meta App Review** with the `Reels` use-case enabled. Before review approval, video posts work fine but `kind: "REEL"` will return `FB_REEL_NOT_APPROVED`.

## Quick start

Workspace is inferred from your API key — no `workspaceId` argument
and no workspace in the URL. Use the **flat shape** (`content` +
`platforms`) for the common case; drop to the
[nested shape](#full-control-nested-shape) at the bottom of this page
only when a power-user workflow demands it.

<CodeGroup>
  ```js Node.js theme={null}
  import Postbreeze from "@postbreeze/node";

  const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

  const post = await postbreeze.posts.create({
    content: "Our new feature drop — read more 👇",
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    platforms: [{
      accountId: "soc_facebook_…",
      platformOptions: {
        platform: "FACEBOOK_PAGE",
        kind: "FEED",
        link: "https://example.com/blog/new-feature",
      },
    }],
  });
  ```

  ```python Python theme={null}
  import os
  from datetime import datetime, timedelta, timezone
  import requests

  API = "https://api.postbreeze.ai/api/v1"

  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "Our new feature drop — read more 👇",
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "platforms": [{
              "accountId": "soc_facebook_…",
              "platformOptions": {
                  "platform": "FACEBOOK_PAGE",
                  "kind": "FEED",
                  "link": "https://example.com/blog/new-feature",
              },
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Our new feature drop — read more 👇",
      "scheduledFor": "2026-06-02T12:00:30Z",
      "platforms": [{
        "accountId": "soc_facebook_…",
        "platformOptions": {
          "platform": "FACEBOOK_PAGE",
          "kind": "FEED",
          "link": "https://example.com/blog/new-feature"
        }
      }]
    }'
  ```
</CodeGroup>

See [Platform settings → Facebook](/concepts/platform-settings#facebook) for
the full `platformOptions` reference and
[Media uploads](/concepts/media-uploads) for the two ways to attach
images and video.

## Content types

### Feed (text + optional link)

`kind: "FEED"`. Pure text post, optionally with a `link` whose OG
preview Facebook auto-generates. When you attach media to a `FEED`
post, the server auto-normalizes it to `PHOTO` (1 image) or
`PHOTO_CAROUSEL` (2–10) — you don't have to set `kind` yourself.

### Photo (single image)

`kind: "PHOTO"`. Send exactly one image, either by `mediaIds` (the
presign flow) or by `mediaItems` (URL ingest).

```js Node.js — pre-uploaded media theme={null}
await postbreeze.posts.create({
  content: "New drop today",
  mediaIds: ["med_abc123"],
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  platforms: [{
    accountId: "soc_facebook_…",
    platformOptions: { platform: "FACEBOOK_PAGE", kind: "PHOTO" },
  }],
});
```

```js Node.js — URL ingest (alternative) theme={null}
await postbreeze.posts.create({
  content: "New drop today",
  mediaItems: [
    { type: "image", url: "https://cdn.example.com/drop.jpg", altText: "Product hero shot" },
  ],
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  platforms: [{
    accountId: "soc_facebook_…",
    platformOptions: { platform: "FACEBOOK_PAGE", kind: "PHOTO" },
  }],
});
```

`mediaItems` accepts any reachable URL — Postbreeze fetches the asset
through an SSRF-guarded ingest path. Use `mediaIds` when you've already
uploaded the file via the
[presign flow](/concepts/media-uploads).

### Photo Carousel (2–10 images)

`kind: "PHOTO_CAROUSEL"`. Mixed photo + video is not allowed on organic Page posts — that surface is ads-only.

<CodeGroup>
  ```js Node.js theme={null}
  const post = await postbreeze.posts.create({
    content: "Highlights from the event",
    mediaIds: ["med_1", "med_2", "med_3", "med_4", "med_5"],
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    platforms: [{
      accountId: "soc_facebook_…",
      platformOptions: {
        platform: "FACEBOOK_PAGE",
        kind: "PHOTO_CAROUSEL",
      },
    }],
  });
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "Highlights from the event",
          "mediaIds": ["med_1", "med_2", "med_3", "med_4", "med_5"],
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "platforms": [{
              "accountId": "soc_facebook_…",
              "platformOptions": {
                  "platform": "FACEBOOK_PAGE",
                  "kind": "PHOTO_CAROUSEL",
              },
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Highlights from the event",
      "mediaIds": ["med_1","med_2","med_3","med_4","med_5"],
      "scheduledFor": "2026-06-02T12:00:30Z",
      "platforms": [{
        "accountId": "soc_facebook_…",
        "platformOptions": { "platform": "FACEBOOK_PAGE", "kind": "PHOTO_CAROUSEL" }
      }]
    }'
  ```
</CodeGroup>

### Video

`kind: "VIDEO"`. Single video file. Up to 4 hours of runtime; the
server-side ingest cap is 5 GB.

```js Node.js theme={null}
await postbreeze.posts.create({
  content: "Behind the scenes of the launch",
  mediaIds: ["med_video_abc"],
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  platforms: [{
    accountId: "soc_facebook_…",
    platformOptions: { platform: "FACEBOOK_PAGE", kind: "VIDEO" },
  }],
});
```

### Reel

`kind: "REEL"`. 9:16, 3–90 seconds, video-only. Optionally pass
`reelTitle` — it's prepended to the description on the Reel surface
(not the Feed).

```js Node.js theme={null}
await postbreeze.posts.create({
  content: "How we shipped this in a week",
  mediaIds: ["med_reel_abc"],
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  platforms: [{
    accountId: "soc_facebook_…",
    platformOptions: {
      platform: "FACEBOOK_PAGE",
      kind: "REEL",
      reelTitle: "Shipping in a week",
    },
  }],
});
```

## Media requirements

### Images

| Property       | Requirement                                       |
| -------------- | ------------------------------------------------- |
| Max items      | 10 per carousel, 1 per Photo post                 |
| Formats        | JPG, PNG, GIF, WebP, HEIC, HEIF                   |
| Max file size  | 5 GB (server-side ingest)                         |
| Aspect ratio   | Any (Facebook crops to 1.91:1 in Feed by default) |
| Min resolution | 600 × 315                                         |

### Videos

| Property            | Requirement               |
| ------------------- | ------------------------- |
| Formats             | MP4, MOV, AVI, WebM       |
| Max file size       | 5 GB (server-side ingest) |
| Max duration (Feed) | 240 minutes               |
| Max duration (Reel) | 90 seconds                |
| Min duration (Reel) | 3 seconds                 |
| Aspect ratio (Feed) | 16:9 to 1:1               |
| Aspect ratio (Reel) | 9:16                      |
| Codecs              | H.264 + AAC               |

## Platform-specific fields

| Field       | Type              | Required            | Description                                                                                                                       |
| ----------- | ----------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `platform`  | `"FACEBOOK_PAGE"` | Yes                 | Discriminator.                                                                                                                    |
| `kind`      | enum              | No (default `FEED`) | `FEED`, `PHOTO`, `PHOTO_CAROUSEL`, `VIDEO`, or `REEL`. With media attached, `FEED` auto-normalizes to `PHOTO` / `PHOTO_CAROUSEL`. |
| `link`      | URL               | No                  | Outbound link. Auto-generates a preview card when `kind: "FEED"`.                                                                 |
| `reelTitle` | string            | No                  | Reel-only title prepended to the description (≤ 2,200 chars).                                                                     |

That's the complete schema — there is no `pageId`, `geoRestriction`,
or story support on Facebook today. The `firstComment` field is **not**
inside `platformOptions`; it lives one level up as a sibling of
`platformOptions` on each platforms entry (see below).

## First comment

Pass `firstComment` on the platforms entry — sibling of
`platformOptions`, not nested inside it. Facebook publishes the comment
as the Page (not as a personal user). No delay needed — Facebook
accepts immediate comments after publish.

```js Node.js theme={null}
await postbreeze.posts.create({
  content: "Our new feature drop",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  platforms: [{
    accountId: "soc_facebook_…",
    platformOptions: { platform: "FACEBOOK_PAGE", kind: "FEED" },
    firstComment: "More details on our blog → https://example.com/blog/new-feature",
  }],
});
```

Limit: 8,000 characters per Page comment. First comments are supported
on every Facebook surface **except Reels** — Meta's Reels API rejects
programmatic first comments.

## Analytics

| Metric                | Available |
| --------------------- | --------- |
| Page follows          | ✅         |
| Page media views      | ✅         |
| Page post engagements | ✅         |
| Page video views      | ✅         |
| Page views total      | ✅         |
| Reach                 | ✅         |
| Reactions breakdown   | ✅         |
| Post clicks           | ✅         |
| Comments              | ✅         |
| Shares                | ✅         |

Refresh cadence: every **14 days**.

The 2026 metric names above (`page_follows`, `page_media_view`, etc.) replaced the pre-June-2026 `page_impressions` family. Postbreeze emits the new names directly — no migration needed.

## Common errors

| Error                   | Meaning                                                   | Fix                                                                    |
| ----------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------- |
| `FB_PAGE_TOKEN_EXPIRED` | The Page access token is invalid                          | Reconnect the account.                                                 |
| `FB_REEL_NOT_APPROVED`  | Your Meta App hasn't been reviewed for the Reels use-case | Submit for Meta App Review.                                            |
| `FB_MIXED_MEDIA`        | Photos + videos in `mediaIds`                             | Pick one type.                                                         |
| `FB_CAROUSEL_SIZE`      | Carousel had fewer than 2 or more than 10 photos          | Trim to 2–10.                                                          |
| `FB_RATE_LIMITED`       | Page or app rate limit hit                                | Postbreeze retries with backoff.                                       |
| `FB_LINK_INVALID`       | The `link` URL didn't resolve                             | Verify the URL works in an incognito browser.                          |
| `FB_VIDEO_TOO_LARGE`    | Video > 5 GB                                              | Compress before uploading.                                             |
| `FB_REEL_QUOTA`         | More than 30 Reels published on this Page in 24h          | Wait for the rolling window to clear; Postbreeze retries with backoff. |

## What you can't do

* ❌ Publish to personal Facebook profiles
* ❌ Schedule via Facebook's native scheduler (we use our own queue)
* ❌ Mix video and image in one carousel (ads-only feature)
* ❌ Live video / Live Reels
* ❌ Stories (planned for v2)
* ❌ Tag specific users in caption text
* ❌ Add location tags (Meta removed this from the Graph API)
* ❌ Cross-post to Instagram in the same call (use two entries in `platforms`)
* ❌ First comment on Reels (Meta API limitation)

## Full control: nested shape

The flat shape (`content` + `platforms`) above is the canonical entry
point and covers every Facebook use case. Postbreeze also accepts an
equivalent nested shape (`caption` + `targets`) for callers porting
from older internal tooling — the two shapes produce identical
posts.

<CodeGroup>
  ```js Node.js theme={null}
  import Postbreeze from "@postbreeze/node";

  const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

  const post = await postbreeze.posts.create({
    caption: "Our new feature drop — read more 👇",
    scheduledAt: new Date(Date.now() + 30_000).toISOString(),
    mediaIds: ["med_hero_image"],
    targets: [{
      socialAccountId: "soc_facebook_…",
      platformOptions: {
        platform: "FACEBOOK_PAGE",
        kind: "PHOTO",
        link: "https://example.com/blog/new-feature",
      },
      firstComment: "More details on our blog →",
    }],
  });
  ```
</CodeGroup>

Field mapping flat ↔ nested:

| Flat                      | Nested                               |
| ------------------------- | ------------------------------------ |
| `content`                 | `caption`                            |
| `scheduledFor`            | `scheduledAt`                        |
| `platforms[]`             | `targets[]`                          |
| `platforms[].accountId`   | `targets[].socialAccountId`          |
| `mediaItems` / `mediaIds` | `mediaIds` (URL ingest is flat-only) |

Prefer the flat shape for all new integrations.
