MELLOW HUB · FIELD NOTES

Schedule social media posts with a REST API

A Mellow Hub recipe for checking connected channels, validating a post, scheduling with an idempotency key and reading each destination’s final result.

By Mellow · Updated

The complete sequence

To schedule a post through Mellow Hub, identify the credential’s authority, select connected channels, validate one request, then create it with a stable idempotency key. Read the resulting post again to establish what happened on each destination. A successful create response is not proof that every network published it.

This recipe uses REST. An MCP client follows the equivalent whoami → list_channels → validate_post → create_post → get_post sequence described in the social media MCP guide.

Try the input check with a ready-to-run example

Download the Node.js input checker and post.example.json template into the same folder. The script needs Node.js 22 or newer and no packages. Read its source, then supply an existing Hub credential through your secret environment as MELLOW_HUB_KEY.

For this check, channels:read and posts:read are enough. Choose only the channels you intend to use in Hub’s access settings. Listing channels needs only the first permission. No publishing permission or subscription is needed for this input check.

node mellow-hub-check.mjs --channels
# Replace the channel IDs and media URL in post.example.json.
node mellow-hub-check.mjs post.example.json

The script checks the credential’s available channels, sends your edited JSON to Mellow Hub’s authenticated validation endpoint and prints the blocking issues and notes. It exits with 0 when those input checks pass, 1 for input issues, or 2 for a file, permission, connection or unexpected-response failure. HTTP 200 with ok: false still fails the check.

The script creates no post, fetches no media and makes no request to a social network. Mellow Hub’s endpoint reads the header of a video at the address in your file, without downloading it, to compare its length, frame and size with each network’s published limits; what is outside one comes back as a note and does not fail the check. Passing this check does not verify codecs, eventual account authorization or publication. The downloadable code is checked with isolated fixtures; this is not a recorded live customer publication. For the curl requests below, save your edited file as post.json.

1. Check the account and credential

Connect your own channels in Hub and create a credential with the channels and mode you intend to delegate. Store it in your server’s secret environment as MELLOW_HUB_KEY; never put it in frontend JavaScript or a public repository. The examples read that environment variable without displaying its value.

curl --fail-with-body https://www.mellow.world/api/hub/v1/whoami \
  -H "Authorization: Bearer $MELLOW_HUB_KEY"

curl --fail-with-body https://www.mellow.world/api/hub/v1/channels \
  -H "Authorization: Bearer $MELLOW_HUB_KEY"

Use the channel IDs returned for this account. The spc_… values below are placeholders. Check the mode and available allowance before creating anything. A review credential prepares a post for a person to approve. An autopilot credential can act within its delegated scope, ceiling and expiry.

2. Describe the actual post

Save the following structure as post.json. Replace both channel IDs, the sample media address, the text and the example date. Use an explicit ISO 8601 timezone offset or UTC Z; the timestamp represents an instant, not the reader’s local clock. The sample’s 2030 date is deliberately illustrative.

{
  "caption": "A short look at how this piece was made.",
  "channels": [
    "spc_your_instagram_channel",
    "spc_your_youtube_channel"
  ],
  "media": [
    "https://cdn.example.com/your-video.mp4"
  ],
  "scheduledAt": "2030-01-15T10:00:00Z",
  "options": {
    "instagram": {
      "placement": "reels"
    },
    "youtube": {
      "title": "How this piece was made",
      "privacyStatus": "public"
    }
  }
}

The network fetches media at publication time, so the real HTTPS URL must remain reachable then. A private local file or an expired signed URL will not work. This example gives YouTube its separate title while selecting the Reels placement for Instagram. Format availability still depends on your connected accounts and the provider.

3. Validate without publishing

curl --fail-with-body https://www.mellow.world/api/hub/v1/validate \
  -H "Authorization: Bearer $MELLOW_HUB_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @post.json

Inspect the response’s ok, issues and notes. Correct every blocking issue before continuing. Validation can return HTTP 200 with ok: false, so checking the HTTP status alone is insufficient. The authenticated endpoint checks your actual channel ownership as well as input rules.

