# Posts, Stories & Reels

Publish feed posts, multi-image posts, link carousels, Stories and Reels to a Facebook Page, plus every platformSpecificData field.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

A plain post becomes a feed post: text on its own, up to 10 images, or 1 video.

Fields in `platformSpecificData` on the Facebook entry select Stories and Reels.

## Feed post

Text with an optional image.

Facebook is one of the few platforms that accepts a post with no media, so `mediaItems` can be omitted.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: published } = await zernio.posts.createPost({
  body: {
    content: 'New collection in store from Monday',
    mediaItems: [
      { type: 'image', url: 'https://cdn.example.com/collection.jpg' }
    ],
    platforms: [
      { platform: 'facebook', accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
    ],
    publishNow: true
  }
});

console.log(published.post.platforms[0].platformPostUrl);
```
</Tab>
<Tab value="Python">
```python
from zernio import Zernio

client = Zernio()

published = client.posts.create_post(
    content="New collection in store from Monday",
    media_items=[
        {"type": "image", "url": "https://cdn.example.com/collection.jpg"}
    ],
    platforms=[
        {"platform": "facebook", "accountId": "66b2e19d8c3f5a7e9d0b1c2d"}
    ],
    publish_now=True
)

print(published["post"]["platforms"][0]["platformPostUrl"])
```
</Tab>
<Tab value="curl">
```bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New collection in store from Monday",
    "mediaItems": [
      {"type": "image", "url": "https://cdn.example.com/collection.jpg"}
    ],
    "platforms": [
      {"platform": "facebook", "accountId": "66b2e19d8c3f5a7e9d0b1c2d"}
    ],
    "publishNow": true
  }'
```
</Tab>
</Tabs>

Response (`201`):

```json
{
  "post": {
    "_id": "65f1c0a9e2b5af0012ab34cd",
    "status": "published",
    "platforms": [
      {
        "platform": "facebook",
        "status": "published",
        "platformPostUrl": "https://www.facebook.com/..."
      }
    ]
  }
}
```

`facebookSettings` holds drafts, link carousels and text backgrounds. It can also be sent at the request root.

Every sample below changes only the `mediaItems`, the `platforms` entry or `facebookSettings` of this request.

## Multi-image post

Up to 10 images in one post. Images and videos cannot be mixed:

```json
"mediaItems": [
  { "type": "image", "url": "https://cdn.example.com/photo1.jpg" },
  { "type": "image", "url": "https://cdn.example.com/photo2.jpg" },
  { "type": "image", "url": "https://cdn.example.com/photo3.jpg" }
]
```

## Link carousel

`facebookSettings.carouselCards` renders a post as a carousel of 2 to 10 cards, each image with its own click-through link and headline.

Cards are layered onto `mediaItems` in order, one card per image. Both arrays must have the same length, and every item must be an image.

`carouselLink` sets the "See more" destination on the end card. It defaults to the first card's link.

Facebook displays about 35 characters of `name` and 30 of `description`. The API accepts up to 255 of each and truncates on render.

```json
{
  "content": "This week's inventory",
  "mediaItems": [
    { "type": "image", "url": "https://cdn.example.com/car-1.jpg" },
    { "type": "image", "url": "https://cdn.example.com/car-2.jpg" },
    { "type": "image", "url": "https://cdn.example.com/car-3.jpg" }
  ],
  "platforms": [
    { "platform": "facebook", "accountId": "66b2e19d8c3f5a7e9d0b1c2d" }
  ],
  "publishNow": true,
  "facebookSettings": {
    "carouselLink": "https://example.com/inventory",
    "carouselCards": [
      { "link": "https://example.com/inventory/car-1", "name": "2027 Sedan", "description": "Low miles" },
      { "link": "https://example.com/inventory/car-2", "name": "2026 SUV", "description": "Certified pre-owned" },
      { "link": "https://example.com/inventory/car-3", "name": "2027 Truck", "description": "Loaded" }
    ]
  }
}
```

## Video and GIF

One video per post.

Send a GIF as `type: "video"`. Facebook treats it as a video that auto-plays and loops.

```json
"mediaItems": [
  { "type": "video", "url": "https://cdn.example.com/launch.mp4" }
]
```

## Large text background

`facebookSettings.textFormatPresetId` renders a text-only feed post as large text on a coloured background, using Facebook's `text_format_preset_id`.

Pass the raw numeric id from Meta. Zernio publishes no catalog of presets, and Facebook may change the set.

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "facebookSettings": { "textFormatPresetId": "123" }
  }
}
```

Pages and text-only feed posts only. An attachment makes Facebook drop the background silently, so Zernio rejects the request with a `400` when:

- `mediaItems` or `carouselCards` are present
- `contentType` is `story` or `reel`
- `content` is empty

**Length is not rejected.** Facebook's composer stops offering a background at around 130 characters, but Meta documents no API limit. Longer content publishes and returns a warning.

While a preset is set, a URL in `content` is not attached as a link preview, for the same reason.

## Story

