← Files BulkPublishARCHIVED FILE

skills/schedule-post/SKILL.md

9.03 KB · Oct 3, 2026 · 06:24 UTC

↓ Download file

---
name: schedule-post
description: Create, schedule, or publish social media posts via BulkPublish MCP. Use when the user wants to post to social media.
---

# BulkPublish — Post Creation Reference

## create_post parameters

```
content        (string, required) — post text
channels       (array, required)  — [{channelId: number, platform: string}]
                                    Get these from list_channels first
status         ("draft"|"scheduled") — default "draft"
scheduledAt    (ISO 8601 string)  — required when status is "scheduled"
timezone       (string)           — e.g. "America/New_York", "Asia/Karachi"
mediaFileIds   (number[])         — IDs from upload_media
platformContent (object)          — per-platform text: {"x": "Short", "linkedin": "Longer version"}
postTypeOverrides (object)        — per-platform format: {"instagram": "reel", "facebook": "story"}
postFormat     ("post"|"video"|"reel"|"story"|"carousel"|"thread") — "thread" requires threadParts
threadParts    (array)            — [{content: string, mediaFileIds?: number[]}], min 2 parts.
                                    EVERY part is length-checked against every target platform,
                                    not just the first — an over-long part 400s the whole request
platformSpecific (object)         — per-platform options; see the
                                    platform-reference skill. Auto-reply after
                                    publishing goes here as the top-level key
                                    "_firstComment", NOT a firstComment param:
                                    {"_firstComment": "Link in bio!"}
                                    Unsupported on discord, pinterest, tiktok,
                                    gmb, tumblr and snapchat (recorded as failed;
                                    the main post still publishes)
requestApproval (boolean)         — default false; hold a scheduled post for team
                                    approval (approvalStatus becomes "pending")
linkTrackingOverride (boolean|null) — default null; per-post override for
                                    bulkpubli.sh link tracking. true shortens the
                                    post's links and counts clicks, false posts
                                    them as written, null inherits the org setting
```

`linkTrackingOverride` is tri-state, so **omit it unless the user actually asked
for one behaviour or the other** — sending `false` is an explicit "post the links
as written" and is not the same as leaving it unset. Shortening happens at
publish time, per channel, and is **skipped** for any channel where the rewrite
would push the post past that platform's character limit: a short URL is 28
characters and can be longer than the link it replaces, so on X (280) or Bluesky
(300) tracking may silently not apply. The post still publishes, with its
original links.

Every post object returned by the API also carries the read-only approval fields
`approvalStatus` (`"none"` default | `"pending"` | `"approved"` | `"rejected"`),
`approvedBy` (string|null), `approvedAt` (date-time|null) and `rejectionReason`
(string|null). See "Team approval" below.

## Post type overrides

| Platform | Types |
|---|---|
| Instagram | `reel`, `story`, `carousel` |
| Facebook | `story`, `reel` |
| TikTok | `slideshow` |
| YouTube | `short` |
| X/Twitter | `thread` |
| Threads | `thread` |
| Bluesky | `thread` |
| Mastodon | `thread` |

## Team approval

`approvalStatus` is **orthogonal to `status`**: the scheduler skips `pending` and
`rejected` posts even when they are scheduled and overdue. Default is `"none"`.

- **Requesting approval** — pass `requestApproval: true` on `create_post` or
  `update_post` (default `false`) to hold a scheduled post for team approval;
  `approvalStatus` becomes `"pending"`. For API keys belonging to members whose
  role lacks `post:publish` (contributors), this is **forced server-side
  regardless of the flag** — their scheduled posts always land in the approval
  queue. Never tell such a user their post was scheduled: check the returned
  `approvalStatus` and say it is awaiting approval.
- **The approval queue** — `list_posts` with `approvalStatus: "pending"` (the
  filter accepts `none` | `pending` | `approved` | `rejected`), or
  `GET /api/posts?approvalStatus=pending`.
- **Approving or rejecting** — done by a teammate with an approver role (owner, admin, approver) in BulkPublish. Approval releases the post: it publishes at its scheduled time, or immediately if that time has passed. A rejected post returns to draft with a reason, and the author can edit and resubmit. You cannot approve or reject from here — say who needs to act.
- **`APPROVAL_REQUIRED`** — `publish_post` and `retry_post` return **403** with
  error code `APPROVAL_REQUIRED` for roles without `post:publish`. Do not retry:
  create/update the post with `requestApproval: true` and tell the user a
  teammate has to approve it. Publishing a pending/rejected post *as an
  approver* implicitly approves it.

