Install
openclaw skills install @azeemkafridi/bulkpublish-using-bulkpublishSchedule, publish and analyse social media posts across 15 networks through BulkPublish. Use whenever the user wants to draft, queue, cross-post, retry or measure social content, or asks about their channels, queue or posting quota.
openclaw skills install @azeemkafridi/bulkpublish-using-bulkpublishBulkPublish schedules and publishes posts to 15 social networks from one place, and reads the numbers back. This skill covers the tools available over MCP, the shape of a post, what each network accepts, and the mistakes that produce a rejected request.
Connect the hosted server at mcp.bulkpublish.com/mcp over Streamable HTTP.
Sign-in is OAuth, so there is no key to paste. To run the server locally
instead, start @bulkpublish/mcp-server over stdio with an API key from
Settings then Developer in the BULKPUBLISH_API_KEY environment variable.
These are not style preferences. Getting them wrong costs the user real posts on real accounts.
create_post with status: "draft" is the safe default and is
what the tool does when status is omitted. publish_post sends
immediately, to a live audience, and cannot be undone from here.list_channels and use what it
returns. A guessed id either fails or posts to the wrong account.delete_post on a published post does not
retract what already went out.platformContent for per-channel copy. A 3,000-character LinkedIn post
truncated into a tweet reads like a mistake, because it is one.The hosted server exposes 20 tools, or 27 where it serves the Inbox and comment tools. The local package exposes 79, adding labels, hashtag groups, templates, calendar notes, review and client-connect links, channel sets, schedules, RSS autopost and quota.
Posts
create_post create a draft or a scheduled post
update_post change content, timing, channels or media
get_post one post, with its per-channel results
list_posts filter by status, channel, label, date, approval, assignee
publish_post send now (irreversible)
retry_post retry failed destinations
delete_post remove a post
get_post_metrics per-post numbers once published
get_queue_slot the next free time in the queue
Channels
list_channels connected accounts, with ids, platform and health
Media
upload_media from a public URL or an absolute local path
create_media_upload start a direct upload
finalize_media_upload finish one
list_media the library
Analytics
get_analytics totals and per-platform breakdown for a date range
Inbox and comments (see the manage-inbox skill before sending anything)
list_conversations DMs, reviews and comment threads
get_conversation messages in one conversation
reply_to_conversation send a DM or review reply now (cannot be unsent)
update_conversation mark read, archive, snooze, assign
list_post_comments comments on a published post
reply_to_comment public reply under a comment (cannot be unsent)
moderate_comment like, hide or delete a comment
The Inbox is part of the Pro and Business plans; comment replies are on every plan except Free, and reading a post's comments works on every plan. A channel connected before message or comment access existed may need reconnecting, and some networks do not allow DMs or replies yet. Always show the exact text and recipient and get a yes before any reply or deletion.
Interactive panels (render in hosts that support MCP Apps)
compose_post view_posts view_channels view_media view_analytics
content post text; optional for media-only posts
channels [{ channelId, platform }] from list_channels
status "draft" (default) or "scheduled"
scheduledAt ISO 8601; required when status is "scheduled"
timezone IANA zone, e.g. "America/New_York"; defaults to UTC
mediaFileIds [ids] from upload_media
labels [label ids]
platformContent per-platform text overrides
platformSpecific per-platform options
postTypeOverrides per-platform post type, e.g. reel instead of feed_photo
postFormat "post" (default), "video", "reel", "story", "carousel", "thread"
threadParts [{ content, mediaFileIds }]; required when postFormat is "thread", minimum 2
requestApproval true holds a scheduled post for a teammate to approve
publishWhenApproved true publishes it the moment it is approved, even past its time
(set it for "post now, once approved", not for a chosen time)
Notes that catch people out:
labels. The filter on
list_posts is labelId. They are different names on purpose.feed_photo. For video, set postTypeOverrides
to reel or feed_video, or the post is rejected.get_queue_slot reads only timezone, position and excludePostId. It
does not take a channel or a date. position is "next" for the first free
slot or "end" for the bottom of the queue.
The normal flow is: get a slot, create the post as a draft with that time,
show it to the user, then update it to scheduled once they agree.
draft not going anywhere yet
scheduled waiting for its time
publishing going out now
published all destinations succeeded
partial some destinations succeeded, some failed
failed no destination succeeded
processing the network is still handling it (video encoding, for example)
partial and processing are real states and are easy to forget. A post
sitting in processing is not stuck; a partial post needs retry_post,
which retries only the destinations that failed.
Approval is separate from status. approvalStatus is none, pending,
approved or rejected. A scheduled post whose approval is pending or
rejected will not publish. Approved more than 15 minutes after its time, a
post goes back to draft for a new time unless it has publishWhenApproved or the
approver passes whenLate: "publish" to approve_post ("hold" forces draft).
| Network | Characters | Threads | Post types |
|---|---|---|---|
| X | 280 | yes | tweet |
| 2,200 | no | feed_photo, feed_video, reel, story, carousel | |
| 63,206 | no | post, reel, story | |
| 3,000 | no | post, multi_image, pdf_carousel, article | |
| Threads | 500 | yes | text, image, video, carousel |
| Bluesky | 300 | yes | post |
| Mastodon | 500 | yes | post |
| TikTok | 2,200 | no | video, photo_slideshow |
| YouTube | 5,000 | no | video, short |
| 500 | no | pin, video_pin, carousel | |
| Google Business | 1,500 | no | standard, event, offer |
| Discord | 2,000 | no | post |
| Telegram | 4,096 | no | post |
| Tumblr | 32,768 | no | post |
| Snapchat | 160 | no | story, saved_story, spotlight |
The platform key for Google Business is gmb, not google_business.
upload_media takes either a public url or an absolute filePath, not
both. Attach the returned id through mediaFileIds.
Uploaded media is kept by default. deleteMediaAfterPublish defaults to
false, so files stay in the library after their post goes out.
get_analytics takes a required from and to in ISO date form, plus
optional channelIds, platforms, labelIds, postFormat, mediaType and
compare. Set compare: true to get the previous equal-length window back
alongside the current one. It is only available for windows of 15 days or
fewer, so it answers "is this week better than last week", not month over
month.
Link clicks are reported separately from platform clicks. Do not add the two together: one visit can appear in both.
-1 means unlimited, 0 means not available on that plan.
| Free | Pro | Business | |
|---|---|---|---|
| Channels | 3 | 30 | 75 |
| Accounts per network | 1 | 2 | 5 |
| Posts per day | 3 | 30 | unlimited |
| Posts per month | 30 | 900 | unlimited |
| Scheduled posts pending | 10 | unlimited | unlimited |
| Media storage | 100 MB | 2 GB | 10 GB |
| API requests per day | 30 | 5,000 | 50,000 |
| API keys | 1 | 5 | 10 |
| Repeating schedules | 0 | 10 | unlimited |
| Labels | 10 | 50 | unlimited |
| Team members | 1 | 3 | 10 |
| RSS feeds | 1 | 10 | 50 |
| RSS auto-publish | no | yes | yes |
Daily limits reset at midnight UTC. Storage is cumulative, so deleting old media frees it.
cronExpression or content on a repeating schedule. The fields are
frequency, timeOfDay, dayOfWeek, dayOfMonth and contentTemplate.
frequency is daily, weekly, biweekly or monthly.labelIds in a create or update body. It is labels there.get_queue_slot.google_business instead of gmb.partial as success, or as total failure. It is neither.scheduledAt, or sending scheduledAt without setting
status to scheduled.approvalStatus: "pending" will go out. It will not.Post one thing, safely
list_channelsget_queue_slot with the user's timezonecreate_post as a draft, with platformContent per networkupdate_post to scheduled once they agreeTurn an article into a set of posts
list_channelscreate_post with all channels and platformContentFix a bad day
list_posts with status: "failed" and a recent from dateget_post on each to see which destinations failed and whyretry_post only where the cause looks temporaryReport on the month
get_analytics for the range (compare works only for 15 days or fewer)list_posts with status: "published" over the same rangeget_post_metrics on the strongest few