← Files BulkPublishARCHIVED FILE

skills/platform-reference/SKILL.md

15.2 KB · Oct 2, 2026 · 00:23 UTC

↓ Download file

---
name: platform-reference
description: Complete reference for all 15 BulkPublish platforms — post types, media requirements, required fields, character limits, and platformSpecific options. ALWAYS consult before creating posts.
---

# BulkPublish — Platform Reference

## Quick Rules

- **YouTube & TikTok** require video — NEVER include them for image-only posts
- **Pinterest** requires a title — set `platformSpecific.pinterest.title`
- **YouTube** requires a title — set `platformSpecific.youtube.title`
- **Instagram** defaults to `feed_photo` — set `postTypeOverrides.instagram` for video content
- Use `platformContent` for shorter text on platforms with low char limits (Bluesky 300, Pinterest/Threads/Mastodon 500)

---

## Facebook

| Field | Value |
|---|---|
| Platform key | `facebook` |
| Char limit | 63,206 |
| Title required | No |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `post` (default) | 0-10 images OR 0-1 video | Text-only OK. No mixing images+video |
| `reel` | 1 video required | Vertical short-form video |
| `story` | 1 image OR 1 video required | Disappears after 24h |

**Media specs:** Image max 10MB (jpg/png/gif/webp). Video max 2GB (mp4/mov).

**platformSpecific options:** `linkPreview.url`

---

## Instagram

| Field | Value |
|---|---|
| Platform key | `instagram` |
| Char limit | 2,200 |
| Title required | No |
| Account type | Business or Creator (personal rejected) |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `feed_photo` (default) | 1 image required | Single photo post |
| `feed_video` | 1 video required | Single video post |
| `reel` | 1 video required | Short-form video, 9:16 recommended |
| `story` | 1 image OR 1 video required | Disappears after 24h |
| `carousel` | 2-10 images/videos required | Mixed media OK |

**Media specs:** Image max 8MB (jpg/jpeg only). Video max 1GB (mp4/mov).

**platformSpecific options:**
- `collaborators` — string (comma-separated) or array of usernames, without a leading `@`. Tagged as co-authors. Post types `feed_photo`, `feed_video`, `reel` and `carousel`; ignored on `story`
- `trialReel` — boolean, trial reel (graduates based on performance)
- `thumbnailTimestamp` — number (seconds), video thumbnail offset

**IMPORTANT:** If you have video and don't set `postTypeOverrides.instagram`, it defaults to `feed_photo` and FAILS. Always set to `reel` or `feed_video` for video.

---

## X / Twitter

| Field | Value |
|---|---|
| Platform key | `x` |
| Char limit | 280 (25,000 for long posts) |
| Title required | No |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `tweet` (default) | 0-4 images OR 0-1 video | Text-only OK. No mixing images+video |
| `thread` | Per-part media | Use `postFormat: "thread"` + `threadParts` |
| `repost` | None | Reposts (retweets) an existing post; `platformSpecific.repostId` = status URL or ID; own text/media ignored |

**Media specs:** Image max 5MB (jpg/png/gif/webp), max 4. Video max 512MB (mp4/mov), max 1, max 140s.

---

## YouTube

| Field | Value |
|---|---|
| Platform key | `youtube` |
| Char limit | 5,000 (description) |
| Title required | **YES** — `platformSpecific.youtube.title` (1-100 chars) |
| Video required | **YES** — always |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `video` (default) | 1 video required | Standard upload |
| `short` | 1 video required | Vertical, under 3 minutes |

**Media specs:** Video max 128GB (mp4/mov/avi/wmv/flv/webm), max 1.

**platformSpecific options:**
- `title` — **REQUIRED**, 1-100 chars (falls back to first line of content)
- `privacyStatus` — `public` (default), `private`, `unlisted`
- `categoryId` — YouTube category ID (default 22)
- `madeForKids` — boolean (default false)
- `tags` — string array
- `playlistId` — add to playlist after upload
- `thumbnailUrl` — custom thumbnail URL

