Mellow Hub
Publish one post to every network you are connected to — Instagram, TikTok, YouTube, X, LinkedIn, Threads, Bluesky, Pinterest and Facebook — through one API call, with each network's own rules checked before anything goes out.
Base URL: https://www.mellow.world/api/hub/v1 MCP endpoint: https://www.mellow.world/mcp
The shape of the work
whoami — what this credential may do, which channels it can reach, and how many posts are left today. Call it first; everything else depends on the answer.
list_channels — the connected accounts. Each has an id beginning spc_; that is what you name in channels.
register_media — if the post has media. Any public https URL works and nothing is copied: the network fetches it when it publishes. For bytes you hold, ask for an upload URL and send the file straight there.
validate_post — free, publishes nothing, and returns every problem at once. Do this before create_post every time. It is the difference between fixing a post once and discovering the rules by breaking them publicly.
create_post — publishes now, or schedules for later.
get_post — the result per channel, with the public link.
Connecting a channel
You cannot do this. Every network makes a person sign in, and Bluesky asks for a handle and an app password typed into Mellow. connect_channel returns a URL and a sentence explaining who has to open it — pass both on and wait. When the person is done, list_channels shows the new channel.
Writing one post for many networks
A post has one caption, one media list, and a list of channels. Where a network genuinely differs, say so in options, keyed by platform:
{
"caption": "The same thought, everywhere.",
"media": ["https://cdn.example.com/clip.mp4"],
"channels": ["spc_instagram", "spc_youtube", "spc_bluesky"],
"options": {
"instagram": { "placement": "reels" },
"youtube": { "title": "The same thought", "privacyStatus": "public" }
},
"perChannel": {
"spc_bluesky": { "caption": "The same thought, in 300 characters." }
}
}
options applies to every channel of that network. perChannel applies to one channel and wins over options — it is how you give Bluesky a shorter caption without shortening the post everywhere.
Publishing twice is the failure to avoid
create_post requires an idempotency key, and it is not decoration. If your request times out you do not know whether it published; retrying with the same key returns the original post instead of making a second one. Use a key derived from the content or the task, not a random value per attempt. Reusing a key for *different* content is refused, because that is a bug worth seeing.
Scheduling
Give scheduledAt an ISO 8601 timestamp to publish later; omit it to publish now. Cancelling works while a post is still scheduled. Once a network has published there is no unpublishing it through any API — Mellow will say so rather than pretend.
What comes back
Each channel succeeds or fails on its own. A post to five networks where YouTube rejects the title and the other four publish comes back as partial, with the failure named on the YouTube channel and public links on the rest. Read targets, not just status.
Authority and limits
A key is a delegation with edges: the channels it may touch, whether it publishes on its own (autopilot) or only prepares a post for a person to approve (review), and how many posts it may make in a day. whoami reports all of them. Everything done with the key is recorded in a journal the owner reads, and revoking a key takes effect immediately.
Reading further
mellow://guide/platforms — every network's limits and required fields.
mellow://guide/errors — what each refusal means and what to do about it.
mellow://guide/recipes — worked examples for the common jobs.
Every network, and what it asks for
These are the rules Mellow checks before it sends anything. A post that passes validate_post has already satisfied everything on this page; what remains is the network's own judgement about the file itself — resolution, duration, aspect ratio — which no API can answer in advance.
Instagram (instagram)
Connecting. A professional (Business or Creator) Instagram account. A personal account cannot publish through any API.
Needed to start the connection:
connection_type — Instagram login is the direct route and the default. Facebook login is for accounts reached through a Page. One of: instagram, facebook.
Caption. Up to 2200 characters.
Media. At least 1, at most 10; image or video. Stories take exactly one item. Reels take one video.
Placement. timeline, reels, stories.
Supports. schedule, carousel, thumbnail, collaborators, mediaTags, metrics.
TikTok (tiktok)
Connecting. A personal or creator TikTok account.
Caption. Up to 2200 characters.
Title. Optional, up to 90 characters. Used as the headline on photo posts.
Media. At least 1, at most 35; image or video. One video, or up to 35 images as a photo post. Videos and images cannot be mixed.
Supports. schedule, draft, carousel, metrics.
TikTok Business (tiktok_business)
Connecting. A TikTok Business account. Use it when you need business analytics; otherwise the ordinary TikTok connection is simpler.
Caption. Up to 2200 characters.
Title. Optional, up to 90 characters.
Media. At least 1, at most 35; image or video. One video, or up to 35 images as a photo post.
Supports. schedule, draft, carousel, thumbnail, metrics.
YouTube (youtube)
Connecting. A Google account with a YouTube channel. Uploads count against the channel's daily API quota.
Caption. Up to 5000 characters. Becomes the video description when no explicit description is given.
Title. Required, up to 100 characters. YouTube refuses an upload without a title.
Media. At least 1, at most 1; video. Exactly one video. A vertical video under three minutes is published as a Short by YouTube itself — there is no separate Shorts endpoint to call.
Required options. title.
Supports. schedule, thumbnail, metrics.
X (x)
Connecting. Posting through the X API depends on the access tier of the app the connection is made with.
Needed to start the connection:
connection_type — OAuth 2.0 is the default. OAuth 1.0a exists for apps still on the older credentials. One of: oauth2, oauth1.
Caption. Up to 280 characters. 280 is the limit for a standard account. A premium account may write far more, but Mellow cannot see the account's tier, so anything longer is reported rather than silently sent.
Media. Optional, at most 4; image or video. Up to four images, or one video. Text on its own is fine.
Supports. schedule, carousel, poll, metrics.
LinkedIn (linkedin)
Connecting. You need admin rights on the company page you want to publish to.
Needed to start the connection:
connection_type — A company page is the route available on the provider's shared credentials. A personal profile needs Mellow's own approved LinkedIn app. One of: organization, personal.
Caption. Up to 3000 characters, and required. LinkedIn publishes nothing without commentary.
Media. Optional, at most 20; image or video. Up to twenty images, or one video.
Supports. schedule, carousel, metrics.
Threads (threads)
Connecting. The Threads account attached to your Instagram login.
Caption. Up to 500 characters.
Media. Optional, at most 20; image or video. Text on its own is fine. Up to twenty items in a carousel.
Placement. timeline, reels.
Supports. schedule, carousel, metrics.
Bluesky (bluesky)
Connecting. Bluesky has no OAuth consent screen: the connection is made with a handle and an app password, so an agent cannot complete this step alone.
Needed to start the connection:
handle — Your full Bluesky handle, for example name.bsky.social.
app_password — Create one in Bluesky under Settings → App Passwords. Never your account password — an app password can be revoked on its own.
Caption. Up to 300 characters, and required. Bluesky counts graphemes, so emoji and combined characters can cost more than they look.
Media. Optional, at most 4; image or video. Up to four images, or one video.
Supports. schedule, carousel, metrics.
Pinterest (pinterest)
Connecting. A Pinterest business account with at least one board to pin to.
Caption. Up to 500 characters. Becomes the pin description.
Title. Optional, up to 100 characters.
Media. At least 1, at most 1; image or video. One image or one video per pin.
Supports. schedule, metrics.
Facebook (facebook)
Connecting. A Facebook Page you administer. Personal timelines cannot be published to through the API.
Caption. Up to 63206 characters.
Media. Optional, at most 10; image or video. Stories take exactly one item. Reels take one video.
Placement. timeline, reels, stories.
Supports. schedule, carousel, thumbnail, collaborators, mediaTags, metrics.
What a refusal means
Every error carries a code, a sentence, and retryable. Trust retryable: if it is false, the same request will fail the same way for ever, and the thing to change is the request.
A rejected post also carries issues, one per problem, each naming the channel and the field. Fix them together and try once, rather than one at a time.
Before anything is sent
| Code | Meaning | What to do |
|---|
authentication_required | No credential, or one Mellow does not recognise. | Present the key or token. |
insufficient_scope | The credential lacks a permission this call needs. | The owner grants it when they create the key. |
channel_not_connected | That channel ID is not one of this owner's. | Call list_channels; IDs are stable but connections come and go. |
channel_out_of_scope | The channel exists, but this key was not given it. | Ask the owner, or publish to the channels you do have. |
caption_too_long | The caption exceeds that network's limit. | Give the channel a shorter caption with perChannel. |
title_required | YouTube will not accept an upload without a title. | Set options.youtube.title. |
option_required | Something the network insists on is missing. | The message names it. |
media_kinds_mixed | Images and video in one post. | Split the post, or give the channel its own media. |
media_required | The network publishes nothing without media. | Add media, or drop that channel. |
schedule_in_past | scheduledAt has already passed. | Omit it to publish now. |
daily_limit_reached | This key has used its day's posts. | Wait, or the owner raises the limit. |
idempotency_key_reused | That key already published something else. | Use a new key. This is a bug in the caller. |
After it was sent
| Code | Meaning | What to do |
|---|
provider_error | The publishing provider refused or failed. | Read retryable. |
provider_result_failed | The network itself rejected this channel's post. | The channel's errorMessage carries what the network said. |
result_never_confirmed | The network never reported an outcome. | Check the account directly before republishing — it may have gone out. |
already_published | Cancelling something already public. | Nothing here can unpublish it; delete it in the network. |
Two failures worth naming
A partial post is not a failed post. status: "partial" means some channels published. Republishing the whole thing duplicates those. Republish only the channels whose target says failed.
A timeout is not a refusal. If create_post times out, the post may exist. Retry with the same idempotency key — that is exactly what it is for — or call list_posts and look.
Worked examples
Publish a vertical video everywhere it belongs
{
"caption": "Three minutes on why we rebuilt the editor.",
"media": ["https://cdn.example.com/editor.mp4"],
"channels": ["spc_ig", "spc_tiktok", "spc_youtube", "spc_threads"],
"options": {
"instagram": { "placement": "reels", "shareToFeed": true },
"youtube": { "title": "Why we rebuilt the editor", "privacyStatus": "public" },
"tiktok": { "privacyStatus": "public", "allowComment": true }
}
}
A vertical video under three minutes becomes a Short on YouTube by itself. There is no Shorts setting to send.
One thought, different lengths
Bluesky allows 300 characters and X allows 280; LinkedIn allows 3000 and rewards using them. Write the long version once and override the short ones:
{
"caption": "The long version, written for LinkedIn…",
"channels": ["spc_li", "spc_bsky", "spc_x"],
"perChannel": {
"spc_bsky": { "caption": "The short version." },
"spc_x": { "caption": "The short version." }
}
}
Schedule a week
Make one create_post per slot with scheduledAt, and give each a stable idempotency key — week-32-tue-morning rather than a random value — so re-running the plan does not double-post.
Prepare something for a person to approve
Under a review key, create_post prepares the post and stops. Tell the person it is waiting; they approve it in Mellow. whoami says which mode the key is in, so check before promising to publish.
Recover from a partial failure
get_post → status "partial"
→ targets[2].status "failed", errorMessage "…"
Fix what the message names, then create_post again with only the failed channel and a new idempotency key. Do not resend the channels that published.