For an account-free preview of caption, title and media-count checks, use the free post checker. The free checker does not read your file. The authenticated endpoint reads a video’s length, frame and size from its header and adds a note when one is outside a network’s published limit. A provider can still reject delivery.

4. Create once, retry consistently

The read-only starter credential cannot create posts. Before this step, use a connection with posts:write and posts:publish for the intended channels. These scopes are required in Review mode too; Review mode still leaves the publishing decision to a person. Without them the request returns a permission error.

The next request creates the post. Run it only when the content and destination authority are correct. Under review mode it waits for approval; under autopilot the delegated action can proceed at the scheduled time.

curl --fail-with-body https://www.mellow.world/api/hub/v1/posts \
  -H "Authorization: Bearer $MELLOW_HUB_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: studio-process-video-slot-001" \
  --data-binary @post.json

Persist the idempotency key alongside the job in your application. If the response is lost, retry the same request with the same key. Do not create a new key just because a timeout occurred. A different post needs a different key; reusing one for changed content is rejected.

5. Read the result per destination

Save the returned post.id and retrieve it with GET /api/hub/v1/posts/{id}, using the same authorization header. Inspect each entry in targets and its public URL. A multi-network post may be partial: one destination can succeed while another fails.

Keep the planned time, Hub post ID, idempotency key and destination outcomes together in your own job record. Avoid logging credentials or full authorization headers. When a target fails, use its reported error to decide the next action rather than blindly recreating the entire post.

Recover a timed-out or partly published post

A timeout means the caller does not know the outcome. A partial result means Hub has recorded different outcomes for the selected destinations. Keep those cases separate when deciding what to send next.

The create response was lost

Keep the original payload and idempotency key in your job record before the first request. Retry that unchanged request with the same key and valid authority. When Hub already has the post, it returns that post’s ID and current state. The REST response is still HTTP 201, so 201 alone does not tell you whether this attempt created a new record. Once you have the ID, use GET to follow its state.

If you receive 409 idempotency_key_reused, compare the request with your saved job. Do not automatically generate another key to get past the error: that would describe a new operation and could duplicate the original publication.

One destination failed after another published

Read targets before doing anything. Hub sends a refused destination again by itself where that can help: a busy network, a file a network could not process, a network’s limit. While it does, the target reads scheduled and carries waiting with the reason and the time. Leave that one alone.

For a destination that still reads failed, send the same post to it again with POST /api/hub/v1/posts/{id}/retry. It goes to the failed destinations only. It needs posts:publish on a credential that publishes on its own, and it is limited to three tries for one network of one post. Create a new post only when the post itself has to change. For example, if Instagram is published and YouTube is failed because of its title, fix the title, validate a new payload containing only that YouTube channel and send it with a new key, such as studio-process-video-slot-001-youtube-recovery-1. Keep its new post ID alongside the original job. The usual channel permissions, review mode and publication allowance still apply.

Replaying the original key returns the existing post; it does not restart its failed destinations. A destination still waiting or publishing is not a confirmed failure. Do not resend it just because the caller stopped waiting, and do not include destinations that already published in the recovery request.

Questions that come up in real integrations

Can I use a different caption on one network?

Yes. A perChannel entry keyed by the real channel ID overrides that channel’s caption or media. Platform-wide fields belong in options. See the worked examples in the Hub reference.

Can an agent connect my social accounts by itself?

No. The account owner completes the relevant connection flow. An API key is delegation for already authorized actions, not permission to impersonate the owner at a network’s login screen.

Does one request use one publication from my plan?

The allowance is counted per network destination. Check the current plans and the credential’s remaining allowance before choosing targets.

Write to Hub's founder

Every message is read by the founder himself, so you are talking to him directly. Questions, bugs, a feature you miss: write about anything. We are glad to keep making Hub better.

What is it about?

We attach this page's address so we can see what you saw.