**IMPORTANT:** YouTube ONLY accepts video. Never include YouTube channel for image-only posts.

---

## TikTok

| Field | Value |
|---|---|
| Platform key | `tiktok` |
| Char limit | 2,200 |
| Title required | No (uses content) |
| Video required | **YES** (or images for slideshow) |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `video` (default) | 1 video required | Standard video upload |
| `photo_slideshow` | 1-35 images required | Auto-creates slideshow with music |

**Media specs:** Video max 4GB (mp4/mov), max 1. Image max 20MB (jpg/jpeg/webp), max 35 for slideshow.

**platformSpecific options:**
- `privacyLevel` — `SELF_ONLY` (default), `PUBLIC`, `FRIENDS`
- `disableDuet` — boolean
- `disableStitch` — boolean
- `disableComment` — boolean
- `isAigc` — boolean (AI-generated content disclosure)
- `thumbnailTimestamp` — number (seconds)

**IMPORTANT:** TikTok ONLY accepts video (or images for slideshow). Default privacy is `SELF_ONLY` — set to `PUBLIC` for visibility.

---

## LinkedIn

| Field | Value |
|---|---|
| Platform key | `linkedin` |
| Char limit | 3,000 |
| Title required | No (optional for articles/videos) |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `post` (default) | 0-1 image OR 0-1 video | Text-only OK |
| `multi_image` | 2-20 images required | Image gallery |
| `pdf_carousel` | 2-20 images required | Converted to PDF carousel |
| `article` | 0-1 image (thumbnail) | Link share, requires `platformSpecific.url` |

**Media specs:** Image max 10MB (jpg/png/gif), max 20. Video max 500MB (mp4 only), max 1, 3s-30min.

**Account types:** `personal` (member profile) or `organization` (company page). Both are connected in the dashboard and then used like any other channel — `list_channels` reports which via `accountType`.

The two run on separate LinkedIn apps (company pages use the Community Management API, which LinkedIn requires to be the only product on its application), so they are **gated separately**. Company pages and personal profiles carry separate availability. Pages can be paused — no new page connections — while personal-profile posting is fully live. When a variant is what blocks a write, the 403 `PLATFORM_DISABLED` error carries an `accountType` field naming it.

**platformSpecific options:**
- `title` — article/video title
- `description` — article description
- `url` — **REQUIRED for article type**, link URL
- `carouselTitle` — PDF carousel title

**Mentions:** write `@[Acme Corp](urn:li:organization:1337)` (Page) or `@[Jane Doe](urn:li:person:ID)` in `content`. Ask the user for the Page or person URN if they want a real mention token; this connector cannot look one up. Other channels on the same post get plain `@Acme Corp`.

---

## Pinterest

| Field | Value |
|---|---|
| Platform key | `pinterest` |
| Char limit | 500 |
| Title required | **YES** — `platformSpecific.pinterest.title` (1-100 chars) |
| Board required | **YES** — `platformSpecific.pinterest.boardId` (auto-creates "BulkPublish" board if missing) |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `pin` (default) | 1 image required | Standard pin, 2:3 ratio recommended |
| `video_pin` | 1 video required | Video pin |
| `carousel` | 2-5 images required | Multi-image pin |

**Media specs:** Image max 20MB (jpg/png/webp), max 5. 1000x1500 recommended. Video max 2GB (mp4/mov), max 1.

