PostKing Social
Handles the social-post lifecycle on PostKing: generating AI drafts or saving manual content, approving/scheduling them, running the recurring Smart Week cadence or a one-off bulk fill, repurposing existing content into new posts, connecting/disconnecting social accounts, attaching visuals, managing the brand's asset library, and configuring per-platform social media rules. Requires an active brand — use the postking skill's brand-pick flow first if one isn't set.
When to Use
Use this skill when the user wants to draft, approve, schedule, or reschedule a social post; paste/save finished copy without AI generation; bulk-fill a date range with posts; set up or run a weekly/recurring posting cadence; turn a URL, blog article, or piece of text into platform posts; connect or disconnect a social account; attach or change a post's image/card/quote/carousel; upload, tag, or search the brand's asset library; edit a platform's content/tone rules; or browse trending posts for hook inspiration, extract them into reusable templates, or reuse a saved template.
When NOT to Use
- Blog articles (writing, publishing, WordPress/Medium/Substack) →
postking-blog. - Landing pages or side pages →
postking-landing-pages. - Multi-channel campaign briefs and strategy (Storylines) →
postking-storylines. - Subreddit-specific matching or native Reddit rewrites →
postking-reddit. - Managing saved voice profiles themselves, or a standalone de-slop/AI-detection pass →
postking-brand-voice. - No active brand yet, or first-time social account connection →
postking-getting-started.
Minimal tool subset
list_brands,set_active_brand— brand context.check_social_accounts,generate_connect_link,disconnect_social_account— confirm/connect/disconnect platforms before posting.generate_post,create_post,approve_post,schedule_post,reschedule_post,cancel_post,delete_post,get_post,list_posts,get_calendar— the core post lifecycle.generate_bulk_posts— one-off AI fill across a date range (not the recurring cadence below).get_weekly_schedule,set_weekly_schedule,enable_weekly_schedule,disable_weekly_schedule,delete_weekly_schedule,run_weekly_schedule_day— recurring cadence (Smart Week).repurpose_content— turn a URL/text/blog/post into new social drafts.generate_post_visual_options,pick_post_visual,regenerate_post_visual,clear_post_visual,generate_post_carousel— visuals.search_stock_images,search_web_images,suggest_assets_for_post— visual discovery, feeding into the picks above.list_assets,view_asset,tag_asset,list_asset_tags,delete_asset,import_asset_from_url,import_assets_csv,upload_asset(+ chunkedupload_asset_begin/upload_asset_chunk/upload_asset_finish/upload_asset_abort) — brand asset library housekeeping.get_social_media_rules,set_social_media_rules— per-platform content/tone rules.trends_list— trending-post hooks.template_list,template_create,template_update,template_delete,template_extract,template_pick— reusable post templates (extract from a trend, or hand-author, then reuse).
Procedure
Connect, verify, and disconnect social accounts
check_social_accounts({ brandId? })— list connected/disconnected platforms before posting; run this first in any new session.generate_connect_link({ brandId?, platform? })— get a browser URL for OAuth; share it with the user to complete the connection. Omitplatformfor a generic connect link covering all supported platforms.disconnect_social_account({ accountId, brandId? })— remove a connected account by its account ID (fromcheck_social_accounts). This does not delete past posts.
Generate, approve, and schedule a post
generate_post({ platform, variations?, theme, voice?, brandId?, language? })— ALWAYS passthemewith the specific topic/angle/tone the user wants; omitting it produces a random brand theme. This call polls until generation completes and already saves the result to a singlepostId(variation 1 is the primary, saved content — all variations live under that onepostId, never call it again to "get the others").language(en/es/pt-BR/de/fr/cs) overrides the brand'scontentLanguagefor this one call — omit it to use the brand default.- Show the variation(s) to the user; ask which one (if more than one) and what time.
approve_post({ postId, scheduledAt, timezone? })—scheduledAtis a future ISO 8601 datetime. This is the free-tier choke point. There is novariationparam on this MCP tool — via MCP you always approve the post's current saved content (variation 1). Picking a different generated variation to schedule is a CLI-only affordance (pking posts approve <id> --variation N).get_calendar({ days })— confirm it appears in the upcoming schedule.
Save manual content instead of generating
Use create_post — not generate_post — whenever the user hands you finished copy (pasted text, content drafted elsewhere) and wants it saved as-is, with no AI drafting and no credit cost.
check_social_accounts({ brandId? })— confirm the target platform(s) are connected.create_post({ platforms, content, scheduledAt?, brandId? })—platformsis an array (x/linkedin/instagram/threads/facebook), so one call can save the samecontentto multiple platforms at once, unlikegenerate_post's single-platformparam. PassscheduledAt(future ISO 8601 UTC) to schedule immediately on save; omit it to save as an unscheduled draft (postType: "queue") and approve later. Returns one post per platform, each with its ownidandeditInVisualEditorlink.- If saved as a draft,
approve_post({ postId, scheduledAt, timezone? })per post when ready to schedule. get_calendar({})— confirm.
Bulk-generate posts across a date range
Use generate_bulk_posts for a one-off fill of many posts over a date range — this is distinct from the recurring Smart Week cadence below (no dayConfigs, doesn't repeat).
check_social_accounts({ brandId? })— confirm the platform is connected.generate_bulk_posts({ platform, days, frequency?, postsPerDay?, times?, voice?, language?, brandId? })—days(1–90) is how many days to fill;frequencyisdaily|every_other|every_third|weekdays(defaultdaily);postsPerDay(1–5, default 1);timesis a comma-separated string of posting times, e.g."09:00,14:00"(default"09:00"). Runs in the background and returns{ operationId, totalQueued, pollUrl }.get_job({ pollUrl, wait: true })— poll untilstateiscompleted/partially_failed/failed; do not callgenerate_bulk_postsagain while it's pending.list_posts({ status: "created" })orget_calendar({ days })to review the generated drafts, thenapprove_posteach one that's ready.
Plan a content week (Smart Week)
get_weekly_schedule({ brandId? })— view the current cadence, if any.set_weekly_schedule({ enabled?, leadTimeDays?, timezone?, voiceProfileId?, dayConfigs, brandId? })wheredayConfigsis an array of{ dayOfWeek (0=Sun…6=Sat), mediums: [{ medium, postsPerDay }] }. There are nomonday/tuesday/… params and the flag isenabled, notenable— those are CLI-only sugar.enable_weekly_schedule({ brandId? })/disable_weekly_schedule({ brandId? })— toggle an already-created schedule on/off without resendingdayConfigs.disable_weekly_schedulepauses (config is kept); usedelete_weekly_schedule({ confirm: true, brandId? })instead to remove the schedule entirely.run_weekly_schedule_day({ date, brandId? })—dateisYYYY-MM-DD. This is the Smart Week engine; call it per day, or leave the scheduleenabledto run automatically.list_posts({ status: "created" })→get_post({ postId })to review each draft.approve_post({ postId, scheduledAt, timezone? })per draft.get_calendar({ days: 7 })— verify the final week.
Repurpose a URL, blog, or text into multi-platform posts
check_social_accounts({ brandId? })— confirm which platforms are connected.repurpose_content({ brandId, sourceType: "url", sourceUrl, targetType: "social", targetPlatforms: ["linkedin", "x"] })— PostKing crawls the URL internally; do not fetch it yourself.- Text input:
sourceType: "text", sourceContent: "<text>". - From an existing blog:
sourceType: "blog", sourceBlogId: <articleId>. - From an existing post:
sourceType: "social_post", sourcePostId: <postId>. - Optional:
angle,variations,voiceProfileIds(single ID applies to all platforms, or["x:id1","linkedin:id2"]per platform).
- Text input:
get_post({ postId })per generated post — inspect before scheduling.approve_post({ postId, scheduledAt })— free-tier choke point.get_calendar({})— confirm.
Pick a visual for a post
generate_post_visual_options({ postId, platform })— always run this first. Relay the numbered options to the user, especially the recommended one — the returned params ARE the spec, no need to render anything yourself.- If none of the prepared options fit, widen the search before picking:
suggest_assets_for_post({ context, limit?, brandId? })— AI-matched picks from the brand's own asset library for the post's content/topic (context). Try this first — it surfaces on-brand assets already in the library.search_stock_images({ query, platform?, detail?, brandId? })— licensed stock photo/video libraries; passplatform(e.g."linkedin") to bias dimensions.search_web_images({ query, maxResults?, gl?, hl?, openLicensedOnly?, brandId?, detail? })— broader Google Images search across the whole web (not just stock libraries).openLicensedOnlyis a best-effort filter, not a legal guarantee — still verify usage rights before publishing.- A
search_stock_images/search_web_imagesresult isn't directly usable bypick_post_visual— firstimport_asset_from_url({ url, name?, tags?, assetType?, brandId? })to add it to the library (passassetType: "google-image"for web-search results, to record provenance), then reference the returnedassetIdinpick_post_visual.
pick_post_visual({ postId, platform, style?, variant?, assetId?, slot?, kind? })— pass the chosen option's fields verbatim from step 1's response, or the imported asset'sassetId. There is no numericpickparam on the MCP tool (that's CLI-only sugar); you must supply one ofstyle,assetId, orslot, or the tool errors. Passkind: "quote"(or"card"/"photo"/etc.) when the chosen option is a quote/card template style — omittingkinddefaults to"card"and rejects quote styles.- "Show me more" →
regenerate_post_visual({ postId, platform, loadExternal: true })pulls additional stock results, then re-rungenerate_post_visual_options. clear_post_visual({ postId, platform })to remove a pick. For carousels: set cards first (set_post_cards/edit_post_card), thengenerate_post_carousel({ postId }).
Manage the asset library
list_assets({ type?, tags?, search?, limit?, detail?, brandId? })— browse the library;typeisIMAGE/DOCUMENT/VIDEO/LINK/LOTTIE.view_asset({ assetId, detail?, brandId? })for one asset's full detail.- Add assets:
import_asset_from_url({ url, name?, tags?, assetType?, brandId? })— one public URL (e.g. asearch_web_images/search_stock_imagesresult).import_assets_csv({ urls, detail?, brandId? })— batch-import up to 50 public URLs in one call.upload_asset({ filePath?, fileBase64?, fileName?, mimeType?, name?, description?, tags?, brandId? })— upload a local file or inline base64; provide exactly one offilePath/fileBase64. PreferfilePathfor local files — the server reads and base64-encodes it server-side, which avoids the truncation risk below.- Chunked upload (
upload_asset_begin→upload_asset_chunk×N →upload_asset_finish, orupload_asset_abortto cancel) — use this instead ofupload_asset'sfileBase64only for large files sent over the remote/HTTP transport with no local file path available (e.g. a generated or fetched file the agent holds only as base64): a single large base64 string can get truncated crossing the LLM→tool_call boundary on that transport.upload_asset_beginreturnsuploadId+ a recommended chunk size (~16000 chars); send chunks in order starting at index 0 with no gaps;upload_asset_finishverifies size/sha256 (if provided at_begin) before saving. Uploads expire after 10 minutes of inactivity.
tag_asset({ assetId, addTags?, removeTags?, brandId? })— add/remove tags (at least one required).list_asset_tags({ brandId? })— see every tag in use across the library.delete_asset({ assetId, confirm: true, brandId? })— soft-delete;confirm: trueis required.
Manage per-platform social media rules
These are the same content/structure/engagement/visual-strategy rules editable in dashboard Settings → Social media rules — they steer generate_post's tone and structure, not a specific post.
get_social_media_rules({ platform?, brandId? })—platformislinkedin/x/twitter/facebook/instagram/threads/general; omit to return all 6. Always call this beforeset_social_media_rulesso you only change fields the user actually asked to change.set_social_media_rules({ platform, rules, replace?, brandId? })—platform(singular, required) is the one platform being updated.rulesaccepts nested groups (content,structure,engagement,visualStrategy, each with free-text sub-fields likepostLength,hook,hashtags) plus flat arrays (secrets,guidelines,contentTypes,avoid,principles). By default this merges provided fields into the existing ruleset — arrays you provide replace the old array, everything else is preserved. Passreplace: trueto overwrite the entire platform ruleset instead.
Trends as hooks, and turning one into a reusable template
trends_list is account/niche-scoped (no brandId, works before any brand is picked); every template_* tool below is brand-scoped.
trends_list({ niche?, platform?, days?, limit?, sort? })— niches:ai-saas,marketing,web3(defaultai-saas); platform currently onlyx. The crawler runs every 3 days; default window is 3 days. Each result includes a hook/template/pattern/virality-reason deconstruction. Feed a result's text intogenerate_post'sthemeorrepurpose_content'sanglefor quick hook inspiration, or turn it into a saved, reusable template with the steps below.- To turn a trending (or any pasted) post into a template:
template_extract({ postText, save?, brandId? })— AI deconstructs the post text into a reusable template (title/body-with-placeholders/example/category/pattern). Synchronous, no polling. Passsave: trueto persist it directly in the same call (skip step 3); omitsaveto just preview the extraction and decide first. - To persist an extraction that wasn't auto-saved, or to hand-author templates directly:
template_create({ title, body, example?, category?, pattern?, platforms?, isFavorite?, brandId? })for one template, or pass the bulktemplates: [...]array instead (bulk takes precedence if both are given). - To reuse an existing saved template instead of extracting a new one:
template_list({ category?, detail?, brandId? })(ordered favorite-first, then most-used) to fetch candidates — usedetail: "full"sincetemplate_pickneeds each candidate'sid/title/body/category— thentemplate_pick({ count, theme, templates, brandId? })to have AI re-rank and return the best-fittingtemplateIds.template_pickdoes not read the library itself — it only scores thetemplatesarray you pass in, sotemplate_listmust run first. template_update({ templateId, title?, body?, example?, category?, pattern?, platforms?, isFavorite?, brandId? })/template_delete({ templateId, brandId? })(irreversible) — template housekeeping.- To turn a chosen template into an actual post: fill its
bodyplaceholders (e.g.[HOOK] … [CTA]) for the topic at hand, thencreate_post(finished copy, no AI) orgenerate_post(let AI draft fromtheme) as in the sections above.
CLI fast path
| Goal | Command |
|---|---|
| Pick active brand | pking brand list / pking brand set <brandId> |
| Generate a post | pking posts generate --platform linkedin --variations 3 --theme "..." |
| Approve & schedule (pick a variation) | pking posts approve <postId> --schedule 2026-08-01T14:00:00Z --variation 2 --timezone America/New_York |
| View calendar | pking posts calendar --days 7 |
| Plan a content week | pking weekly-schedule set --monday "linkedin:1,x:1" --timezone America/New_York --enable then pking weekly-schedule run-day --date <YYYY-MM-DD> |
| Repurpose a URL | pking repurpose --source-type url --source-url <url> --target-type social --target-platforms linkedin,x |
| Visual options / pick | pking visuals options <postId> --platform <p> → pking visuals pick <postId> --platform <p> --pick <N> |
| Trends | pking trends list --niche ai-saas --days 3 --json |
For the full command catalog, use the postking skill's references/commands.md, or run pking --help / pking <group> --help.
Pitfalls
approve_posthas novariationparam via MCP. If the user wants a specific generated variation scheduled (not variation 1), that's a CLI-only affordance (--variation N); via MCP the only way is to work from the CLI, or use the visual editor link (editInVisualEditor, returned bygenerate_post/get_post) to edit the saved content directly.set_weekly_scheduleneedsdayConfigs, not per-day flags. Passingmonday/tuesday/etc. orenable(instead ofenabled) will fail validation.pick_post_visualneeds an explicitstyle/assetId/slot. There is no index-basedpickparam — always callgenerate_post_visual_optionsfirst and copy its fields.- Omitting
themeongenerate_postgives a random topic. Always pass it when the user wants specific content. - Visuals are never auto-attached.
generate_postprepares visual options but does not attach one — nothing changes untilpick_post_visualis called. create_postvsgenerate_post.create_postsaves the user's own finished content verbatim (no AI, no credits, takesplatformsarray for multi-platform in one call);generate_postdrafts AI content for oneplatformand costs credits. Don't callgenerate_postwhen the user already gave you the copy to post.cancel_postvsdelete_post.cancel_postreverts a scheduled/approved post to draft without deleting it (reversible).delete_postremoves the post permanently regardless of status. Usedelete_postonly when the user actually wants it gone.- Chunked asset upload is not the default. Use
upload_assetwithfilePathfor local files. Reach forupload_asset_begin/upload_asset_chunk/upload_asset_finishonly when uploading from base64 content over the remote/HTTP transport with no local file path — a large singlefileBase64string can get silently truncated crossing that boundary. INSUFFICIENT_CREDITS/FREE_CAP_REACHED— surface the error envelope'scheckoutUrlin one line and stop; don't retry or recap.
Verification
get_credits({ detail: "short" })— confirms auth and balance.check_social_accounts({})— confirms at least one connected platform.list_posts({ status: "created", limit: 1 })— confirms the brand/posts API is reachable.