Publishing

Connect a rendered video to a publishing profile, pick platforms, and read the publish state including which platforms had no account.

Publishing sends a rendered video to the social accounts a user connected in the ShortFast app. Two things decide where it goes: a profile (a named destination holding the connected accounts) and a list of platforms.

Profiles and accounts

A profile is created and connected in the app, not through the API. GET /profiles lists the ones this workspace holds, each with the accounts connected to it. It is unpaginated: a workspace holds a handful at most.

curl https://app.shortfast.com/api/v1/profiles \
  -H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx"
{
  "data": [
    {
      "id": "pR4kM8xT2vQ6nL9wz",
      "name": "Acme brand",
      "description": "Main brand accounts",
      "color": "#0A66C2",
      "isDefault": true,
      "createdAt": "2026-05-14T08:31:00.000Z",
      "accounts": [
        { "id": "acc_tt_01", "platform": "tiktok", "username": "acmehq", "displayName": "Acme", "pictureUrl": "https://cdn.shortfast.com/uploads/avatars/acmehq.jpg" },
        { "id": "acc_yt_01", "platform": "youtube", "username": "@acme", "displayName": "Acme Studio", "pictureUrl": null }
      ]
    }
  ]
}

accounts is cached. Pass ?refresh=true (or ?refresh=1) on /profiles or /profiles/{id} to re-read them upstream first. That is slower, and it never fails the request: if the accounts cannot be re-read, the cached ones are returned unchanged. Use it before a publish that depends on an account the user just connected.

Platforms

GET /platforms returns the platform keys a profile can publish to. These are exactly the values platforms accepts on a publish and on an automation's destination. The list is static and workspace-independent, so read it once at integration time rather than per call.

KeyName
tiktokTikTok
youtubeYouTube
instagramInstagram
twitterTwitter/X
facebookFacebook
linkedinLinkedIn
threadsThreads
pinterestPinterest
redditReddit
blueskyBluesky

A platform key is not a promise that an account exists for it. Whether a profile can actually reach a platform is decided per publish, from the accounts connected to that profile.

Check before publishing

GET /videos/{id}/publish answers three questions at once: can this video be published, where would a publish send it, and what happened last time.

curl https://app.shortfast.com/api/v1/videos/7bQxL2mF9dR4tK1sv/publish \
  -H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx"
{
  "data": {
    "videoId": "7bQxL2mF9dR4tK1sv",
    "rendered": true,
    "destination": { "profileId": "pR4kM8xT2vQ6nL9wz", "platforms": ["tiktok", "youtube"] },
    "publish": null
  }
}

rendered is true once a render exists; a video can only be published with one. destination is what a publish with no explicit body would use: a clip inherits its blueprint's destination, so an automation's clips need no configuration of their own. publish is null until the first attempt.

Publish a video

curl -X POST https://app.shortfast.com/api/v1/videos/7bQxL2mF9dR4tK1sv/publish \
  -H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9a1c4d77-52b6-4e0f-8c33-71d0e2f9b845" \
  -d '{
    "profileId": "pR4kM8xT2vQ6nL9wz",
    "platforms": ["tiktok", "youtube"],
    "caption": "Burnout is not a workload problem. #founders"
  }'

Every field is optional. Omit profileId and platforms to use the video's own destination, and caption to use the video's brief.description (caption is capped at 5000 characters, comfortably above every platform's own limit). Duplicate platform keys are collapsed, and an unknown one is a 400 before anything is sent.

Always send an Idempotency-Key. A publish is the one action with a visible, irreversible side effect on someone else's timeline, and a retry after a client timeout would post the same video twice.

missingPlatforms

A requested platform with no connected account does not fail the call. It is reported back instead, so the platforms that could be reached still go out:

{
  "data": {
    "videoId": "7bQxL2mF9dR4tK1sv",
    "platforms": ["tiktok"],
    "missingPlatforms": ["youtube"],
    "publish": {
      "status": "COMPLETED",
      "scheduledAt": null,
      "startedAt": "2026-08-06T09:40:02.000Z",
      "publishedAt": "2026-08-06T09:40:31.000Z",
      "failedAt": null,
      "platforms": ["tiktok"],
      "missingPlatforms": ["youtube"],
      "caption": "Burnout is not a workload problem. #founders",
      "error": null
    }
  }
}

platforms is what the video actually reached and missingPlatforms what it did not. Treat a non-empty missingPlatforms as a partial success worth surfacing: the publish is COMPLETED and will not retry those platforms on its own. The only case that fails outright is none of the requested platforms resolving, which is warning-publish-no-accounts.

The publish state machine

publish.status moves through four values, and the timestamps say when each transition happened:

StatusMeaningTimestamp
SCHEDULEDAn automation slotted this clip to go out laterscheduledAt
PUBLISHINGThe post is in flightstartedAt
COMPLETEDThe video is live on platformspublishedAt
FAILEDThe attempt stopped. error.code says whyfailedAt

DELETE /videos/{id}/publish wipes the block so the video can be sent again. It is refused with warning-invalid-input while a publish is live or already finished: clearing it then would lose the result and let an automation post the same video twice.

When a publish is refused

CodeWhyWhat clears it
warning-plan-publishPublishing needs a paid plan. Downloads stay freeUpgrade
warning-publish-not-configuredNo render yet, no profile selected, no platforms selected, or the profile no longer existsFix the destination
warning-publish-no-accountsNone of the requested platforms has a connected accountConnect an account
warning-publish-authA connected account's authorization expiredReconnect it in Settings

The same codes come back on publish.error.code when an attempt fails after it started, and on the video.publish_failed webhook. Subscribe to video.published and video.publish_failed rather than polling: a publish triggered by an automation happens without any call from you.

Reference

On this page