# Posts

Source: https://postgale.com/docs/api/posts

Create drafts, scheduled and live posts, idempotent retries, and the platformSpecificData options of each platform.

## Create a post

`platforms` lists 1–20 distinct accounts, each with its own `platformSpecificData`. Without `publishNow` or
`scheduledFor` the post is a draft; `publishNow: true` publishes **live**. Host media with
[`POST /media`](https://postgale.com/docs/api/media) and use its `publicUrl`.

POST /api/v1/posts:
```bash
curl https://postgale.com/api/v1/posts \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -H 'x-request-id: menu-2026-11-01' \
  -d @post.json
```

post.json:
```json
{
  "content": "New menu is live ☕",
  "scheduledFor": "2026-11-01T15:00:00Z",
  "platforms": [
    {
      "accountId": "acc_instagram",
      "platformSpecificData": {
        "mediaUrl": "https://postgale.com/media/…/menu.jpg",
        "firstComment": "#coffee #brunch"
      }
    },
    {
      "accountId": "acc_facebook_page",
      "platformSpecificData": {
        "mediaUrl": "https://postgale.com/media/…/menu.jpg"
      }
    },
    {
      "accountId": "acc_threads",
      "platformSpecificData": {
        "topicTag": "coffee",
        "replyControl": "everyone"
      }
    }
  ]
}
```

## Idempotency

Send `x-request-id` on `POST /posts`. A retry with the same id returns the original post (200 instead of 201). The same text cannot
be posted twice within 24 hours.

## Lifecycle

- `GET /posts` lists the 100 most recent posts with their targets (`?status=`); `GET /posts/{id}` returns one post with per-target status, platformPostId, URL and error.
- `PATCH /posts/{id}` with `{content}` replaces the text of a draft, scheduled, pending_approval or failed post.
- `POST /posts/{id}/publish` publishes live now (and retries failed targets).
- `DELETE /posts/{id}` deletes the post from Postgale: scheduled posts are cancelled, published posts stay on the platforms.
- When approval applies, new posts are held as `pending_approval` until a person approves them in the dashboard (see [Approval](https://postgale.com/docs/api/authentication#approval)).

## Published posts

| Method | Path | Description |
| --- | --- | --- |
| POST | `/posts/{id}/edit` | Body `{content, accountId?}`: edit the live post (Facebook, Telegram, Discord; X Premium when X launches) |
| POST | `/posts/{id}/unpublish` | Body `{accountId?}`: delete the live post from the platform (Facebook, Threads, Telegram, Discord, Bluesky, Mastodon; X when it launches) |
| POST | `/posts/{id}/comments-settings` | Body `{enabled, accountId?}`: turn comments on or off (Instagram) |

## platformSpecificData by platform

Only the keys for the account's platform are used. Skipped options are returned as warnings on the target. X (coming soon) options are in the
[X guide](https://postgale.com/docs/x), Telegram's in the [Telegram guide](https://postgale.com/docs/telegram), Discord's in the [Discord guide](https://postgale.com/docs/discord), Bluesky's in the [Bluesky guide](https://postgale.com/docs/bluesky), Mastodon's in the [Mastodon guide](https://postgale.com/docs/mastodon) and TikTok's in full in the [TikTok guide](https://postgale.com/docs/tiktok).

### Instagram

| Key | Notes |
| --- | --- |
| `mediaUrl / mediaUrls` | Required. Public HTTPS; images must be JPEG. Carousel: 2–10 items |
| `mediaType / contentType` | IMAGE \| VIDEO \| REELS \| STORIES \| CAROUSEL, or feed \| reel \| story \| carousel. Videos publish as Reels |
| `caption (post content)` | Max 2200 characters and 30 hashtags; not used on stories |
| `firstComment` | Max 2200 characters; not on stories |
| `collaborators` | Up to 3 usernames; not on stories |
| `userTags` | `[{username, x, y, mediaIndex?}]`; photos need x and y in [0,1], videos only the username |
| `locationId` | Location page id; not on stories |
| `altText / altTexts` | Images only, max 1000 characters; altTexts per carousel item |
| `shareToFeed, thumbOffset, coverUrl, audioName` | Reels only (thumbOffset in ms, coverUrl HTTPS) |
| `trialParams` | Reels only: `{graduationStrategy: MANUAL \| SS_PERFORMANCE}` |
| `commentsEnabled` | false turns comments off right after publishing |

### Facebook Pages

| Key | Notes |
| --- | --- |
| `contentType` | feed (default) \| reel \| story |
| `mediaUrl / mediaUrls, mediaType` | Feed: one photo or video, or 2–10 photos (no videos mixed in). Reel: one video. Story: one photo or video, no caption. mediaType IMAGE \| VIDEO |
| `message (post content)` | Max 63206 characters |
| `link` | HTTPS link; text posts without media only |
| `title` | Videos and reels, max 255 characters |
| `firstComment` | Max 8000 characters; not on stories or drafts |
| `draft` | true creates an unpublished Page post (text, link or multi-photo only) |

### Threads

| Key | Notes |
| --- | --- |
| `text (post content)` | Max 500 characters |
| `mediaUrl / mediaUrls, mediaType` | TEXT \| IMAGE \| VIDEO \| CAROUSEL (2–20 items) |
| `topicTag` | 1–50 characters, no "." or "&" |
| `replyControl` | everyone \| accounts_you_follow \| mentioned_only \| parent_post_author_only \| followers_only |
| `linkAttachment` | HTTPS URL shown as a preview; text posts only |
| `altText / altTexts` | Max 1000 characters |
| `firstComment` | Max 500 characters, published as a reply |
| `threadItems` | Reply chain of 1–20 `{content, mediaUrl?, mediaUrls?, mediaType?, altText?}`; replaces the post content |

### TikTok

| Key | Notes |
| --- | --- |
| `privacyLevel` | Required: PUBLIC_TO_EVERYONE \| MUTUAL_FOLLOW_FRIENDS \| FOLLOWER_OF_CREATOR \| SELF_ONLY (unaudited apps: SELF_ONLY) |
| `mediaUrl / mediaUrls, mediaType` | video (default) or image (photo posts take mediaUrls; title max 90) |
| `disableComment, disableDuet, disableStitch, brandContent, brandOrganic, autoAddMusic` | Interaction and disclosure settings |
