Social Media Action

One node, every supported social platform. Picks the platform from a registry and dispatches universal operations (post, reply, repost, like, search, …) plus platform-specific extras. Use the crosspost sentinel to fan out to multiple platforms in one node, or pair with the Trigger node to listen for new posts across platforms in a single trigger.

Supported providers

PlatformCredentialAuth modelNotes
Blueskybluesky_app_passwordApp passwordNo platform-side review. Cardyb-backed link card embeds for richer posts.
X (Twitter)twitter_oauth2OAuth 2.0 + PKCE280-weighted-char cap with smart-truncate / auto-thread escape hatches (see below).
Mastodonmastodon_oauth2OAuth 2.0 (per-instance app reg)Per-instance app registered dynamically on first connect. 500-char cap.
LinkedInlinkedin_oauth2OAuth 2.0 (UGC API)Personal posts only in V1. postWithMedia + searchPosts gated.
Threadsthreads_oauth2Meta Graph + long-lived tokenTwo-step token (short → long-lived 60d). Daily refresh cron. Single + carousel media (10 max).
Instagraminstagram_oauth2Facebook Login + IG GraphBusiness / Creator account linked to a FB Page. 2200-char captions. Single + carousel up to 10 (image + video). All posts require media — no text-only.
Facebook Pagesfacebook_oauth2Facebook Login + Page tokensPosts to a Page you administer (V1 picks first page). Text + single image; multi-image albums + video deferred.
Meta app setup (Instagram + Facebook): ONE app at developers.facebook.com/apps covers both platforms. Add products: Facebook Login for Business + Instagram Graph API. Set env vars META_OAUTH_CLIENT_ID + META_OAUTH_CLIENT_SECRET. Add valid OAuth redirect URIs: {API_URL}/api/credentials/instagram/oauth/callback + {API_URL}/api/credentials/facebook/oauth/callback.

Post

Post is the one operation for publishing. Give it a text, media (images or videos), or both — at least one — and it posts the right way on each network: text only, text with media, or media only. The form shows a live line, Will post as…, so you can see which one it will be, or what is missing.

  • Add a text or a media file — with neither, there is nothing to publish.
  • Instagram always needs media. A crosspost to Instagram without media fails on Instagram's own row; the other networks still publish.
  • Post with content warning — a checkbox, with the warning text, that hides the post behind a banner until the reader expands it. It only shows on networks that have one: Mastodon today (media posts included). In a crosspost it applies to Mastodon only and the other networks publish without it; on a single-network node of a network without it, the post is rejected rather than published silently without the warning. Bluesky's content labels are a fixed list, not a free-text warning, so they stay their own operation (postWithLabels).
ℹNodes saved before this with Post text, Post with media or Post with content warning keep working exactly as they did: they show up as Post, are not rewritten when you open them, and switch to post the first time you change something and it saves. Saved “Block these tool calls” lists and MCP clients that name the old tools (bluesky_postText…) keep applying: blocking <network>_post blocks the old names, and blocking an old name blocks <network>_post. Replying (replyToPost), quoting (quoteTweet) and labels (postWithLabels) are separate operations and are never part of Post.

In AI toolkit mode the model sees one publishing tool per network, <network>_post, with text and media (and, on Mastodon, contentWarning + spoilerText). From the API or Hermes, build the node with operation: "post".


Operation gates

Most universal operations work on every provider. A few are gated by the platform itself — either behind a paid partnership tier or behind a scope that needs review by the platform's team. The provider throws a clear error when you try to run a gated op so the AI Toolkit and the canvas surface the gate without a silent failure.

searchPosts

Recent-search requires special access on LinkedIn and Threads. The other three providers (Bluesky, X, Mastodon) ship it under their regular auth.

PlatformsearchPostsWhat unlocks it
BlueskyAvailablePublic AT-Protocol search.
X (Twitter)AvailableFree tier ≈ 100 reads/day. Basic ($200/mo) lifts the cap. Hit /2/tweets/search/recent internally.
MastodonAvailablePer-instance search via the standard API.
LinkedInGatedRequires LinkedIn Marketing Developer Platform (MDP) partnership. Apply at linkedin.com/developers. Approval can take weeks to months and is granted to verified marketing/analytics use cases — not casual integrations.
ThreadsGatedRequires the threads_keyword_search scope, which is a premium scope under Meta review. Only granted for approved business / research use cases. Apply via the Threads / Meta App Review flow.
InstagramGatedHashtag search via /ig_hashtag_search needs additional Meta App Review + heavy rate-limit gates. Mentions surface is Webhooks-only push, not pull — different shape entirely. Both deferred until customer demand.
Facebook PagesGatedPage-graph search needs Pages Public Content Access permission, which goes through Meta App Review for any non-developer use. Mentions same gate.
⚠If you try to run searchPosts on a gated platform without the partnership, the provider throws an explicit error explaining what to apply for — not a silent empty result.

