Create a new social media post, story, or reel.
IMPORTANT — Before calling this tool, make sure you have all required information from the user. If anything is missing, ASK the user before calling:
0. **Workspace**: If the user has only one workspace, just use it (no need to ask). If the user has named a workspace in their request, switch to it once and remember it for the rest of the conversation. If multiple workspaces exist and the user has NOT named one, call `list_workspaces` and ASK which workspace to post to before continuing. Never silently pick a workspace when more than one exists — each workspace has different connected channels and different audiences. After a successful create/schedule/publish, mention the workspace name in your reply for clarity (e.g. "Scheduled to the Acme workspace for Tuesday 3pm").
1. **Content/caption**: What text should the post have? If captions differ per platform, use one call with an object: { "default": "fallback text", "linkedin": "long version", "threads": "short version", "x": "short version", "bluesky": "short version" }. Always prefer one call per topic. RESPECT character limits per platform (see below).
2. **Channels**: Which platforms to post to? Call `list_accounts` to show available options for the active workspace. Ask the user which channels to use.
3. **Schedule**: When should it be published? (Or save as draft?)
4. **Media** (REQUIRED for some types):
- Stories: ALWAYS require an image or video. A story can carry up to 10 media items: each item is one slide, published as its own story in the order given. Ask the user which slides and in what order.
- Reels: ALWAYS require a video.
- Instagram posts: ALWAYS require at least one image or video.
- TikTok posts: ALWAYS require at least one image or video.
- Pinterest posts: ALWAYS require an image AND a board_id.
- Other platforms (LinkedIn Profile, LinkedIn Page, X, Bluesky, etc.): Media is optional.
5. **Platform-specific options** (ask only when relevant):
- **Pinterest board (auto-default to first board)**: If Pinterest is in `channels` and `pinterest.board_id` is NOT provided, do NOT block on asking the user — and do NOT skip Pinterest. Instead:
1. Call `get_account` on the Pinterest account — the response includes a `Pinterest Boards` table with each board's name and ID.
2. Use the FIRST board in that list as `pinterest.board_id` automatically.
3. In your reply, explicitly mention which board you used (e.g. "Posted to your 'Marketing' board on Pinterest — let me know if you'd prefer a different one and I'll move it.") so the user can correct course.
If the user named a board ("post to my Marketing board") or specified one in the request, match it (case-insensitive) against the list and use that one instead of the first.
- YouTube: Title, privacy status, tags?
- TikTok: Privacy level?
- **X threads vs long-form**: A chained "thread" (the user explicitly asks for one) → pass `x.thread_parts` as an array of 2–25 `{ text }` objects (each ≤ 280 chars); the top-level `content` is then ignored for X. A single **long-form** post on a Premium / Premium+ account → just put the full text (up to 25,000 chars) in `content` — no threading needed (check `platform_details.subscription_type` via list_accounts). On free / Basic, X caps a single post at 280 chars, so either split into a thread or shorten. Never cram "1/", "2/" prefixes into `content` — that posts one tweet, not a thread.
- **X posts containing a link use credits**: X's API charges more for posts whose text contains a URL; OmniSocials passes that platform fee through as credits from the organisation's existing balance (threads: per link-containing part). Only a link written with http:// or https:// counts; a bare domain (brand.com) or a www. link is free. The create response includes a `warnings` entry (`x_url_post_credits`) with the cost and current balance — relay it to the user. Credits are only deducted after the post successfully publishes; a failed publish is never charged. If the balance can't cover it at publish time, only the X target fails (its error explains the shortfall) and the post can be retried later. Posts without links stay free — never remove a user's link to dodge the fee without asking them. Scheduling is also gated up front: every scheduled X link post reserves its cost, and a create/schedule that would push the reserved total past the balance is refused with a 402 `x_credits_insufficient` error (details carry credits_required / credits_balance / credits_reserved) — tell the user their X credit balance is too low for this post, don't silently retry.
**VIDEO DURATION CAPS — MUST RESPECT THESE (returns 400 validation_error when exceeded):**
| Platform | Post mode | Reel mode |
|----------|-----------|-----------|
| Facebook | 240 min | 240 min (the old 90 s Reel cap is gone) |
| YouTube Short | (no Post mode) | **3 min** |
| X | 140 s | N/A |
| Bluesky | 180 s | N/A |
| Threads | 5 min | N/A |
| TikTok | 10 min | 10 min |
| LinkedIn / LinkedIn Page | 10 min | N/A |
| Instagram | 15 min | 15 min |
| Pinterest | 15 min | N/A |
| Reddit | 15 min | N/A |
| Mastodon | (instance-dependent) | N/A |
**VIDEO FILE-SIZE CAPS — MUST RESPECT THESE (validated via ffprobe at schedule/publish time; drafts exempt):**
| Platform | Video cap |
|----------|-----------|
| Mastodon | 99 MB |
| Bluesky | 100 MB |
| Instagram | 300 MB |
| X (free tier) | 512 MB |
| Threads / Reddit | 1 GB |
| Pinterest | 2 GB |
| Facebook / TikTok | 4 GB |
| LinkedIn / LinkedIn Page | 5 GB |
| YouTube | 256 GB |
Upload requests are capped at **100 MB** on top of these; anything bigger gets rejected with `code: file_too_large` before the validator runs.
Cap-violation error shape (one sentence per offending platform):
```json
{ "error": { "code": "validation_error", "message": "YouTube only allows videos up to 3min; yours is 3min 12s. Trim the video or deselect YouTube." } }
```
Before sending an oversized video, warn the user and offer to trim, deselect that platform, or split. Drafts can still be created without media — the validator only runs when transitioning to scheduled or publish_now.
**CHARACTER LIMITS — MUST RESPECT THESE:**
| Platform | Max chars | Notes |
|----------|-----------|-------|
| X (free / Basic) | 280 | Long posts require Premium or Premium+ — Basic does NOT count |
| X (Premium / Premium+) | 25,000 | Check platform_details.subscription_type on the account |
| Bluesky | 300 | Counted in graphemes |
| Mastodon | 500 | |
| Threads | 500 | |
| YouTube | 500 | Description field |
| Pinterest | 500 | Pin description |
| Instagram | 2,200 | Caption |
| TikTok | 2,200 (videos) / 4,000 (photo posts) | Videos have a single caption field |
| LinkedIn | 3,000 | |
| Facebook | 63,206 | Very generous |
When posting to multiple platforms with different limits, ALWAYS use per-platform captions. For example, if posting to LinkedIn (3000 chars) and X (280 chars), use: { "default": "full version", "x": "shortened version" }. Never post content that exceeds a platform's limit.
Channel IDs you can pass: instagram, facebook, threads, linkedin (personal profile), linkedin_page (company page), youtube, tiktok, pinterest, x, bluesky, mastodon. `linkedin` and `linkedin_page` are independent. A workspace can have both connected and post to each separately. Always confirm with list_accounts which are actually connected.
Do NOT call this tool without media when creating stories, reels, Instagram posts, TikTok posts, or Pinterest posts — it will fail.
**IMPORTANT - When the user shares an image or screenshot in chat**: You MUST upload it before creating the post. Do NOT skip the image. Do NOT create a text-only draft when an image was provided. Follow these steps:
1. Call `upload_media` with `method="upload_url"` to get upload instructions
2. Use code execution to upload the image file to OmniSocials
3. Use the returned media ID in `media_ids` when creating the post
Only if code execution is completely unavailable, save as a draft and tell the user to add the image at app.omnisocials.com.