**platformSpecific options:**
- `title` — **REQUIRED**, 1-100 chars (falls back to first 100 chars of content)
- `description` — pin description, max 500 chars
- `link` — destination URL
- `boardId` — target board ID (ask the user which board; the ID is in the board's URL)
- `dominantColor` — hex color
- `coverImageUrl` — video pin cover image. Optional: when omitted the server uses an attached image, then the video's auto-extracted poster frame

---

## Threads

| Field | Value |
|---|---|
| Platform key | `threads` |
| Char limit | 500 |
| Title required | No |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `text` (default) | None | Text-only post |
| `image` | 1 image required | Photo post |
| `video` | 1 video required | Video post |
| `carousel` | 2-20 images/videos | Mixed media OK |
| `thread` | Per-part | Use `postFormat: "thread"` + `threadParts` |
| `repost` | None | Reposts an existing post; `platformSpecific.repostId` = numeric post ID; own text/media ignored |

**Media specs:** Image max 8MB (jpg/png), max 20. Video max 500MB (mp4/mov), max 20.

**platformSpecific options:** `quotePostId` — quote another Threads post; `topicTag` — one topic, no leading `#`, no periods/ampersands; `locationId` — numeric place ID, supplied by the user (only once the channel has granted location tagging)

---

## Bluesky

| Field | Value |
|---|---|
| Platform key | `bluesky` |
| Char limit | 300 |
| Title required | No |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `post` (default) | 0-4 images OR 0-1 video | Text-only OK. No mixing. |
| `thread` | Per-part | Use `postFormat: "thread"` + `threadParts` |
| `repost` | None | Reposts an existing post; `platformSpecific.repostId` = post URL or ID; own text/media ignored |

**Media specs:** Image max 10MB auto-resized to <976KB (jpg/png/webp), max 4. Video max 100MB (mp4 only), max 1, max 60s.

---

## Google Business Profile

| Field | Value |
|---|---|
| Platform key | `gmb` |
| Char limit | 1,500 |
| Title required | Only for events (`eventTitle`) |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `standard` (default) | 0-1 image | Business update |
| `event` | 0-1 image | Requires dates/times |
| `offer` | 0-1 image | Promotional offer |

**Media specs:** Image max 5MB (jpg/png), max 1. No video support.

**platformSpecific options:**
- `ctaType` — `BOOK`, `ORDER`, `SHOP`, `LEARN_MORE`, `SIGN_UP`, `CALL`
- `ctaUrl` — CTA destination URL (not needed for CALL)
- `eventTitle` — event title (required for event type)
- `startDate` / `endDate` — `YYYY-MM-DD` format
- `startTime` / `endTime` — `HH:MM` format
- `couponCode` — offer coupon code
- `redeemOnlineUrl` — offer redemption URL
- `termsConditions` — offer terms text

---

## Mastodon

| Field | Value |
|---|---|
| Platform key | `mastodon` |
| Char limit | 500 |
| Title required | No |

**Post types:**

| Type | Media | Notes |
|---|---|---|
| `post` (default) | 0-4 images OR 0-1 video | Text-only OK. No mixing. |
| `thread` | Per-part | Use `postFormat: "thread"` + `threadParts` |
| `repost` | None | Reposts an existing post; `platformSpecific.repostId` = post URL or ID; own text/media ignored |

**Media specs:** Image max 16MB (jpg/png/webp/avif/gif), max 4. Video max 99MB (mp4 only), max 1, max 300s.

**platformSpecific options:**
- `visibility` — `public` (default), `unlisted`, `private`, `direct`
- `spoilerText` — content warning text
- `language` — ISO 639-1 language code

---

## Discord

| Field | Value |
|---|---|
| Platform key | `discord` |
| Char limit | 2,000 |
| Title required | No |
| Target channel required | **YES** — `channelId` |

**Post types:** message with optional attachments (images/video), sent to a text channel of the connected server.

**Media specs:** up to **10 attachments**, 25MB each (Discord's limit for a non-boosted server). Images and video may be mixed.

**platformSpecific** — keyed by **channel ID**. A connected Discord "channel" in BulkPublish is an entire **server** (guild), so the message still needs a destination channel inside it:

```json
{"platformSpecific": {"discord": {"12": {"channelId": "1090123456789012345"}}}}
```

| Option | Notes |
|---|---|
| `channelId` | **REQUIRED.** The target Discord *text channel* snowflake — **not** the BulkPublish channel id used as the outer key. List postable channels via `GET /api/channels/{id}/options`. |

**Gotchas:** publishing uses a **global bot token**, not the per-user OAuth token. A failure is therefore never a re-authentication problem — reconnecting will not fix it and the channel is never flagged `needs_reconnect`. The real causes are permissions: the bot lacks access to the channel, cannot send messages there, or the channel was deleted.

---

## Telegram

| Field | Value |
|---|---|
| Platform key | `telegram` |
| Char limit | 4,096 (text) / 1,024 (media caption) |
| Title required | No |

**Post types:** text message, photo, video, or media group (up to 10 items), sent to the chat fixed when the channel was connected.

**platformSpecific:** **none.** Telegram accepts no options — there is nothing to select per post. To publish to a second channel or group, connect it as a separate BulkPublish channel.

**Media specs:** media is handed to Telegram as a **URL** rather than uploaded, and the Bot API caps download-by-URL far below its upload limits: **5MB** images (jpg/png/webp) and **20MB** video (mp4).

**Gotchas:** content is sent as **plain text with no parse mode**, so `&`, `<` and `>` are safe but Markdown you write is **not rendered** (URLs still auto-link). When a post has media and text longer than 1,024 characters, the media is sent captionless and the full text follows as a **second message** — nothing is truncated, but it arrives as two messages.

---

## Tumblr

| Field | Value |
|---|---|
| Platform key | `tumblr` |
| Char limit | 32,768 |
| Title required | No |

**Post types:** `post` — text, photo, or video post to a Tumblr blog.

**Media specs:** up to 30 images (jpg/png/gif/webp, 20MB each), OR exactly one video (mp4/mov, 500MB). A video CANNOT be combined with images in the same post.

**platformSpecific** — keyed by **channel ID**, because one Tumblr account can own several blogs:

```json
{"platformSpecific": {"tumblr": {"12": {"blogName": "myblog", "title": "Heading", "tags": ["art"], "link": "https://example.com"}}}}
```

| Option | Notes |
|---|---|
| `blogName` | Target blog. Defaults to the blog the channel was connected as. List options via `GET /api/channels/{id}/options`. |
| `title` | Rendered as a heading above the body. |
| `tags` | Array of strings, no leading `#`. Tags drive discovery on Tumblr more than on other networks. |
| `link` | Appended as a link block. |
| `sourceUrl` | Attribution URL stored as the post's source. |

---

## Snapchat

| Field | Value |
|---|---|
| Platform key | `snapchat` |
| Char limit | 160 (Spotlight description / Saved Story title fallback only) |
| Title required | No |

**Post types** (set via `postTypeOverrides.snapchat`):

| Type | Media | Notes |
|---|---|---|
| `story` (default) | exactly 1 image or video | The caption is NOT sent to Snapchat — plain stories carry no text. |
| `saved_story` | exactly 1 image or video | Title max 45 chars; defaults to the first line of the caption, truncated. |
| `spotlight` | 1 video only, 6–60s | Caption becomes the description (max 160 chars, truncated); hashtags are clickable. |

**Media specs:** exactly ONE file per post. Images jpg/png; videos mp4/mov, vertical, 5–60s (Spotlight 6–60s), max 1GB.

**platformSpecific** — keyed by **channel ID** (a flat object applies to every Snapchat channel):

```json
{"platformSpecific": {"snapchat": {"12": {"title": "Behind the scenes", "locale": "en_US", "saveToProfile": true}}}}
```

| Option | Notes |
|---|---|
| `title` | Saved Story title, max 45 chars. Defaults to the caption's first line, truncated. |
| `locale` | Spotlight locale. Default `en_US`. |
| `saveToProfile` | Spotlight only. Default `true`; `false` sends `skip_save_to_profile`. |

**Gotchas:** `_firstComment` is NOT supported. Engagement (individual comments/reactions) is not readable. Metrics ARE reported: impressions, reach, likes, comments, shares, clicks, video views.

SHA-256: fdf1f8caadc73dc1ecb64938dbe8d2fed4ed84a6c937c171b0dfec400ef1d91d