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.
Image fit (aspect ratio): a feed image outside what a channel shows uncropped is handled PER CHANNEL. Instagram shows feed images between 4:5 and 1.91:1: a 9:16 (story-style) image posted to the Instagram feed is CROPPED to 4:5 around the subject by default, because Instagram cuts it to that window anyway and Meta cannot boost a feed post outside it. Pinterest keeps a Pin taller than 2:3 and reports it. Before posting a tall image to Instagram, run
check_media_compatibility(its fit rows say per connected channel whether the image fits and what the default does) and ASK the user: crop to 4:5 (recommended: Meta can boost it) or keep the original (Instagram cuts it itself, no boost)? Pass the answer asmedia_fit: { instagram: "crop" | "keep" }. If the user gave no preference, let the default run and RELAY theimage_fitwarning from the response (it names the crop and how to undo it with update_post). Stories, reels and videos are never cropped; 'default' and the other channels keep the original. Never drop a channel over this.
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 Basic, Premium or Premium+ account → just put the full text (up to 25,000 chars) incontent— no threading needed (checkplatform_details.subscription_typevia list_accounts). On a free account, 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: X treats a post whose text contains a link written with http:// or https:// as a link post (a bare domain such as brand.com or a www. link does not count; in a thread, every part with such a link). Link posts on X are a feature the organisation enables on its OmniSocials account. When it is not enabled, the request is refused with an
x_feature_not_enablederror that says so: tell the user, offer to post without the link or without X, and do not retry as is. Never remove a user's link without asking them.
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 | 10 min | N/A |
Threads | 5 min | N/A |
TikTok | 10 min | 10 min |
LinkedIn / LinkedIn Page | 30 min | N/A |
15 min | 15 min | |
15 min | N/A | |
15 min | N/A | |
Mastodon | (instance-dependent) | N/A |
Telegram | (no cap, 50 MB file limit) | (no cap, 50 MB file limit) |
Discord | (no cap) | (no cap) |
VIDEO FILE-SIZE CAPS — MUST RESPECT THESE (validated via ffprobe at schedule/publish time; drafts exempt):
Platform | Video cap |
Telegram | 50 MB |
Mastodon | 99 MB |
Discord | 1 GB (a file over 20 MB goes as a link in the message) |
Bluesky | 300 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 | Default (mastodon.social); the connected server's own limit applies, links count as 23 |
Threads | 500 | |
YouTube | 500 | Description field |
800 | 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 | |
Telegram | 4,096 | With media, text over 1,024 goes as a reply under the media; a poll question is max 300 |
Discord | 2,000 | A poll question is max 300 |
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, telegram (a Telegram channel or group), discord (a Discord channel). 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: thread mode via `thread_parts`, a poll via `poll`. | |
| 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. | |
| discord | No | Discord options. The post goes to the connected Discord channel through its webhook and shows the webhook's name and picture. Text up to 2,000 chars. Up to 10 images, GIFs and videos per message (they may mix); a video or GIF over Discord's 20 MB file limit is sent as a link in the text. No Stories; a 'reel' is sent as one video message. | |
| threads | No | Threads options: thread mode via `thread_parts`, location tag via `location_id`, a poll via `poll`, the topic via `topic_tag`. | |
| 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: thread mode via `thread_parts`, a poll via `poll`. | |
| telegram | No | Telegram options. Text up to 4,096 chars; with media, text over 1,024 chars is sent as a reply under the media. Up to 10 photos and videos per album, videos up to 50 MB, no Stories. A 'reel' is sent as a video message. | |
| No | Instagram options | ||
| media_fit | No | Image fit per channel, for feed images (type 'post') whose ratio is outside what a channel shows uncropped: { instagram: 'auto' | 'crop' | 'keep', pinterest: ... }. 'auto' (the default when omitted) takes the channel's rule: Instagram crops a tall (9:16) or wide image to 4:5 / 1.91:1 around the subject (Instagram cuts it to that window anyway, and Meta cannot boost a feed post outside it); Pinterest keeps a Pin taller than 2:3 and only reports it. 'crop' / 'keep' force it for that channel. Every decision comes back in the response `warnings` with code `image_fit` (relay it). The crop is a separate file for that channel only; 'default' and the other channels keep the original. On update_post, media_fit alone (no media) re-decides the stored images: 'keep' gives the original back, 'crop' crops an image that was kept. | |
| 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 5-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. | |
| comment_dm_automation | No | Comment-to-DM automation on this post: when somebody comments one of trigger.keywords on the post once it is live on Instagram or the Facebook Page, the account DMs them actions.dm.text once (on Facebook the DM lands in Messenger), and optionally posts one of actions.public_reply.variations under the comment. Instagram or Facebook must be in channels; platforms picks which ones get it. Show the user the keywords and the exact DM text before creating it. The created automations come back as comment_dm_automation on the post (automations lists the id of each channel); manage them later with update_automation / get_automation_activity. |