---
tokens: 6295
title: "Publish Video to Every Platform via API | Postproxy"
description: "Publish video to every social platform via API — encoding, upload flows, processing checks, and thumbnails across all ten platforms in one reference."
url: "https://postproxy.dev/blog/how-to-publish-video-to-every-social-platform-via-api/"
---
![Publish Video to Every Platform via API](/blog/how-to-publish-video-to-every-social-platform-via-api.png)

Quick answer

Publishing video across X, Instagram, Facebook, LinkedIn, Threads, TikTok, YouTube, and Pinterest requires five different upload protocols (chunked INIT/APPEND/FINALIZE, URL-pull container model, resumable upload with file handles, ETag-tracked chunks, and resumable PUT) plus seven different processing-status polling mechanisms. The safe encoding baseline is H.264 video with AAC audio in MP4.

## Why does one video become seven different jobs across platforms?

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.

## How do you encode video for every social platform?

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.

Platform

Container

Video codec

Audio codec

Frame rate

Max resolution

Max duration

Max size

X

MP4, MOV

H.264

AAC

—

—

140 seconds

512 MB

Instagram

MP4, MOV

H.264

AAC

—

—

60 min (feed), 90 min (reels)

300 MB

Facebook

MP4, MOV

—

—

—

—

4 hours

4 GB

LinkedIn

MP4

H.264

—

—

—

30 minutes

500 MB

Threads

MOV, MP4

H.264, HEVC

AAC (up to 48 kHz)

23–60 FPS

1920px width

5 minutes

1 GB

TikTok

MP4, WebM, MOV

H.264, HEVC

—

—

—

10 minutes

4 GB

YouTube

Most formats

Most codecs

Most codecs

—

—

12 hours (default)

256 GB

Pinterest

MP4, MOV

H.264

AAC

—

—

15 minutes

2 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:

```
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.

## How do you upload video to each social platform?

After encoding, the video has to reach the platform. There are five distinct upload protocols across ten 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](/blog/x-twitter-api-posting-integration-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](/blog/post-to-instagram-via-api/).

### 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](/blog/facebook-api-posting-integration-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](/blog/linkedin-api-automate-company-page-publishing/).

### 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](/blog/how-to-post-to-threads-via-api/).

### 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](/blog/how-to-post-to-tiktok-via-api/).

### 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](/blog/youtube-upload-api-guide/); for daily upload limits and the ways to automate the whole flow, see [how to automate YouTube uploads](/blog/automate-youtube-uploads/).

## How do platforms process uploaded video?

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`.

State

Meaning

`pending`

Queued for processing

`in_progress`

Processing, check `progress_percent`

`succeeded`

Ready to attach to a post

`failed`

Must 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.

Status

Meaning

`IN_PROGRESS`

Still processing

`FINISHED`

Ready to publish

`PUBLISHED`

Already published

`EXPIRED`

Container not published within 24 hours

`ERROR`

Processing 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.

Status

Meaning

`WAITING_UPLOAD`

Awaiting upload data

`PROCESSING`

Being processed

`AVAILABLE`

Ready to use in a post

`PROCESSING_FAILED`

Upload 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}"}
```

Status

Meaning

`PROCESSING_UPLOAD`

File upload in progress

`PROCESSING_DOWNLOAD`

TikTok is downloading from your URL

`PUBLISH_COMPLETE`

Successfully posted

`FAILED`

Check `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`.

Field

Values

`status.uploadStatus`

`uploaded`, `processed`, `rejected`, `deleted`, `failed`

`processingDetails.processingStatus`

`processing`, `succeeded`, `failed`, `terminated`

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

### Processing at a glance

Platform

Method

Interval

Status field

X

GET poll

`check_after_secs`

`processing_info.state`

Instagram

GET poll

Once per minute, max 5 min

`status_code`

Facebook

None documented

—

—

LinkedIn

GET poll

—

`status`

Threads

Wait 30s, then GET poll

—

`status_code`

TikTok

POST poll

—

`status` in response body

YouTube

GET poll

—

`processingDetails.processingStatus`

Pinterest

GET poll

—

`status`

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.

## How do you handle thumbnails across platforms?

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

Platform

Custom image

Frame selection

Method

X

No

No

Auto-generated

Instagram

Reels only (`cover_url`)

Reels only (`thumb_offset`)

Parameter on container creation

Facebook

No

No

Auto-generated

LinkedIn

Yes

No

Upload to `thumbnailUploadUrl`

Threads

No

No

Auto-generated

TikTok

No

Yes (`video_cover_timestamp_ms`)

Parameter on publish init

YouTube

Yes

No

Separate 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.

## How do you publish video on each platform?

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:

Step

X

Instagram

Facebook

LinkedIn

Threads

TikTok

YouTube

Pinterest

**Encode**

H.264/AAC/MP4

H.264/AAC/MP4

MP4

H.264/MP4

H.264 or HEVC/AAC/MP4

H.264 or HEVC/MP4

Most formats

MP4/MOV

**Upload**

Chunked 3-step

URL pull or resumable

Resumable + file handle

Chunked + ETag

URL pull

URL pull or chunked

Resumable PUT

File handle

**Process**

Poll GET, use `check_after_secs`

Poll GET, 1/min, 5 min max

None documented

Poll GET for `AVAILABLE`

Wait 30s minimum

Poll POST with `publish_id`

Poll GET for `succeeded`

Poll GET for `succeeded`

**Thumbnail**

None

`cover_url` / `thumb_offset` (reels)

None

Upload to URL

None

`video_cover_timestamp_ms`

Separate POST

`cover_url` / `thumb_offset`

**Publish**

POST `/2/tweets`

POST `media_publish`

Included in upload

POST `/rest/posts`

POST `threads_publish`

Automatic

Included in upload

POST `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 ten platforms looks like through Postproxy:

