From omnisocials
Manage social media across 11 platforms (Instagram, Facebook, LinkedIn Profile + Page, YouTube, TikTok, X, Pinterest, Bluesky, Threads, Mastodon, Google Business). Create posts, stories, reels, upload media, organize folders, view analytics, read and reply to the social inbox (DMs, comments, mentions), and configure webhooks via the OmniSocials API.
How this skill is triggered — by the user, by Claude, or both
Slash command
/omnisocials:omnisocialsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create, schedule, and publish social media content across 11 platforms using OmniSocials.
Create, schedule, and publish social media content across 11 platforms using OmniSocials.
OmniSocials is a social media management tool that lets you schedule posts and track analytics across Instagram, Facebook, LinkedIn (personal profile + company page), YouTube, TikTok, X (Twitter), Pinterest, Bluesky, Threads, Mastodon, and Google Business.
LinkedIn has two channel ids:
linkedin_pageis a company page. Both can be connected to one workspace and post independently. Always checkaccounts:listfor which the user has connected.
Before using this skill, ensure:
API Key: Run the setup command to configure your API key securely
<skill-path>/scripts/omnisocials.js setupexport OMNISOCIALS_API_KEY=omsk_live_your_keyRequirements: Node.js 18+ (for built-in fetch API). No other dependencies needed.
Config priority (highest to lowest):
OMNISOCIALS_API_KEY environment variable./.omnisocials/config.json (project-local, in user's working directory)~/.config/omnisocials/config.json (user-global)CRITICAL: When you receive an "API key not found" error from the CLI:
Note: All script paths in this document are relative to the skill directory where this SKILL.md file is located. Resolve them accordingly based on where the skill is installed.
IMPORTANT: Follow these rules at all times.
--pinterest-board-idaccounts:get <pinterest_account_id> — its output lists each board's name and ID. Use the FIRST board automatically. After the post is created, mention to the user which board was 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."). If the user named a specific board in the request, match it case-insensitively against the list and use that one instead.| User says... | Action |
|---|---|
| "Post this to Instagram" | accounts:list to find Instagram channel ID, then posts:create --text "..." --channels <id> |
| "Schedule a post for tomorrow" | posts:create --text "..." --channels <ids> --schedule "2026-04-07T09:00:00Z" |
| "Show my scheduled posts" | posts:list --status scheduled |
| "Upload this image" | media:upload --url "https://..." (or media:upload-base64 --file ./img.jpg for a local file) |
| "Create a reel for TikTok" | posts:create --text "..." --channels <tiktok_id> --type reel --media-urls "https://video.mp4" |
| "Post to all platforms" | accounts:list, then posts:create --text "..." --channels <all_ids> |
| "Post a thread to X" | `posts:create --channels <x_id> --x-thread "part 1 |
| "Post a thread to Bluesky" | `posts:create --channels <bluesky_id> --bluesky-thread "part 1 |
| "Post a thread to Mastodon" | `posts:create --channels <mastodon_id> --mastodon-thread "part 1 |
| "Tag a location on Instagram" | locations:search "<place name>", then posts:create ... --location-id <id> |
| "Post a reel with music" | audio:search "<song or artist>" (no query = trending), then posts:create ... --type reel --instagram-audio-id <id> |
| "How are my posts doing?" | analytics:overview --period 7d, or analytics:posts <id,id,...> for many posts at once |
| "Any new DMs / comments?" | inbox:list --unread (add --platform / --type dm|comment|mention to filter) — needs the inbox:read scope |
| "Reply to that message" | inbox:messages <conversation-id> to read the thread, then inbox:reply <conversation-id> --text "..." — needs inbox:write |
| "Mark that conversation read" | inbox:read <conversation-id> — needs inbox:write |
| "Organize my media" | folders:list / folders:create --name "...", then upload with --folder-id <id> |
| "Delete that post" | Confirm with user, then posts:delete <id> |
| "Publish my draft" | Confirm with user, then posts:publish <id> |
| "Set up a webhook" | webhooks:create --url "https://..." --events post.published,post.failed |
Follow this workflow when creating posts:
List accounts to find available channel IDs:
./scripts/omnisocials.js accounts:list
Upload media if needed (required for stories, reels, Instagram, TikTok, Pinterest):
./scripts/omnisocials.js media:upload --url "https://example.com/image.jpg"
Note the returned media_id.
Create the post with appropriate channels, media, and platform options:
./scripts/omnisocials.js posts:create --text "..." --channels <id1>,<id2> --media-ids <media_id>
Schedule or publish as needed:
--schedule "2026-04-10T14:00:00Z" to scheduleposts:create-and-publish to publish immediatelyposts:publish <id> after confirmation| Command | Description |
|---|---|
setup | Interactive setup - prompts for API key, validates, and saves |
setup --api-key <key> --global | Non-interactive setup to global config |
config:show | Show current config, API key source |
| Command | Description |
|---|---|
posts:list | List posts. Flags: --status draft|scheduled|published|failed, --limit, --offset |
posts:get <id> | Get full post details |
posts:recent-platform | Fetch recent posts live from the connected platform APIs, including content published outside OmniSocials. Use when posts:list is empty (brand-new workspace). Returns each post's platform-native id (the stable de-dupe key for storing posts), a permalink, the full caption, format, timestamps, normalized engagement, and every raw metric the platform exposes as an exact integer (Instagram includes reach/views/saves/shares from per-post insights). Add --json for the full, untruncated captions + exact metrics + ids + permalinks (the human table truncates/rounds). LinkedIn personal profiles can't be listed live (LinkedIn grants apps no such permission), so linkedin results are posts published through OmniSocials with their latest collected stats; TikTok photo posts are backfilled the same way. Flags: --limit (1-50, default 25), --platforms (comma-separated filter). Requires the analytics:read scope. |
posts:create | Create a new post. Flags: --text, --channels, --schedule, --type post|story|reel, --media-ids, --media-urls, --link-url (+--link-title/--link-description/--link-thumbnail-url), --location-id, --collaborators, --user-tags, --x-thread, plus platform flags |
posts:create-and-publish | Create and publish immediately. Same flags as posts:create except --schedule |
posts:update <id> | Update a draft or scheduled post. Same flags as posts:create |
posts:publish <id> | Publish a draft/scheduled post now |
posts:delete <id> | Delete a post (cannot be undone) |
| Command | Description |
|---|---|
media:list | List uploaded media files. Flags: --limit, --offset |
media:upload | Upload media from a URL — image, video, or PDF. Flags: --url (required), --filename |
media:upload-base64 | Upload a local file or base64 data (image, video, or PDF). Flags: --file <path> (auto-encodes + infers MIME, incl. .pdf) OR --data + --mime-type; plus --filename, --name (findable label), --folder, --folder-id |
| PDF carousels | Upload a PDF and it is split into one image slide per page (max 20). The response lists a media ID for every slide — pass all of them to posts:create --media-ids to post the deck as a carousel. On LinkedIn the slides post as a native swipeable document; on Instagram, TikTok, Threads and Pinterest as an image carousel. Lets you post an existing deck (Canva/PowerPoint/Figma exported to PDF) without exporting each slide by hand. |
media:check | Check whether media fits target platforms before posting. Flags: --url, or --media-id, or --size-bytes + --mime |
media:delete <id> | Delete a media file |
| Command | Description |
|---|---|
folders:list | List media folders (id, name, parent, item count). Use folder ids with media:upload-base64 --folder-id. |
folders:create | Create a folder. Flags: --name (required), --parent-id (nest under another folder) |
| Command | Description |
|---|---|
locations:search "<name>" | Search Facebook Places for taggable locations (min 2 chars). Returns location_id values to pass to posts:create --location-id for Instagram place tags. |
| Command | Description |
|---|---|
audio:search ["<song/artist>"] | Search Meta's licensed music catalog for Instagram Reels. No query = currently trending audio; --type original_sound searches original sounds. Returns audio_id values for posts:create --instagram-audio-id. Only tracks licensed for third-party publishing appear (selection can differ from the IG app); needs a Facebook account connected whose Page links the Instagram account. |
| Command | Description |
|---|---|
accounts:list | List all connected social media accounts with channel IDs, platforms, content types, and Pinterest boards |
accounts:get <id> | Get full account details including platform-specific info |
| Command | Description |
|---|---|
analytics:post <post-id> | Get post analytics: impressions, engagements, likes, comments, shares, per-platform stats (thread posts on X/Bluesky/Mastodon are summed across their parts) |
analytics:best-times | Recommended posting slots (day + hour) for one platform, computed from the workspace's own posting history (recency-weighted, outlier-damped, in the account's timezone). Top 3 slots + per-day scores. Under 15 analyzed posts it returns clearly-labeled industry defaults with posts_needed — tell the user that. Use before scheduling when no time was specified. Flags: --platform (required), --timezone (IANA override). Requires analytics:read. |
analytics:posts <id,id,...> | Get analytics for up to 100 posts in one call (bulk). Use this instead of looping analytics:post to avoid the rate limit. |
analytics:overview | Workspace analytics overview. Flags: --period 7d|30d|90d, --start-date YYYY-MM-DD, --end-date YYYY-MM-DD |
analytics:accounts | Account-level analytics (followers, subscribers). Flags: --platform, --date YYYY-MM-DD |
Read and reply to DMs, comments, and mentions across connected accounts. Requires the opt-in inbox:read / inbox:write scopes — the user enables "Social Inbox access" when creating the API key. If a call returns insufficient_scope, tell the user to create a new key with Social Inbox access. conversation_id values can contain : and () (LinkedIn URNs); pass them exactly as returned by inbox:list (the CLI URL-encodes them for you). Results are cursor-paginated: when more exist, the CLI prints the --cursor value to fetch the next page.
| Command | Description |
|---|---|
inbox:list | List conversations (latest message per conversation). Flags: --platform instagram|facebook|linkedin, --type dm|comment|mention, --unread (only conversations with unread messages), --limit, --cursor. Shows participant, unread count, last message, and the related post for comments/mentions. Requires inbox:read. |
inbox:messages <conversation-id> | Full message history for one conversation, oldest→newest, each with direction, timestamp, and read/replied state. Flags: --limit, --cursor. Requires inbox:read. |
inbox:read <conversation-id> | Mark a conversation's messages as read. Requires inbox:write. |
inbox:reply <conversation-id> | Send a reply. Flags: --text (required), --attachment-url, --attachment-type. Requires inbox:write. |
| Command | Description |
|---|---|
webhooks:list | List all webhooks |
webhooks:create | Create a webhook. Flags: --url (required), --events (required, comma-separated: post.scheduled, post.published, post.failed) |
webhooks:get <id> | Get webhook details |
webhooks:update <id> | Update webhook. Flags: --url, --events, --active true|false |
webhooks:delete <id> | Delete a webhook |
webhooks:rotate-secret <id> | Rotate webhook signing secret (save the new secret immediately) |
All commands support these flags:
| Flag | Description |
|---|---|
--json | Output raw JSON response (useful for parsing) |
--api-key <key> | Override API key for this command |
--base-url <url> | Override API base URL |
--help | Show help |
| Platform | Post | Story | Reel | Media Required |
|---|---|---|---|---|
| Yes | Yes | Yes | Always (image or video) | |
| Yes | Yes | Yes | Optional for posts, required for stories/reels | |
LinkedIn (linkedin) | Yes | No | No | Optional |
LinkedIn Page (linkedin_page) | Yes | No | No | Optional |
| YouTube | No | No | Yes (Shorts) | Always (video) |
| TikTok | Yes | No | Yes | Always (image or video) |
| X (Twitter) | Yes | No | No | Optional |
| Yes | No | No | Always (image or video + board_id) | |
| Bluesky | Yes | No | No | Optional |
| Threads | Yes | No | No | Optional |
| Mastodon | Yes | No | No | Optional |
| Google Business | Yes | No | No | Optional |
| Flag | Description |
|---|---|
--pinterest-board-id | Required for Pinterest. Get board IDs from accounts:get <pinterest_account_id> |
--pinterest-title | Pin title |
--pinterest-link | Link URL attached to the pin |
--pinterest-video-cover | Cover image URL for a video pin |
--pinterest-alt-text | Alt text for the pin |
| Flag | Description |
|---|---|
--youtube-title | Video title |
--youtube-privacy | Privacy: public, private, or unlisted |
--youtube-tags | Tags (comma-separated) |
--youtube-category-id | YouTube category ID |
--youtube-made-for-kids | Made for kids flag |
| Flag | Description |
|---|---|
--instagram-share-to-feed | Share reel to feed |
--instagram-cover-url | Reel cover image URL |
--instagram-thumbnail-type | Thumbnail type: from-video or from-library |
--instagram-audio-id | Licensed music for the reel — an audio_id from audio:search. Reels only (Meta's API can't add music to feed posts/carousels/stories). Needs a Facebook account connected whose Page links this Instagram account. |
--instagram-audio-volume | Music volume 0–100 (default 100). Only with --instagram-audio-id |
--instagram-video-volume | Video's own audio volume 0–100 (default 100; 0 = music-only reel) |
--instagram-trial-reel | Publish the reel as a Trial Reel — shown to non-followers first to test performance. ONLY use when the user explicitly asks for a Trial Reel. Reels only. Not available on every account: Instagram requires roughly 1,000+ followers and enables the feature per account; ineligible accounts fail at publish with a clear error. |
--instagram-trial-graduation-strategy | How a Trial Reel graduates to all followers: MANUAL (default — the user decides in the Instagram app) or SS_PERFORMANCE (Instagram shares it automatically if it performs well). Only with --instagram-trial-reel |
Posts the given text as the first comment automatically, right after the post publishes. Common for keeping hashtags or a link out of the main caption. One flag per channel, so you can set a different first comment per platform in the same call. Not posted for stories.
| Flag | Description |
|---|---|
--instagram-first-comment | First comment on the Instagram post/reel (max 2200 chars) |
--facebook-first-comment | First comment on the Facebook post. Page posts only (the API cannot comment on personal profiles) |
--linkedin-first-comment | First comment on the LinkedIn profile post (max 1250 chars). Handy for "link in first comment" |
--linkedin-page-first-comment | First comment on the LinkedIn company page post (max 1250 chars) |
--youtube-first-comment | First comment on the YouTube video (max 10000 chars). The video must allow comments |
| Flag | Description |
|---|---|
--tiktok-privacy | Privacy: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY |
--tiktok-disable-comment | Disable comments |
--tiktok-disable-duet | Disable duets |
--tiktok-disable-stitch | Disable stitches |
--tiktok-video-cover-timestamp-ms | Video only. Timestamp (ms) of the frame to use as the cover |
--tiktok-is-aigc | Mark as AI-generated content |
--tiktok-brand-content-toggle | Paid partnership promoting a third-party brand |
--tiktok-brand-organic-toggle | Promoting your own business / brand |
--tiktok-auto-add-music | Photo carousels only. TikTok auto-selects a soundtrack |
| Flag | Description |
|---|---|
--x-reply-settings | Who can reply: following or mentionedUsers (empty string = everyone) |
--x-thread "a || b || c" | Post a thread. Parts are split on || (2–25 parts). For per-tweet media, build the post with --json and a full x.thread_parts array instead. |
--bluesky-thread "a || b || c" | Post a Bluesky thread. Parts are split on || (2–25 parts, each ≤ 300 chars). Links, mentions and hashtags become clickable automatically. For per-post media, build the post with --json and a full bluesky.thread_parts array instead. |
--mastodon-thread "a || b || c" | Post a Mastodon thread. Parts are split on || (2–25 parts, each ≤ 500 chars). Each toot replies to the previous one natively. For per-toot media, build the post with --json and a full mastodon.thread_parts array instead. |
| Flag | Description |
|---|---|
--link-url | URL to attach as a link-preview card |
--link-title | Override the preview title |
--link-description | Override the preview description |
--link-thumbnail-url | Override the preview thumbnail image |
| Flag | Description |
|---|---|
--location-id | Facebook Place ID to tag (find one with locations:search) |
--collaborators | Up to 3 public Instagram usernames invited as co-authors (comma-separated) |
--user-tags | JSON array of photo tags: [{"username":"name","x":0.5,"y":0.5,"image_index":0}] (x/y are 0–1 from top-left) |
Media can be the same across all platforms or different per platform:
Same media for all platforms:
./scripts/omnisocials.js posts:create --text "..." --channels <ig>,<li> --media-urls "https://example.com/photo.jpg"
Different media per platform (use --json flag and API directly for per-platform media objects):
The API supports media_urls as an object: { "default": ["url1"], "instagram": ["url2"], "pinterest": ["url3"] }. The default key is the fallback for platforms without their own key. Pass an empty array to opt a platform out of media.
PDF by URL: a PDF passed via --media-urls (or media_urls in the API) is rasterized into one image slide per page (max 20, in order) — on LinkedIn it publishes as a swipeable document, elsewhere as an image carousel. Same behaviour as uploading the PDF via media:upload.
./scripts/omnisocials.js accounts:list
./scripts/omnisocials.js posts:create --text "Excited to announce our new feature!" --channels <linkedin_id>,<x_id>
./scripts/omnisocials.js posts:create --text "Check this out" --channels <instagram_id> --type reel --media-urls "https://example.com/video.mp4" --instagram-share-to-feed --instagram-cover-url "https://example.com/cover.jpg"
./scripts/omnisocials.js posts:create --text "New drop is live" --channels <instagram_id> --type reel --media-urls "https://example.com/reel.mp4" --instagram-first-comment "#reels #newdrop #marketing
link: https://example.com/shop"
./scripts/omnisocials.js posts:create --text "Happy Monday!" --channels <id1>,<id2> --schedule "2026-04-13T09:00:00Z"
./scripts/omnisocials.js posts:create --text "Beautiful design inspiration" --channels <pinterest_id> --media-urls "https://example.com/pin.jpg" --pinterest-board-id <board_id> --pinterest-title "Design Inspiration" --pinterest-link "https://example.com"
./scripts/omnisocials.js media:upload --url "https://example.com/photo.jpg"
# Returns: ID: media_abc123
./scripts/omnisocials.js posts:create --text "New photo!" --channels <id> --media-ids media_abc123
./scripts/omnisocials.js posts:create --text "Quick tip" --channels <youtube_id> --type reel --media-urls "https://example.com/short.mp4" --youtube-title "Quick Tip #1" --youtube-privacy public --youtube-tags "tips,tutorial"
./scripts/omnisocials.js posts:create --text "Watch this" --channels <tiktok_id> --type reel --media-urls "https://example.com/video.mp4" --tiktok-privacy PUBLIC_TO_EVERYONE
./scripts/omnisocials.js posts:create --text "Kicking off a thread" --channels <x_id> --x-thread "First point || Second point || Wrapping up"
./scripts/omnisocials.js posts:create --channels <bluesky_id> --bluesky-thread "First point || Second point || Wrapping up"
./scripts/omnisocials.js posts:create --channels <mastodon_id> --mastodon-thread "First point || Second point || Wrapping up"
./scripts/omnisocials.js locations:search "Blue Bottle Coffee"
# Returns: location_id: 1234567890 Blue Bottle Coffee — 1 Ferry Building, San Francisco
./scripts/omnisocials.js posts:create --text "Coffee time" --channels <instagram_id> --media-urls "https://example.com/photo.jpg" --location-id 1234567890
./scripts/omnisocials.js folders:create --name "Summer Campaign"
# Returns: ID: 42
./scripts/omnisocials.js media:upload-base64 --file ./promo.jpg --name "summer-hero" --folder-id 42
./scripts/omnisocials.js analytics:posts 1024,1025,1026
./scripts/omnisocials.js inbox:list --unread --platform instagram
# Returns each conversation with its Conversation: <conversation_id>
./scripts/omnisocials.js inbox:messages "<conversation_id>"
./scripts/omnisocials.js inbox:reply "<conversation_id>" --text "Thanks so much for the kind words!"
./scripts/omnisocials.js inbox:read "<conversation_id>"
./scripts/omnisocials.js posts:recent-platform --limit 25
./scripts/omnisocials.js posts:recent-platform --platforms instagram,tiktok --json
./scripts/omnisocials.js posts:create --text "Company update" --channels linkedin_page
./scripts/omnisocials.js posts:list --status scheduled --json
./scripts/omnisocials.js analytics:overview --period 30d
./scripts/omnisocials.js analytics:overview --start-date 2026-03-01 --end-date 2026-03-31
./scripts/omnisocials.js webhooks:create --url "https://yoursite.com/webhook" --events post.published,post.failed
./scripts/omnisocials.js setup
./scripts/omnisocials.js setup --api-key omsk_live_xxx --global
| Error | Cause | Fix |
|---|---|---|
API key not found | No API key configured | Run setup or set OMNISOCIALS_API_KEY |
unauthorized / invalid_api_key | Invalid or expired API key | Check key at Settings > API |
insufficient_scope | API key missing required scope | Create a new key with needed scopes |
rate_limit_exceeded | Too many requests (100/min limit) | Wait and retry after the reset time |
validation_error | Missing required fields or invalid data | Check required media/fields for the platform |
not_found | Resource doesn't exist | Verify the ID is correct |
The API allows 100 requests per minute per API key. Response headers include:
X-RateLimit-Limit: Max requests per windowX-RateLimit-Remaining: Remaining requestsX-RateLimit-Reset: Unix timestamp when the window resetsaccounts:list to discover channel IDs and platform capabilities--json when you need to parse the output programmaticallyaccounts:list to see what content types each account supports (post, story, reel)accounts:get <pinterest_id> to see available boards and their IDs2026-04-10T14:00:00Z)--schedule), review, then publish with posts:publishGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.
npx claudepluginhub omnisocials/omnisocials-agent-skills --plugin omnisocials