# Creating and scheduling posts

A post has one caption, its media, and one **target** per account it goes to. Each target picks a
[content type](/platforms/) that its account supports, and can change the caption, media and options for that account.

```json
{
  "caption": "Grand opening Sabtu ini! 🎉",
  "mediaIds": ["m_1", "m_2"],
  "targets": [
    { "socialAccountId": "acc_ig", "contentType": "ig_carousel", "options": { "firstComment": "#bali #grandopening" } },
    { "socialAccountId": "acc_th", "contentType": "th_carousel", "captionOverride": "Grand opening Sabtu ini, mampir ya!" },
    { "socialAccountId": "acc_tt", "contentType": "tt_photo", "options": { "tiktok": { "privacyLevel": "PUBLIC_TO_EVERYONE" } } }
  ],
  "tagIds": ["tag_promo"],
  "action": "schedule",
  "scheduledAt": "2026-11-07T10:00:00+08:00"
}
```

## Actions

| `action` | What happens |
|---|---|
| `draft` | Saved, nothing publishes. Pre-flight doesn't run, so a draft can be incomplete. Add `scheduledAt` to show it on the calendar. |
| `schedule` | Checked, then published at `scheduledAt`. |
| `now` | Checked, then published right away. |
| `submit` | Saved as a draft waiting for the owner's approval. See [Approvals](/guides/approvals/). |

Check a post without saving it with [`POST /posts/validate`](/api/operations/validatepost/): same body, `action` optional. It
returns every account's errors and warnings.

## Times

Send ISO 8601 with an offset or `Z`, such as `2026-11-07T10:00:00+08:00`. A time without an offset is rejected, so there's no guessing
about time zones. `GET /me` returns the workspace's time zone, which the calendar and queue use.

## Queue slots

Set weekly posting times in the calendar menu under **Queue slots**, or with [`PUT /queue`](/api/operations/setqueue/). Send `"useNextSlot": true` instead of `scheduledAt` and PostFlow
picks the next free slot (at least 10 minutes ahead, and not within 30 minutes of another post). `GET /queue/next` shows that slot first.

## Options per account

| Option | For |
|---|---|
| `firstComment` | A comment posted right after the post (hashtags, links). Instagram, Facebook, Threads. |
| `followUps` | Up to 5 comments, or a Threads thread, each `delayMinutes` after the one before. |
| `link` | Link posts on Facebook. |
| `shareToFeed` | Instagram Reels: also show in the feed. |
| `instagram.collaborators` | Up to 3 usernames to invite as collaborators. |
| `tiktok.privacyLevel` | Who can see it, e.g. `PUBLIC_TO_EVERYONE`, `SELF_ONLY`. Plus `disableComment`, `disableDuet`, `disableStitch`, `brandContent`, `brandOrganic`. |
| `youtube.title`, `privacy`, `madeForKids`, `tags`, `categoryId`, `thumbnailMediaId` | YouTube uploads. |
| `cover.offsetMs` / `cover.mediaId` | Video cover from a moment in the video or an image, where the platform allows it. |

## Repeating posts

`repeatDays` publishes the post again every 1, 2, 3, 7, 14 or 30 days. `null` stops it.

## Editing

`PUT /posts/{id}` replaces the post: send every field, as when creating. Accounts it already published to are kept as they are.
Send the `version` you read as `expectedVersion` and you'll get `409 CONFLICT` instead of overwriting a change someone made in the app.

To change only the time, use [`POST /posts/{id}/reschedule`](/api/operations/reschedulepost/) with `{ "scheduledAt": "…" }`.
To publish a draft or scheduled post right away, use [`POST /posts/{id}/publish`](/api/operations/publishpost/).

## Deleting

`DELETE /posts/{id}` removes the post from PostFlow. Anything already live on a platform stays there. A post that's publishing at that
moment returns `409`; try again a minute later. [`POST /posts/delete`](/api/operations/deleteposts/) deletes up to 50 at once and
reports the ones it skipped.

## Seeing what's planned

- [`GET /calendar?from=…&to=…`](/api/operations/getcalendar/): every post in a range of up to 92 days, plus the queue slots still free.
- [`GET /posts?status=…`](/api/operations/listposts/): `scheduled`, `published`, `failed`, `attention` or `drafts`, filtered by
  `accountId` or `tagId`. See [Post statuses](/platforms/statuses/).

## TikTok

Before posting to a personal TikTok account, read its settings with
[`GET /accounts/{id}/tiktok-creator-info`](/api/operations/gettiktokcreatorinfo/) and pick `options.tiktok.privacyLevel`
from `privacyLevelOptions`. TikTok requires the creator to choose it; there's no default. See [TikTok](/platforms/tiktok/).