```
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](https://app.postproxy.dev/users/sign_up).

![Denis Baranov](/blog/authors/denis-baranov.jpg)

[Denis Baranov](/blog/author/denis-baranov/)

Postproxy Founder

CTO behind Postproxy's infrastructure. Software engineer with over 20 years of building systems that don't break. Previously at Adjust, Denis built and scaled high-throughput integrations with major social ad platforms, processing attribution events at volume. Before that, he worked across fintech and legaltech, where handling sensitive data isn't optional.

social-media-publishingapi-integrationautomationworkflowsinfrastructure

```json
[
  {
    "@context": "https://schema.org",
    "@type": "Organization",
    "@id": "https://postproxy.dev/#organization",
    "name": "Postproxy",
    "alternateName": [
      "Postproxy API",
      "postproxy.dev"
    ],
    "url": "https://postproxy.dev",
    "logo": "https://postproxy.dev/logo-512.png",
    "description": "Postproxy is a unified social media API for agencies, SaaS products, and AI agents — publishing, comments, DMs, reviews, and analytics across Instagram, TikTok, LinkedIn, X, YouTube, Facebook, Threads, Bluesky, Pinterest, Telegram, and Google Business.",
    "foundingDate": "2026",
    "founder": [
      {
        "@type": "Person",
        "@id": "https://postproxy.dev/#dmitry-sereda",
        "name": "Dmitry Sereda",
        "jobTitle": "CEO",
        "sameAs": [
          "https://www.linkedin.com/in/dmitrysereda/"
        ]
      },
      {
        "@type": "Person",
        "@id": "https://postproxy.dev/#denis-baranov",
        "name": "Denis Baranov",
        "jobTitle": "CTO",
        "sameAs": [
          "https://www.linkedin.com/in/denis--baranov/"
        ]
      }
    ],
    "sameAs": [
      "https://github.com/postproxy",
      "https://www.linkedin.com/company/postproxy/",
      "https://www.youtube.com/@postproxy",
      "https://bsky.app/profile/postproxy.dev",
      "https://www.producthunt.com/products/postproxy",
      "https://www.capterra.com/p/10045650/Postproxy/",
      "https://sourceforge.net/software/product/Postproxy/",
      "https://n8n.io/integrations/postproxy/",
      "https://futuretools.io/tools/postproxy",
      "https://www.npmjs.com/package/postproxy-sdk",
      "https://pypi.org/project/postproxy/",
      "https://rubygems.org/gems/postproxy-sdk"
    ],
    "contactPoint": {
      "@type": "ContactPoint",
      "email": "privacy@postproxy.dev",
      "contactType": "customer service"
    },
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "Kolonnenstr. 8",
      "addressLocality": "Berlin",
      "addressCountry": "DE"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "WebSite",
    "@id": "https://postproxy.dev/#website",
    "name": "Postproxy",
    "alternateName": "Postproxy — Social Media API",
    "url": "https://postproxy.dev",
    "description": "Unified social media API for agencies, SaaS, and AI agents.",
    "inLanguage": "en-US",
    "publisher": {
      "@id": "https://postproxy.dev/#organization"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "headline": "Publish Video to Every Platform via API",
    "description": "Publish video to every social platform via API — encoding, upload flows, processing checks, and thumbnails across all ten platforms in one reference.",
    "datePublished": "2026-02-13T00:00:00.000Z",
    "author": {
      "@type": "Person",
      "name": "Denis Baranov",
      "url": "https://postproxy.dev/blog/author/denis-baranov/",
      "jobTitle": "Postproxy Founder",
      "image": "https://postproxy.dev/blog/authors/denis-baranov.jpg",
      "sameAs": [
        "https://www.linkedin.com/in/denis--baranov/"
      ]
    },
    "image": [
      "https://postproxy.dev/blog/how-to-publish-video-to-every-social-platform-via-api.png"
    ],
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://postproxy.dev/blog/how-to-publish-video-to-every-social-platform-via-api/"
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://postproxy.dev/#organization",
      "name": "Postproxy",
      "logo": {
        "@type": "ImageObject",
        "url": "https://postproxy.dev/logo-512.png"
      }
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "WebPage",
    "@id": "https://postproxy.dev/blog/how-to-publish-video-to-every-social-platform-via-api/",
    "url": "https://postproxy.dev/blog/how-to-publish-video-to-every-social-platform-via-api/",
    "name": "Publish Video to Every Platform via API | Postproxy",
    "description": "Publish video to every social platform via API — encoding, upload flows, processing checks, and thumbnails across all ten platforms in one reference.",
    "inLanguage": "en",
    "isPartOf": {
      "@id": "https://postproxy.dev/#website"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Home",
        "item": "https://postproxy.dev/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "Blog",
        "item": "https://postproxy.dev/blog/"
      },
      {
        "@type": "ListItem",
        "position": 3,
        "name": "Publish Video to Every Platform via API"
      }
    ]
  }
]
```
