How to publish video to every social platform via API

End-to-end guide covering encoding requirements, upload flows, processing status checks, and thumbnail options across all eight platforms. A single reference for video publishing.

How to publish video to every social platform via API

One video, seven different jobs

Publishing a video to a single platform is a multi-step process. Publishing the same video to X, Instagram, Facebook, LinkedIn, Threads, TikTok, and YouTube is seven different multi-step processes. Each platform has its own encoding requirements, upload protocol, processing model, and thumbnail handling.

This guide covers the full lifecycle — encoding, uploading, waiting for processing, setting thumbnails, and publishing — for every platform, in one place.

Step 1: Encoding

Before a video reaches any API, it has to be in the right format. Every platform has codec and container requirements, and most will reject your upload with an unhelpful error if they are not met.

PlatformContainerVideo codecAudio codecFrame rateMax resolutionMax durationMax size
XMP4, MOVH.264AAC140 seconds512 MB
InstagramMP4, MOVH.264AAC60 min (feed), 90 min (reels)300 MB
FacebookMP4, MOV4 hours4 GB
LinkedInMP4H.26430 minutes500 MB
ThreadsMOV, MP4H.264, HEVCAAC (up to 48 kHz)23–60 FPS1920px width5 minutes1 GB
TikTokMP4, WebM, MOVH.264, HEVC10 minutes4 GB
YouTubeMost formatsMost codecsMost codecs12 hours (default)256 GB
PinterestMP4, MOVH.264AAC15 minutes2 GB

YouTube is the most permissive — it accepts nearly anything and transcodes server-side. Every other platform has opinions.

The safe baseline for cross-platform publishing is H.264 video with AAC audio in an MP4 container. This is the one combination that every platform accepts. If your source video is encoded differently — VP9 in a WebM container, for example — X will reject it at the FINALIZE step, LinkedIn will reject it at upload, and Instagram will return a container error.

In practice, a single transcode pass handles most cases:

Terminal window
ffmpeg -i input.webm -c:v libx264 -c:a aac -movflags +faststart output.mp4

But codec is only the start. Threads enforces 23–60 FPS and a max width of 1920 pixels. Instagram requires sRGB color space. TikTok expects a minimum resolution of 720x1280 for videos. A single ffmpeg command rarely satisfies all platforms at once. Any system that publishes video cross-platform needs a transcoding layer that inspects source files and converts per platform.

Step 2: Upload

After encoding, the video has to reach the platform. There are five distinct upload protocols across eight platforms. None of them are interchangeable.

X: Chunked INIT/APPEND/FINALIZE

X requires all media uploaded before creating a post. The flow is always three steps.

INIT. POST to /2/media/upload/initialize with the file size, MIME type, and media_category set to tweet_video:

{
"media_type": "video/mp4",
"total_bytes": 12345678,
"media_category": "tweet_video"
}

Returns a media_id.

APPEND. POST binary chunks to /2/media/upload/{media_id}/append as multipart/form-data. Each chunk has a segment_index starting at 0. Max 5 MB per chunk.

FINALIZE. POST to /2/media/upload/{media_id}/finalize. Returns processing_info with a check_after_secs value — the video is not ready yet.

Full details in the X publishing guide.

Instagram: Container model with URL pull

Instagram does not accept file uploads. You provide a publicly accessible URL and Instagram fetches the video.

Create container. POST to /{ig-user-id}/media:

{
"video_url": "https://example.com/video.mp4",
"media_type": "VIDEO",
"caption": "Your caption"
}

For large files or unreliable connections, use the resumable upload flow: create a container with upload_type: "resumable", then POST the binary to https://rupload.facebook.com/ig-api-upload/{api-version}/{container-id} with offset and file_size headers.

Publish. POST to /{ig-user-id}/media_publish with the container ID.

For reels, set media_type to REELS. For stories, set it to STORIES. Same container model, different type values.

Full details in the Instagram publishing guide.

Facebook: Resumable Upload API with file handle

Facebook uses its own Resumable Upload API with a three-step flow.

Start session. POST to /{app-id}/uploads with file_name, file_length, and file_type. Returns an upload_session_id.