`contentType: "story"` publishes a Page Story.

- Media is required.
- Stories disappear after 24 hours.
- Captions are not displayed.
- Interactive stickers are not available through the API.

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "contentType": "story" }
}
```

## Reel

`contentType: "reel"` publishes a Reel from a single vertical (9:16) video of 3 to 60 seconds.

`content` is the caption. `title` sets a separate Reel title:

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "contentType": "reel", "title": "Studio day" }
}
```

`collaborators` invites up to 10 other Pages, by numeric Page ID, after the Reel is published (never for drafts). Each Page must accept. Rejected invites come back in `failedCollaborators`:

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "contentType": "reel", "collaborators": ["123456789"] }
}
```

## Draft in Publishing Tools

`facebookSettings.draft: true` creates an unpublished draft in Facebook Publishing Tools instead of publishing, for review and approval workflows.

Drafts work for feed posts (text, link, image, video) and Reels, not Stories. They expire after about 30 days.

`firstComment` is skipped on a draft.

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "facebookSettings": { "draft": true }
  }
}
```

## First comment

`firstComment` is posted as the first comment right after publishing.

It works on feed posts and Reels, not Stories. It is skipped when `facebookSettings.draft` is `true`:

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "firstComment": "Order here: https://shop.example.com"
  }
}
```

## Geo-restriction

`geoRestriction.countries` limits who can see the post to up to 25 countries, as uppercase ISO 3166-1 alpha-2 codes.

It is a hard visibility restriction. People outside those countries cannot see the post at all.

It works on feed posts, videos and Reels, not Stories.

```json
{
  "platform": "facebook",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "geoRestriction": { "countries": ["US", "ES"] }
  }
}
```

## Post to several Pages

When the connected account manages more than one Page, list them with [List Facebook pages](/connect/get-facebook-pages):

```bash
curl "https://zernio.com/api/v1/accounts/66b2e19d8c3f5a7e9d0b1c2d/facebook-page" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"
```

Response (`200`):

```json
{
  "pages": [
    { "id": "123456789012345", "name": "My Brand Page", "username": "mybrand", "category": "Brand", "fan_count": 5000 },
    { "id": "234567890123456", "name": "My Other Page", "username": "myotherpage", "category": "Business", "fan_count": 1200 }
  ],
  "selectedPageId": "123456789012345",
  "cached": true
}
```

`refresh=true` bypasses the cache and fetches the Pages from Meta again, at most once per 60 seconds.

To post to two Pages in one request, repeat the same `accountId` with a different `pageId` in each entry:

```json
"platforms": [
  {
    "platform": "facebook",
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "platformSpecificData": { "pageId": "123456789012345" }
  },
  {
    "platform": "facebook",
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "platformSpecificData": { "pageId": "234567890123456" }
  }
]
```

To change the default Page instead, so `pageId` can be omitted, call `PUT /v1/accounts/{accountId}/facebook-page` with `selectedPageId` ([Update Facebook page](/connect/update-facebook-page)).

## Platform fields

All fields go in `platformSpecificData` on the Facebook entry.

`facebookSettings` can also be sent at the request root, where it applies to every Facebook entry in the request. A per-entry value wins.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `contentType` | `"story"` \| `"reel"` | (feed) | `"story"` for a Page Story (24 hours, no caption), `"reel"` for a Reel (single vertical video). Omit for a feed post. |
| `title` | string | | Reel title, separate from the `content` caption. `contentType: "reel"` only. |
| `collaborators` | Array\<string\> | | Page IDs (digits) to invite as Reel collaborators, up to 10. `contentType: "reel"` only. Not sent for drafts. Rejected invites are listed in `failedCollaborators`. Non-numeric values are ignored with a warning. |
| `firstComment` | string | | Posted as the first comment after publishing. Feed posts and Reels, not Stories. Skipped when `facebookSettings.draft` is `true`. |
| `pageId` | string | (default Page) | Target Page when the account manages several. List them with `GET /v1/accounts/{accountId}/facebook-page`. |
| `geoRestriction` | \{countries\} | | Up to 25 uppercase ISO 3166-1 alpha-2 codes. Feed posts, videos and Reels; not Stories. See [Geo-restriction](#geo-restriction). |
| `facebookSettings.draft` | boolean | `false` | Create an unpublished draft in Facebook Publishing Tools. Feed posts and Reels, not Stories. Drafts expire after about 30 days. |
| `facebookSettings.carouselCards` | Array\<\{link, name?, description?\}\> | | 2 to 10 cards, one per image in `mediaItems` (same length, images only). Mutually exclusive with `contentType`. `name` and `description` take up to 255 characters; Facebook displays about 35 and 30. |
| `facebookSettings.carouselLink` | string (URL) | (first card's link) | "See more" destination on the carousel end card. Only with `carouselCards`. |
| `facebookSettings.textFormatPresetId` | string (digits) | | Facebook preset id for a large-text background. Text-only feed posts on Pages; `400` when media or cards are present, `contentType` is set, or `content` is empty. |