Media in a Post

All providers ship media uploads (the Post operation sends them the same way the former postWithMedia did). Per-platform notes:

  • Bluesky — up to 4 images, 1 MB each, blob upload at post time.
  • X (Twitter) — up to 4 images, 5 MB each, single-shot upload via /2/media/upload.
  • Mastodon — up to 4 attachments via the per-instance /api/v2/media webhook.
  • LinkedIn — IMAGE only in V1, up to 9 images per UGC post, 5 MB each. Uses the 3-step registerUpload dance (register → PUT binary → reference asset URN in the UGC post). VIDEO support deferred — throws a clear early error when a video URL is detected so users aren't left waiting on a confusing LinkedIn rejection.
  • Threads — async container + publish flow with status polling. Single image/video, or CAROUSEL up to 10 mixed image/video children.
  • Instagram — same container + publish flow as Threads. Single image/video or CAROUSEL (10 mixed). All posts require media; no text-only. 2200-char captions.
  • Facebook Pages — V1 ships text + single image only. Multi-image albums (create-then-publish dance) + video (chunked resumable upload) deferred to a follow-up.

X (Twitter) long-text strategies

X enforces a 280-weighted-character cap on Free / Basic / Pro tiers (URLs count as 23 after t.co shortening; emoji as 2). The default rejects posts that exceed the cap at validation time. Switch the “When text exceeds 280 chars” selector under any text-posting op (Post, replyToPost, quoteTweet) to opt into one of two escape hatches:

  • Smart truncate — trims your text at the last word boundary, appends an ellipsis, and (if you set truncateAppendUrl) reserves room for a permalink at the end. URL is counted as 23 weighted chars regardless of its raw length, so even a 200-char URL only steals 23 from your budget.
  • Auto-thread — splits the text into a chain of replies, posting each ≤ 280-weighted chunk via in_reply_to_tweet_id. Cuts on paragraph (\n\n) → sentence (. ! ?) → word boundaries in that priority order. With threadNumbering (default ON) each chunk gets a (i/N) suffix so readers see the order. The lead tweet's id surfaces at the top of the output (postId, url) for back-compat; the full chain lives in posts[] with _meta.iterable: true for downstream loops.
jsonSample auto-thread output
{
  "platform": "twitter",
  "postedAt": "2026-05-10T14:23:11.582Z",
  "postId": "1791000000000000001",          // lead tweet
  "url": "https://x.com/i/status/1791000000000000001",
  "text": "First chunk … (1/3)",
  "threadCount": 3,
  "_meta": { "iterable": true, "iterateField": "posts", "count": 3 },
  "posts": [
    { "threadIndex": 1, "postId": "1791000000000000001", "url": "...", "text": "First chunk … (1/3)" },
    { "threadIndex": 2, "postId": "1791000000000000002", "url": "...", "text": "Second chunk … (2/3)" },
    { "threadIndex": 3, "postId": "1791000000000000003", "url": "...", "text": "Third chunk (3/3)" }
  ]
}
ℹFor a Post with media + auto-thread, media attaches to the first tweet only — subsequent reply chunks are text-only. Matches what readers expect when scrolling a thread on X.

  • Crosspost — set platform: "crosspost" and supply a credentialIds map + operationConfig.targetPlatforms[] to fan out a single text/media post to N platforms in parallel. Each result lands in output.results[] with per-platform success + error fields so downstream nodes can iterate. Each network posts the right way for what it was given (see Post above). To give a network its own wording — a shorter text for X, say — set operationConfig.textByPlatform to { "twitter": "…" } (templates allowed): a network with an entry gets its own text, a network without one — or with a blank one — gets the shared text, never another network's. Nothing is cut and nothing is split into a thread: a text that does not fit fails on its own network and the others still publish. Each result row says which text it used in textSource (own or shared).
  • Trigger — to listen for new posts across multiple platforms (Bluesky, X, Mastodon today) in a single trigger, see the multi-platform poll configuration.