Upload. POST the binary to /upload:{upload_session_id} with a file_offset: 0 header. Returns a file handle h.

Publish. POST to /{page-id}/videos with fbuploader_video_file_chunk set to the file handle.

If the upload is interrupted, GET /upload:{session_id} returns the current file_offset so you can resume from where you left off.

Full details in the Facebook publishing guide.

LinkedIn: Chunked upload with ETag tracking

LinkedIn has the most complex upload flow. Videos require initializing an upload session, uploading in chunks, tracking ETags from each chunk response, and finalizing with all ETags.

Initialize. POST to /rest/videos?action=initializeUpload:

{
"initializeUploadRequest": {
"owner": "urn:li:organization:5515715",
"fileSizeBytes": 1055736,
"uploadThumbnail": true
}
}

Returns a video URN, an uploadToken, and uploadInstructions — an array of parts with byte ranges and assigned upload URLs.

Upload chunks. Split the video into 4 MB chunks. Upload each to its assigned URL. Save the ETag from each response header.

Finalize. POST to /rest/videos?action=finalizeUpload with the video URN, uploadToken, and uploadedPartIds (the array of ETags). This step fails if any ETag is missing or incorrect.

Every request requires Linkedin-Version, X-Restli-Protocol-Version: 2.0.0, and Authorization headers.

Full details in the LinkedIn publishing guide.

Threads: Container model with URL pull

Threads uses the same container pattern as Instagram but with a different base URL and different constraints.

Create container. POST to /{threads-user-id}/threads:

{
"media_type": "VIDEO",
"video_url": "https://example.com/video.mp4",
"text": "Your caption"
}

Base URL is https://graph.threads.net/v1.0/ — not graph.facebook.com. The caption field is text, not caption.

Wait. You must wait at least 30 seconds before publishing. This is unique to Threads.

Publish. POST to /{threads-user-id}/threads_publish with the container ID.

Full details in the Threads publishing guide.

TikTok: Pull from URL or chunked upload

TikTok supports two upload methods through the same initialization endpoint.

Pull from URL. POST to /v2/post/publish/video/init/:

{
"post_info": {
"title": "Your video caption",
"privacy_level": "PUBLIC_TO_EVERYONE",
"disable_duet": false,
"disable_stitch": false,
"disable_comment": false,
"video_cover_timestamp_ms": 1000
},
"source_info": {
"source": "PULL_FROM_URL",
"video_url": "https://example.com/video.mp4"
}
}

Chunked file upload. Same endpoint with source set to FILE_UPLOAD, plus video_size, chunk_size, and total_chunk_count. Returns an upload_url. PUT chunks with Content-Range headers. The upload URL expires after 1 hour.

Before any publish, you must call the creator info endpoint (/v2/post/publish/creator_info/query/) to get available privacy levels. Publishing with an unsupported privacy level fails silently or with an opaque error.

Full details in the TikTok publishing guide.

YouTube: Resumable upload with PUT

YouTube uses Google’s resumable upload protocol. Metadata and video data are sent separately.

Initiate. POST to https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status with video metadata as JSON:

{
"snippet": {
"title": "Your video title",
"description": "Video description",
"tags": ["tag1", "tag2"],
"categoryId": "22"
},
"status": {
"privacyStatus": "public",
"selfDeclaredMadeForKids": false
}
}

Include X-Upload-Content-Length and X-Upload-Content-Type headers. Returns a Location header with the resumable upload URI.

Upload. PUT the video binary to the resumable URI. For large files, send in chunks with Content-Range headers. Interrupted uploads can be resumed by checking which bytes were received.

Full details in the YouTube publishing guide.

Step 3: Processing

After upload, every platform processes the video before it can go live. Each has a different polling mechanism, different status values, and different timing behavior.

X

Poll GET /2/media/upload/{media_id}. The response includes processing_info with a state field and check_after_secs.

StateMeaning
pendingQueued for processing
in_progressProcessing, check progress_percent
succeededReady to attach to a post
failedMust retry the upload

Always wait check_after_secs between polls.

Instagram

Poll GET /{container-id}?fields=status_code. Query once per minute, for no more than 5 minutes.

