create_post
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:
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_workspacesand 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").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).
Channels: Which platforms to post to? Call
list_accountsto show available options for the active workspace. Ask the user which channels to use.Schedule: When should it be published? (Or save as draft?)
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.
Platform-specific options (ask only when relevant):
Pinterest board (auto-default to first board): If Pinterest is in
channelsandpinterest.board_idis NOT provided, do NOT block on asking the user — and do NOT skip Pinterest. Instead:Call
get_accounton the Pinterest account — the response includes aPinterest Boardstable with each board's name and ID.Use the FIRST board in that list as
pinterest.board_idautomatically.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_partsas an array of 2–25{ text }objects (each ≤ 280 chars); the top-levelcontentis then ignored for X. A single long-form post on a Premium / Premium+ account → just put the full text (up to 25,000 chars) incontent— no threading needed (checkplatform_details.subscription_typevia 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 intocontent— 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
warningsentry (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 402x_credits_insufficienterror (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 |
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 |
15 min | 15 min | |
15 min | N/A | |
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 |
300 MB | |
X (free tier) | 512 MB |
Threads / Reddit | 1 GB |
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):
{ "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 |
500 | Pin description | |
2,200 | Caption | |
TikTok | 2,200 (videos) / 4,000 (photo posts) | Videos have a single caption field |
3,000 | ||
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:
Call
upload_mediawithmethod="upload_url"to get upload instructionsUse code execution to upload the image file to OmniSocials
Use the returned media ID in
media_idswhen 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.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| x | No | X (Twitter) options, including thread mode via `thread_parts`. | |
| type | No | Content type. 'story' takes 1 to 10 media items; each is one slide, published in order. | |
| tiktok | No | TikTok options | |
| bluesky | No | Bluesky options, including thread mode via `thread_parts`. | |
| content | Yes | Post caption. String for same text on all channels, or object with platform keys for per-channel captions: { "default": "fallback", "linkedin": "long version", "threads": "short version" }. The "default" key is used for any selected channel without its own key. Always prefer one call per topic, even when captions differ across platforms. | |
| threads | No | Threads options: thread mode via `thread_parts`, location tag via `location_id`. | |
| youtube | No | YouTube Shorts options. Only applies when type is 'reel' and youtube is among the selected channels. A custom Shorts thumbnail is set with the top-level `video_cover` (a `youtube` override for a YouTube-only image); it shows on Shorts only on channels where YouTube has enabled custom Shorts thumbnails (Partner Program channels first, since July 2026), other channels show a frame from the video. | |
| channels | No | Array of channel IDs to post to (e.g. linkedin, linkedin_page, instagram). Get available IDs from list_accounts. | |
| No | Facebook options | ||
| link_url | No | URL to share as a rich preview card on platforms that support link-share posts (LinkedIn and Facebook). Renders as a tile with thumbnail/title/description instead of plain text. Ignored on platforms that don't support link shares, and ignored on posts that already have media attached (media wins). | |
| No | LinkedIn Profile options | ||
| mastodon | No | Mastodon options, including thread mode via `thread_parts`. | |
| No | Instagram options | ||
| media_ids | No | Media IDs — flat array or per-platform object | |
| No | |||
| user_tags | No | Instagram only. Tag public accounts at x/y positions on a PHOTO (not video/reels/stories). For a single image omit image_index; for a carousel, set image_index to the slide each tag belongs to. Private/non-existent usernames are rejected at publish time. Ignored by other platforms. | |
| link_title | No | Optional title for the link-share preview. LinkedIn uses this when set; Facebook ignores it and fetches OG metadata server-side. Omit to let LinkedIn auto-fetch the page title. | |
| media_urls | No | External image/video/PDF URLs — flat array or per-platform object. Max 10 total (Pinterest: max 5 images per carousel pin), each file ≤ 100 MB. Entries are URL strings or { url, alt } objects (alt = accessibility description, delivered to Mastodon/Bluesky/X/Pinterest/Instagram (images)/LinkedIn (images)). For larger files (up to 1 GB): upload_media with method 'url' first, then pass the returned media id in `media`. | |
| hashtag_set | No | Name of a saved hashtag set (from list_hashtag_sets, matched case-insensitively) to apply to this post. The set's tags are merged in once at create time; tags already present in a caption are skipped. When the user says something like 'add my usual hashtags', check list_hashtag_sets first. Instagram's 30-hashtag cap is enforced with a clear error (hashtag_limit_exceeded). | |
| location_id | No | Instagram only. Facebook Place ID for a single physical venue (with a street address) to tag the post's location. Find it via the location search in the OmniSocials dashboard. Ignored by other platforms. | |
| video_cover | No | Video thumbnail for a post whose media is ONE video. Applied on Instagram (feed videos and reels), Facebook (feed videos and reels), LinkedIn Profile and Page, TikTok, Pinterest and YouTube Shorts. YouTube Shorts: the cover shows on Shorts only on channels where YouTube has enabled custom Shorts thumbnails (rolling out since July 2026, Partner Program channels first); on other channels YouTube stores it as the default thumbnail but shows a frame from the video on Shorts. Say that it depends on the channel; do not promise it. TikTok only takes a frame: a 'custom' cover is skipped there, so add a tiktok override with type 'frame' when the user wants a specific TikTok frame. The per-platform reel fields (instagram.thumb_offset / cover_url, tiktok.video_cover_timestamp_ms, pinterest.video_cover) keep working and win over the base cover for their platform. get_post reads it back as `video_cover`. | |
| scheduled_at | No | ISO 8601 date for scheduled publishing | |
| collaborators | No | Instagram only. Up to 3 public Instagram usernames to invite as co-authors (the 'Collab' feature). Works on image, carousel, and reel posts — NOT Stories. Invited users get an invite in the Instagram app; once they accept, the post also appears on their profile and feed. A leading '@' is stripped; usernames are case-insensitive. Private or non-existent usernames are rejected by Instagram at publish time. Ignored by other platforms. | |
| linkedin_page | No | LinkedIn Company Page options | |
| linkedin_poll | No | Non-sponsored LinkedIn poll(s) — independent per channel, keyed by `linkedin` (personal profile) / `linkedin_page` (company page). A poll is mutually exclusive with media and a link share on that channel's post — a poll takes priority over both at publish time. Still requires `content.linkedin` (or `content.default`) as that channel's caption; the poll itself only carries the question/options/duration. On update_post, set a channel's key to `null` to clear that channel's poll and revert it to a normal post — send the full desired state for both channels, since the whole object replaces wholesale. | |
| google_business | No | Google Business Profile options. Use to publish EVENT or OFFER posts, attach a CTA button, or both. Shape mirrors Google's LocalPost resource (see https://developers.google.com/my-business/reference/rest/v4/accounts.locations.localPosts#LocalPost). Google Business caption rules (enforced at scheduling — text that violates these will return a `validation_error` 400 before the post is saved): • Phone numbers in the caption are rejected — use a CALL button instead. • Inline URLs / bare domains / emails are rejected — use LEARN_MORE / BOOK / SHOP / SIGN_UP / ORDER buttons instead. • Caption max 1500 characters. • Media is optional (text-only posts are allowed). If attached: exactly one JPEG/PNG/WebP image (no video, no carousels). • The workspace must have a Google Business location selected (Settings → Organisation → Workspaces → Google Business) before scheduling — otherwise returns a 400. | |
| link_description | No | Optional description for the link-share preview. LinkedIn uses this when set; Facebook auto-fetches the OG description. | |
| hashtag_placement | No | Where the set's tags land. caption_append (default): appended to each target caption after a blank line. first_comment: posted as the auto first comment on Instagram/Facebook/LinkedIn/LinkedIn Page/YouTube/TikTok (TikTok only when the workspace enabled TikTok comments) (keeps hashtags out of the caption — appended after any explicit first_comment); platforms without a comment API fall back to caption_append. Stories always use captions. | |
| hashtag_platforms | No | Optional subset of the post's channels to apply the hashtag set to (e.g. ["instagram", "tiktok"]). Defaults to all selected channels. | |
| link_thumbnail_url | No | Optional thumbnail image URL for the preview card. Currently applied on Facebook; reserved for LinkedIn. | |
| approval_workflow_id | No | Route the post through a saved approval workflow (id from list_approval_workflows). The post is created as `in_approval` instead of `scheduled`: its approvers are notified, they review it on the Approvals page, and it publishes at schedule_at only once the workflow's last step approves it (or approve_post). Use this whenever the user wants to review/approve agent-created posts before they go out. Requires schedule_at; not allowed with publish_now (use create_post, never create_and_publish_post). A workflow with no approvers on its first step is rejected with validation_error. |