- Social
- v1.0.0
- threads
- youtube
- publishing
Social Media Manager
Post to connected Facebook Pages and Instagram Business accounts. Supports text posts, photo posts, scheduled posts, post insights, and post deletion.
SKILL.md
Social Media Manager
ALWAYS show the user a preview and ask for explicit confirmation before posting, reposting or deleting. Connected accounts are managed in the MyClawBots dashboard; all posts are audited and logged.
Use the MCP tools
The myclawbots MCP server is pre-configured on this machine — do all
social work through its tools, never by hand-building API calls:
list_social_accounts— call FIRST; every other call needs anaccount_idfrom herecreate_upload_url— stage a local file: returns a one-timeupload_url+ finalpublic_url; PUT the raw bytes (curl -X PUT --data-binary @file.png -H "Content-Type: image/png" <upload_url>) — no credentials needed in that command- Both steps are required. The
public_urlis dead until the PUT succeeds. Posting it beforehand hands the platform a 404, which the platform then reports as its own download failure — that reads like a domain-verification or networking problem and is not one. If you cannot PUT raw bytes (for example the image only exists in this conversation, not on disk), useupload_mediawith base64 instead.
- Both steps are required. The
upload_media— small images only, via base64 (1–2 small files max; otherwisecreate_upload_url)post_to_social— publish (fields below)
Threads replies — read the conversation with get_post_comments, then answer a
specific comment by passing its reply_to_id back to post_to_social. That is the
whole loop: read → pick → reply. The same field chains replies to yourself, which
is how you post a multi-part thread: publish, then reply to the id you got back,
and so on. Replies take media and carousels like any other post.
Threads quotes — quote_post_id: "18077148170451907" publishes your post as a
quote of that one: your text (and any image, video or carousel) with the quoted
post embedded under it. Use the numeric id from get_recent_posts,
search_threads, get_threads_mentions or get_post_comments — a permalink or
its short code is rejected. It combines with anything a normal post can do
except ghost_post. The result carries is_quote: true and quoted; confirm
it applied with get_recent_posts (quoted_post).
Threads reposts — repost_on_threads {account_id, post_id} shares an
existing post (anyone's) to the account's profile with no added text; it
shows publicly under the profile's Reposts tab. Want to add commentary? Quote it
instead. Show the user the post you are about to repost and get an explicit
yes first. To undo a repost, call delete_post with the repost_id it
returned — that removes it (verified; Meta does not document it). Reposting the
same post twice is refused with "already reposted" — say so, and don't retry. A repost appears in
get_recent_posts as its own row with media_type: REPOST_FACADE and
reposted_post.
Threads polls — poll_options: ["Traditional", "Hybrid", "Agentic"] attaches a
poll. 2-4 options, each 1-25 characters, text-only posts only: a poll cannot
go on a post with an image, video or carousel. Options appear in the order given.
Write them short — 25 characters is roughly two words, so "Retrieval-Augmented
Generation" will be rejected rather than trimmed.
Threads topics — topic_tag: "AI agents" files the post under a topic, which
is how it surfaces to people who do not follow the account. One per post, 1-50
characters, no . or &. It belongs to the post that opens a thread; sending
it on a reply is rejected, because Threads would accept the field and drop it.
Threads long text — a Threads post is capped at 500 characters, and
long_text is the way past it: up to 10,000 characters attached behind the post,
with content staying as the short visible hook. Text-only posts, and not
combinable with a poll. long_text_link adds a URL inside the attachment.
Threads spoilers — spoiler_text: ["he dies at the end"] blurs those exact
words until a reader taps them; up to 10 phrases, each quoted verbatim from
content (a phrase that is not in the text is rejected, not skipped, so that a
spoiler never silently fails to hide anything). spoiler_media: true blurs the
attached image, video or carousel.
Threads ghost posts — ghost_post: true publishes a text-only post that is
fully public now and auto-archives 24 hours later. It cannot be combined with
media, a poll, a reply, a quote, a topic tag or long text; text spoilers are the one extra
it allows. Tell the user about the 24-hour expiry before publishing one — the
post looks permanent in the feed, so nobody finds out by looking.
get_recent_posts,get_post_insights,get_post_comments,get_account_insights— readssearch_public_pages,get_public_page_metrics— public competitor researchdelete_post— remove a post you publishedhide_reply— hide or unhide a reply on your own Threads postrepost_on_threads— repost a Threads post to the profile (confirm first; undo with delete_post on the repost_id)reply_to_comment,hide_comment,delete_comment— answer or moderate a comment on your own Facebook or Instagram post (LinkedIn: reply to any comment, delete your own)comment_on_post,edit_comment,like_post— LinkedIn profile only: comment on any post, edit your own comment, like/unlike a post or comment (all public — confirm first)search_threads— search public Threads by keyword or tagget_threads_mentions— public posts mentioning the connected accountlookup_threads_profile— a public competitor's Threads profile + public posts
Threads keyword search needs Meta's approval, and failure is silent.
Without the threads_keyword_search permission Meta does not error — it
returns only the account's own posts, or nothing. So thin results mean
"not approved" at least as often as they mean "nobody is talking about
this". Never report an empty search as though the topic were quiet.
When a search comes back empty or only self-authored, say that plainly:
I can't search public Threads yet — that needs keyword-search approval from Meta for the MyClawBots Threads app, which is a separate App Review submission. Until then I can only see our own posts.
Do not tell the user to change their own Threads account settings. The permission belongs to the app, not to them, and there is nothing for them to toggle.
get_threads_mentions fails the same silent way: unapproved, Meta returns
only mentions written by a Threads tester on the app. Apply the same rule —
few results is not evidence that nobody mentioned them.
Both tools return a note field when the result set looks unapproved.
Quote it. Do not invent a reason for thin results: there is no such thing as
a search permission restricted to certain topics or industries, and telling
the user their account is limited that way sends them looking for a setting
that does not exist.
lookup_threads_profile needs the exact handle; Meta offers no fuzzy
match, so a near-miss returns nothing rather than a suggestion. Only profiles
with 100+ followers are discoverable at all. Use it to read a competitor's
positioning and recent posts before drafting a content plan — never present
its numbers as the user's own.
Fallback only if the MCP tools are unavailable: the same operations exist as a
REST API at ${PROXY_URL}/social/* with header x-api-key: $PROXY_API_KEY —
shell expansion only, never paste a key value: secrets appear masked as
*** in your transcript, and pasting the mask sends a broken key. If auth
fails while test -n "$PROXY_API_KEY" says the var is set, your command was
built wrong — do not advise regenerating keys.
Media flow
- Images the user attaches in chat reach you as pictures, without a file
path. If the message has an
[attachment note: …], run itscurlcommands to get them as files. For an image pasted straight into the chat, look for OpenClaw's copy, newest first:ls -t /root/.openclaw/media/inbound/ | head -5. If there is none, the image is only visible to you: ask the user for a link to it — never for a file path. - A
media://inbound/<name>reference is not a path — curl and other tools cannot open it. The file (image or video) is on disk at/root/.openclaw/media/inbound/<name>; use that path, e.g.curl -X PUT --data-binary @/root/.openclaw/media/inbound/<name> …. - Show the user an image before posting it with one line
MEDIA:<absolute path>per file in your reply (files under/root/.openclaw/workspace/).view_imageshows a file only to you. - Media URLs must be publicly reachable HTTPS — platforms fetch them themselves.
Staged URLs live ~2h. Stage once, reuse the same
public_urlfor every platform — never re-upload per platform. - A staged URL that has expired cannot be retried. Post it promptly after staging. If posting fails because the file is gone, the fix is to upload it again and post straight away — never offer to retry the same post later, and never read a media failure as the platform being down. The error says which it is; quote it rather than interpreting it.
- TikTok only accepts URLs on our own staged domain — always stage for TikTok.
- Google Drive files: post with
media_source: "google_drive"+media_file_id. - Accepted types: jpeg, png, webp, gif, mp4, quicktime. Images ≤12MB, video ≤300MB (via
create_upload_url). Pinterest takes JPEG or PNG only — convert a WebP or GIF before staging it for a Pin.
Platform support at a glance
| Text only | Single image | Video / Reel | Carousel | Mixed carousel | Schedule | Quote / repost | |
|---|---|---|---|---|---|---|---|
| yes | yes | yes (video or reel) |
2–10 | no — images only | yes | no | |
| no — media required | yes | yes (always a Reel) | 2–10 | yes | no | no | |
| threads | yes | yes | yes | 2–20 | yes | no | yes (quote_post_id / repost_on_threads) |
| linkedin (profile) | yes | yes | yes (one video, ≤128MB) | 2–10 | no — images only | no | no |
| linkedin (Page) | yes | yes | no | 2–10 | no — images only | no | no |
| youtube | no — video only | no | yes (the whole post) | no | no | no | no |
| tiktok | no — media required | yes | yes | 2–35 photos | no | no | no |
| twitter (X) | yes | yes | yes (one video) | 2–4 images | no | no | no |
| no — image required | yes | only when enabled (one staged MP4/MOV) | 2–5 images | no | no | no |
"Mixed carousel" = photos and videos in one post (item types are auto-detected —
no media_type with media_urls). Never combine media_url with media_urls.
Per-platform rules
- facebook —
scheduled_time(ISO 8601 with a timezone, 15 min–29 days ahead) schedules; on any other platform it is refused, never ignored;media_type: "reel"posts a Page Reel vs"video"a regular video. - instagram — media mandatory; video always publishes as a Reel (max 15 min, 9:16 best). Licensed library music cannot be attached via any API — the audio must already be in the file; say so instead of trying.
- threads — 500 character limit. Quote with
quote_post_id(any post type, not a ghost post); repost withrepost_on_threads— both public, both need the user's explicit yes, anddelete_poston therepost_idundoes a repost. - linkedin — max 150 posts/day; 3000 characters. A profile can post one video
(
media_url, ≤128MB, never in a carousel); a Page cannot (offer text/images).- Keep the
post_idURN (urn:li:share:…/urn:li:ugcPost:…):delete_post,comment_on_postandlike_posttake it. - Comments and likes (profile only).
comment_on_post {account_id, post_id, message}(≤1250 chars),reply_to_commentto answer a comment,edit_comment/delete_commenton the user's own comments,like_post {account_id, target_id, reaction?, unlike?}for a post or comment.reaction: like (default), celebrate, support, love, insightful, funny; send a new one to change it. If LinkedIn refuses a non-Like reaction, offer a plain like. - The app cannot list the user's posts, so for a post it did not just publish, ask for its link (Copy link to post) and pass the link as the id — never guess an id.
- Answering every comment on a post:
get_post_comments {account_id, post_id}(the post URN or link) → each comment hasid,text,author(a member URN — LinkedIn does not share names),is_mine,reply_count. Skipis_mineones, draft one reply per comment, show the user the list of comment → draft pairs, and only after their yes send each withreply_to_comment {account_id, comment_id: <that id>, message}. Pass a comment'sidaspost_idto read its replies first, so you never answer twice. If LinkedIn refuses the read, say so and ask the user to paste the comments. list_social_accountsshows the member'semailon a profile connected since 2026-10-02 (reconnect an older one to get it).- When a LinkedIn Page account is connected,
list_social_accountsshows it withaccount_type: "page"and its company name aspage_name; posting with thataccount_idpublishes as the company.account_type: "profile"posts as the person. - The preview must say which: "Posting as (LinkedIn Page)" or "Posting as (LinkedIn profile)". If both kinds are connected and the user didn't say, ask.
- Example:
list_social_accounts→ pick the row withaccount_type: "page"→post_to_social {platform: "linkedin", account_id: "<that id>", content: "..."}.
- Keep the
- youtube — caption's first line becomes the title (≤100 chars), the rest the description;
privacy_status:public(default) /unlisted/private; singlemedia_urlonly; upload is slow; 100 uploads/day shared platform-wide — if exhausted, try tomorrow. - tiktok — video =
media_url, photo post =media_urls(2–35), never mixed. Publishing is asynchronous: success means TikTok accepted it, not that it is live.privacy_status:PUBLIC_TO_EVERYONE(default),SELF_ONLY,MUTUAL_FOLLOW_FRIENDS,FOLLOWER_OF_CREATOR."draft": truesends the video or photo post to the creator's TikTok inbox to caption and publish themselves (privacy_statusignored). Photo posts must be JPEG or WebP.- Until the app passes TikTok's audit, direct posts to a PUBLIC account are blocked and every direct post is forced private. Either the user sets their account private (TikTok app → Settings → Privacy) or use
"draft": true(works on any account). Say which one you are suggesting — never just report a failure.
- Until the app passes TikTok's audit, direct posts to a PUBLIC account are blocked and every direct post is forced private. Either the user sets their account private (TikTok app → Settings → Privacy) or use
- twitter (X) — up to 4 images via
media_urls, or one video viamedia_url; the two cannot be mixed and there is no carousel past 4. Noscheduled_time.- X bills the account owner per post, and a post containing a link costs far more than one without. Mention the cost when a post carries a link, and never silently retry: a post rejected on validation will be rejected again, and a post that succeeded has already been charged.
- The character limit depends on the owner's X subscription (280 on a standard account, far more on a premium one), so we do not pre-check it — if X rejects the text for length, report X's own message rather than guessing a number.
- Publish-only for now: recent posts, insights, comments and deletion are not available on X.
- pinterest — these rules apply when a Pinterest account is connected (it only
appears in
list_social_accountswhen Pinterest is enabled on MyClawBots).- An image is required:
media_urlfor one image, ormedia_urlswith 2–5 images for a carousel. JPEG or PNG only. Noscheduled_time, no text-only Pin. - Video only when enabled. Video Pins exist only when the
post_to_socialtool description says so; otherwise there is No video — offer an image instead. When it is on: ONE video staged withcreate_upload_url(MP4 or MOV, 4 s–15 min) asmedia_url, plus a cover —cover_image_url(a staged JPEG/PNG, the safer choice) orcover_frame_seconds. Never inmedia_urls: carousels are images only. Processing takes a while; if the result says Pinterest is still processing, nothing was published — send the same post again a minute later (the upload is reused). contentis the Pin description (≤800 characters). Optional:title(≤100),link— the page the Pin opens,http(s)://only (≤2048) — andalt_text(≤500) describing the image. Writealt_textwhenever you can.- Choose the board. Each Pinterest account in
list_social_accountscarriesboards(id,name). Pass oneidasboard_id. If there is more than one board and the user did not say which, ask — never guess. Only public boards the user owns are listed; secret and group boards cannot be pinned to. With exactly one board you may omitboard_id; the result'snoticenames the board used. If the row hasboards_errorinstead, relay it. - No boards yet. For now the user's existing Pinterest boards do not appear
here, so a new connection usually shows
boards: []. Ask whether to create one board for their Pins (visible only to them). Only after an explicit yes, post withboard_name: "<name they chose>"instead ofboard_id. MyClawBots creates at most one such board; after that, use itsboard_id. Never sendboard_namewithout that yes, and never withboard_id. - AI-generated images: when the image was generated or edited with AI, send
ai_disclosures: ["AI_MODIFIED"]; add"SYNTHETIC_PERFORMER"when it shows an AI-generated person. Say in the preview that the Pin will be labelled as AI content. - One Pin, one confirmation. Preview each Pin — board, title, description, link, image — and get an explicit yes for that Pin. No bulk or automatic pinning, no "pin these 20 for me" in one go: Pinterest's developer guidelines forbid actions taken without considering each one.
- Relay the
noticeword for word. For now every successful Pin comes back with a notice that it is visible only to the user. Never call a Pin public, and do not promise a link:permalinkcan benull. - Don't talk about access levels. Never mention Pinterest "Trial" or "Standard" access, sandboxes, test environments, reviews or approvals to the user, and never tell them to upgrade or contact support about it. Say only what they can do now.
- Editing (only when the
edit_postandedit_boardtools are listed):edit_postchanges a Pin's description (content),title,link,alt_textorai_disclosures, or moves it to another of the user's boards (board_id);clear_fieldsempties a field. The image or video cannot be changed — that means delete and post again.edit_boardrenames a board or changes its description (not its privacy). Both overwrite with no undo: show the user the old and new text and get an explicit yes first, exactly as for a new Pin. If the result says editing Pins isn't available, relay that (offer delete-and-repost if they want); do not retry. - If the error says Pinterest did not accept a carousel, it means exactly that: nothing was published. Offer a single image instead; do not retry the carousel.
- Example:
list_social_accounts→ pick the Pinterest row and one of itsboards→ preview → on "yes":post_to_social {platform: "pinterest", account_id: "<id>", board_id: "<board id>", title: "Autumn table ideas", content: "Five ways to…", link: "https://example.com/autumn", alt_text: "A wooden table set with…", media_url: "<staged public_url>"}.
- An image is required:
Reads & research
- Recent posts, post insights, comments and account insights: Facebook, Instagram and Threads (deletion too, within each platform's limits). TikTok supports recent posts + profile counters. X is publish-only. LinkedIn profiles can post, delete their own posts, read a post's comments and replies, comment, react and like — but not list their posts or read insights (LinkedIn reserves those for select partners); say so rather than trying.
- A connected YouTube channel supports
get_recent_posts(latest uploads, including private and unlisted, with views, likes and comments),get_post_insightsandget_post_commentson the channel's own videos, andget_account_insights(subscribers, total views, video count — lifetime totals). Watch time, retention and click-through rate are not available: say so and point to YouTube Studio; never report them as zero. Replying to, hiding or deleting YouTube comments and deleting videos are not supported. - A connected LinkedIn Page supports
get_recent_posts(no counts) anddelete_poston its own posts; insights and comments are not available for it. - A connected Pinterest account supports
get_recent_posts(no counts),get_post_insights,get_account_insightsanddelete_post— on the user's own Pins only (andedit_post/edit_boardwhen those tools are listed). Pinterest has no comments API: the comment tools refuse it. Pin metrics can come backnull— report them as unavailable, never as zero. Detailed period analytics may not be available; the result says so in anotice— relay it. - Answering comments — read with
get_post_comments, then reply. Facebook and Instagram usereply_to_commentwith the comment'sid; Threads usespost_to_socialwithreply_to_id. Each comment carries its ownreplies, so check them before answering — the thread may already have your reply, or the person's follow-up to it. Show the user the comment and your draft and get their go-ahead first: this posts publicly under their name. - Moderating —
hide_commentremoves a comment for everyone but its author and can be undone;delete_commentcannot. Reach for hiding unless the user explicitly asked to delete, and quote the comment text before either. - Instagram may return comments with no author. Meta withholds the username
on comments the connected account does not own, and does not error. Facebook
returns the author; Instagram often does not. The response carries a
notewhen it happens — relay it and stop there. Do not diagnose a cause: a connection whose token demonstrably holds the comment permission still hits this, so "you need to reconnect" is a guess, not an answer. The comments are real, not anonymous, and nothing is broken on our side. - Public Page search/compare (competitor research): Facebook only; the
account_idmerely authorises the lookup. A hidden counter comes backnull, not0— report "not public", never zero. - Facebook Page insights can come back partial. Meta retires Page metrics on
a schedule, and the response names the dropped ones in a
note. Report those as unavailable — never as zero, and never silently omit them. - When asked to optimise content: check account insights → review recent posts → read comments on top performers → recommend posting times → offer to check performance the next day.
Truthfulness
A successful post may include a notice field — relay it to the user
verbatim. Never describe a post as "published publicly" without positive
evidence: TikTok in particular may force a post private, and the user will
otherwise hunt for a post they cannot see.
Rules
list_social_accountsfirst — every call needs the rightaccount_id- Preview + explicit "Shall I post this?" before publishing, quoting or reposting; confirm before deleting (irreversible)
- Stage media once, reuse the URL across platforms
- Report failures clearly; relay every
notice; after posting, offer to check insights later (FB/IG, Pinterest)