StatusMeaning
IN_PROGRESSStill processing
FINISHEDReady to publish
PUBLISHEDAlready published
EXPIREDContainer not published within 24 hours
ERRORProcessing failed

Facebook

Videos are processed after the publish call to /{page-id}/videos. There is no explicit polling endpoint documented for page video processing status.

LinkedIn

Poll GET /rest/videos/{video-urn} for the status field.

StatusMeaning
WAITING_UPLOADAwaiting upload data
PROCESSINGBeing processed
AVAILABLEReady to use in a post
PROCESSING_FAILEDUpload or processing failed

The video must be AVAILABLE before you can reference it in a POST to /rest/posts.

Threads

No dedicated processing endpoint. You must wait at least 30 seconds after creating the container before publishing. You can also poll the container status the same way as Instagram — GET /{container-id}?fields=status_code.

TikTok

POST to /v2/post/publish/status/fetch/ with the publish_id:

{
"publish_id": "{PUBLISH_ID}"
}
StatusMeaning
PROCESSING_UPLOADFile upload in progress
PROCESSING_DOWNLOADTikTok is downloading from your URL
PUBLISH_COMPLETESuccessfully posted
FAILEDCheck fail_reason for details

Processing takes under 30 seconds for files up to 512 MB. Public posts also go through content moderation after processing.

YouTube

Poll GET https://www.googleapis.com/youtube/v3/videos?id={VIDEO_ID}&part=status,processingDetails.

FieldValues
status.uploadStatusuploaded, processed, rejected, deleted, failed
processingDetails.processingStatusprocessing, succeeded, failed, terminated

Only the video owner can access processingDetails. Processing time depends on file size, format, and resolution.

Processing at a glance

PlatformMethodIntervalStatus field
XGET pollcheck_after_secsprocessing_info.state
InstagramGET pollOnce per minute, max 5 minstatus_code
FacebookNone documented
LinkedInGET pollstatus
ThreadsWait 30s, then GET pollstatus_code
TikTokPOST pollstatus in response body
YouTubeGET pollprocessingDetails.processingStatus
PinterestGET pollstatus

Eight platforms, seven polling mechanisms. GET vs POST. Response body vs status code field. Seconds vs minutes. Building a generic “wait for ready” function requires per-platform adapters.

Step 4: Thumbnails

Not every platform gives you control over the video thumbnail. Those that do each handle it differently.

X — No custom thumbnail API for videos in posts. The platform auto-generates a thumbnail from the video.

Instagram — Reels support a cover_url parameter (publicly accessible image URL) or thumb_offset (timestamp in milliseconds for a frame from the video). If both are provided, cover_url takes precedence. Feed video posts do not support custom thumbnails.

Facebook — No custom thumbnail option documented for page video posts through the Graph API.

LinkedIn — The initializeUpload response includes a thumbnailUploadUrl. Upload a custom thumbnail image (JPG or PNG) directly to that URL before finalizing the video. LinkedIn also provides a captionsUploadUrl for SRT caption files.

Threads — No custom thumbnail option. The platform selects a frame automatically.

TikTok — The video_cover_timestamp_ms field in post_info lets you specify which frame (in milliseconds) to use as the cover image. No custom image upload.

YouTube — Separate API call after upload. POST the thumbnail image to https://www.googleapis.com/upload/youtube/v3/thumbnails/set?videoId={VIDEO_ID}. Accepts JPEG, PNG, GIF, or BMP. Recommended resolution: 1280x720. Max file size: 2 MB.

Thumbnail support summary

PlatformCustom imageFrame selectionMethod
XNoNoAuto-generated
InstagramReels only (cover_url)Reels only (thumb_offset)Parameter on container creation
FacebookNoNoAuto-generated
LinkedInYesNoUpload to thumbnailUploadUrl
ThreadsNoNoAuto-generated
TikTokNoYes (video_cover_timestamp_ms)Parameter on publish init
YouTubeYesNoSeparate POST after upload

Three platforms auto-generate with no override. Two let you pick a frame. Two accept a custom image. One requires a separate API call after the video is uploaded. There is no common interface.