## Publishing flow

- **Draft then publish**: `create_post` (status: "draft") → `publish_post` (postId)
- **Schedule for later**: `create_post` (status: "scheduled", scheduledAt: "2026-04-12T09:00:00Z")
- **Schedule with review**: `create_post` (status: "scheduled", scheduledAt: ..., requestApproval: true) → a teammate approves it in BulkPublish
- **Optimal timing**: call `get_queue_slot` (optionally pass `timezone`, default UTC) to get the best next slot. It returns `{suggestedTime, timezone}` — it does NOT take a channelId or date (any such args are ignored).
- **Retry failures**: `retry_post` (postId, optional `republish`) re-queues the
  post's `failed` platforms. A platform can also end in status `unconfirmed` —
  terminal: the publish request may have reached the platform but its response
  was lost, so the post **may already be live**; it is never auto-retried. If
  the post has unconfirmed platforms and no failed ones, `retry_post` returns
  **400** with code `UNCONFIRMED_REQUIRES_REPUBLISH` — ask the user to check
  the account on the platform, and only pass `republish: true` (default false)
  after they confirm the post is not live; it also retries the unconfirmed
  platforms and **can duplicate the post**.
- **Publish as story**: set `postTypeOverrides` to `"story"` for Facebook/Instagram/Snapchat — publishes directly as a story, no separate call needed (Snapchat stories need exactly 1 image or video and send no caption)

## Stories vs postTypeOverrides

To publish as a story, use `postTypeOverrides` at creation time:
```json
{ "postTypeOverrides": { "facebook": "story", "instagram": "story" } }
```
This publishes the post AS a story. Set it at creation time — an existing regular post cannot be turned into a story from here.

## Character limits

| Platform | Limit |
|---|---|
| X/Twitter | 280 (25,000 long posts) |
| Instagram | 2,200 |
| Facebook | 63,206 |
| LinkedIn | 3,000 |
| TikTok | 2,200 |
| YouTube | 5,000 (description) |
| Threads | 500 |
| Bluesky | 300 |
| Pinterest | 500 |
| Google Business | 1,500 |
| Mastodon | 500 |
| Snapchat | 160 (Spotlight description / Saved Story title fallback — plain stories send no text) |

## Platform media requirements

| Platform | Requires | Notes |
|---|---|---|
| YouTube | Video ONLY | Do NOT include YouTube for image-only posts |
| TikTok | Video ONLY | Or images for `photo_slideshow` type |
| Instagram | Depends on type | `feed_photo`=image, `reel`/`feed_video`=video, `carousel`=2-10 mixed |
| Pinterest | Image or video | Needs board ID in `platformSpecific` or channel default |
| Facebook/X/LinkedIn/Threads/Bluesky/Mastodon | Any or none | Text-only posts OK |
| Snapchat | Exactly 1 image or video | jpg/png or mp4/mov, vertical, 5–60s (Spotlight 6–60s video-only), max 1GB |

## Common mistakes

- `channels` takes objects `{channelId, platform}`, NOT just IDs
- Always call `list_channels` first to get valid channelId + platform pairs
- `scheduledAt` must be in the future and in ISO 8601 format
- To publish immediately: create as draft, then call `publish_post`
- `mediaFileIds` are numbers from `upload_media`, not file paths
- **Do NOT send image-only posts to YouTube or TikTok** — they will fail
- **Instagram defaults to `feed_photo`** — set `postTypeOverrides.instagram` to `reel` or `feed_video` for video
- **Pinterest needs a board ID** — set via `platformSpecific.pinterest.boardId` or it tries to auto-create one
- **Never assume a scheduled post will go out** — if the response has
  `approvalStatus: "pending"`, it is held until someone approves it. Report that,
  not "scheduled".
- **`requestApproval` defaults to `false`** and `approvalStatus` defaults to
  `"none"` — only set the flag when the user asks for review, but always read the
  response back because contributors get it forced on.
- **Do not call `publish_post` again after a 403 `APPROVAL_REQUIRED`** — the role
  cannot publish; submit for approval instead.
- **Content char limits** are enforced per-platform — use `platformContent` for shorter overrides on Pinterest (500), Bluesky (300), etc.

SHA-256: a95b280d0f65ffed7ff653a671162e6126d79ebc727aa3539e62c3ceb1bc091f