Step 5: Publish

After encoding, uploading, processing, and optionally setting a thumbnail, the video still needs to be published. This step also varies.

X — POST to /2/tweets with the media_id from the upload in media.media_ids. One call, video is live.

Instagram — POST to /{ig-user-id}/media_publish with the container ID as creation_id. This is the second API call after container creation.

Facebook — The POST to /{page-id}/videos with the file handle both uploads and publishes. No separate publish step.

LinkedIn — POST to /rest/posts with the video URN in content.media.id. The video must already be AVAILABLE.

Threads — POST to /{threads-user-id}/threads_publish with the container ID as creation_id. Must happen at least 30 seconds after container creation.

TikTok — Publishing happens automatically after the init call. The publish_id returned lets you track when it completes. No separate publish step.

YouTube — The video is created during the upload. The videos.insert call that sends the metadata initiates the upload, and the video goes live once processing completes (if privacyStatus is public).

Pinterest — POST to /v5/media with the video URL and media_type: "video", waiting processing is done. Then POST to /v5/pins with the media ID and media_type: "video".

Three platforms require an explicit publish call. Five publish as part of the upload or post creation. No two platforms handle this the same way.

The full picture

Here is every step, for every platform, in one table:

StepXInstagramFacebookLinkedInThreadsTikTokYouTubePinterest
EncodeH.264/AAC/MP4H.264/AAC/MP4MP4H.264/MP4H.264 or HEVC/AAC/MP4H.264 or HEVC/MP4Most formatsMP4/MOV
UploadChunked 3-stepURL pull or resumableResumable + file handleChunked + ETagURL pullURL pull or chunkedResumable PUTFile handle
ProcessPoll GET, use check_after_secsPoll GET, 1/min, 5 min maxNone documentedPoll GET for AVAILABLEWait 30s minimumPoll POST with publish_idPoll GET for succeededPoll GET for succeeded
ThumbnailNonecover_url / thumb_offset (reels)NoneUpload to URLNonevideo_cover_timestamp_msSeparate POSTcover_url / thumb_offset
PublishPOST /2/tweetsPOST media_publishIncluded in uploadPOST /rest/postsPOST threads_publishAutomaticIncluded in uploadPOST v5/media

Five encoding targets, five upload protocols, seven processing models, four thumbnail approaches, and two publish patterns. A video that works end-to-end on one platform requires a completely different implementation on each of the others.

The same video, through Postproxy

Here is what publishing a video to all eight platforms looks like through Postproxy:

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "3 tips that changed how we approach customer onboarding"
},
"profiles": ["twitter", "instagram", "facebook", "linkedin", "threads", "tiktok", "youtube", "pinterest"],
"media": ["https://example.com/video.mp4"],
"platforms": {
"youtube": {
"title": "3 Tips for Customer Onboarding",
"privacy_status": "public"
},
"instagram": {
"format": "reel",
"cover_url": "https://example.com/thumbnail.jpg"
},
"tiktok": {
"privacy_status": "PUBLIC_TO_EVERYONE",
"video_cover_timestamp_ms": 3000
}
}
}'

One request. One video URL. Postproxy handles everything described in this guide — transcoding to each platform’s required codec, uploading through each platform’s protocol, polling each platform’s processing status, setting thumbnails where supported, and publishing through each platform’s endpoint.

What Postproxy handles

Postproxy abstracts away seven different video publishing pipelines so your system does not have to implement any of them:

  • Automatic video transcoding to each platform’s required codec, container, frame rate, and resolution
  • Five different upload protocols (chunked, container, resumable, ETag-tracked, PUT-based)
  • Per-platform processing status polling with appropriate intervals and timeout handling
  • Thumbnail support — custom images on YouTube and LinkedIn, frame selection on TikTok, cover URLs on Instagram reels
  • Per-platform publish calls where required
  • Per-platform format validation before upload begins
  • Per-platform outcome reporting — what succeeded, what failed, and why

Your system provides a video URL. Postproxy handles encoding, uploading, processing, thumbnails, and publishing across every platform.

Connect your accounts and start publishing video through the Postproxy API.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.