---
tokens: 5534
title: "Image & Video Uploads Across Social APIs | Postproxy"
description: "How each social media platform handles media uploads differently — chunked, container, resumable, pull-from-URL — and what it takes to support all of them."
url: "https://postproxy.dev/blog/handling-image-and-video-uploads-across-social-media-apis/"
---
![Image & Video Uploads Across Social APIs](/blog/handling-image-and-video-uploads-across-social-media-apis.png)

Quick answer

Social media APIs use five distinct media upload patterns: chunked INIT/APPEND/FINALIZE (X), container model with URL pull (Instagram, Threads), resumable upload with file handle (Facebook), chunked upload with ETag tracking (LinkedIn), and resumable PUT (YouTube). TikTok supports URL pull or chunked. Each enforces different formats, codecs, and processing-status polling, so cross-platform publishing requires a transcoding layer plus per-platform implementations.

## No two platforms upload media the same way

Media uploads are the hardest part of social media API integration. Every platform has a different upload mechanism, different format requirements, different size limits, and different processing models. A video that publishes to X in three steps requires a completely different flow on Instagram, LinkedIn, TikTok, Facebook, and YouTube.

This guide covers what each platform expects and where the differences create real engineering complexity. For a per-platform walkthrough of encoding, upload, and processing focused on video, see [Publish Video to Every Platform via API](/blog/how-to-publish-video-to-every-social-platform-via-api/).

## What are the five social media upload models?

Across ten platforms, there are five distinct upload patterns:

1.  **Chunked upload** (X) — INIT, APPEND in chunks, FINALIZE, then poll for processing
2.  **Container model with URL pull** (Instagram, Threads) — Provide a public URL, the platform fetches the file
3.  **Resumable upload with file handle** (Facebook) — Upload to a session endpoint, get a handle, attach to a post
4.  **Chunked upload with ETag tracking** (LinkedIn) — Upload in parts to assigned URLs, track ETags, finalize
5.  **Resumable upload with PUT** (YouTube) — Initiate a session, PUT binary to the returned URI

TikTok supports two patterns: URL pull (like Instagram) or chunked file upload with Content-Range headers.

## Platform-by-platform breakdown

### X (Twitter): Chunked INIT/APPEND/FINALIZE

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

**Step 1 — Initialize.** POST to `/2/media/upload/initialize` with `media_type`, `total_bytes`, and `media_category` (`tweet_image`, `tweet_gif`, or `tweet_video`). Returns a `media_id`.

**Step 2 — 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.

**Step 3 — Finalize.** POST to `/2/media/upload/{media_id}/finalize`. For videos and GIFs, the response includes `processing_info` with a `check_after_secs` value. You must poll GET `/2/media/upload/{media_id}` until `state` is `succeeded` before attaching the media to a post.

**Limits:**

-   Images: 5 MB (JPEG, PNG, WebP)
-   Animated GIFs: 15 MB
-   Videos: 512 MB (MP4, MOV)
-   Max per post: 4 images, 1 video, or 1 GIF

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 directly. You provide a publicly accessible URL and Instagram fetches the media from your server.

**Images.** POST to `/{ig-user-id}/media` with `image_url` and `caption`. Returns a container ID. Publish with POST to `/{ig-user-id}/media_publish`.

**Videos.** Same container flow with `video_url` and `media_type: "VIDEO"`. For large files, 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.

**Carousels.** Create up to 10 item containers with `is_carousel_item: true`, then create a parent container with `media_type: "CAROUSEL"` and the children IDs. All images are cropped to the first image’s aspect ratio.

**Reels and Stories.** Same container flow with `media_type` set to `REELS` or `STORIES`.

**Processing.** Poll `/{container-id}?fields=status_code` once per minute, max 5 minutes. Statuses: `IN_PROGRESS`, `FINISHED`, `PUBLISHED`, `EXPIRED`, `ERROR`.

**Limits:**

-   Images: JPEG only
-   Videos: varies by type (feed, reels, stories)
-   100 posts per 24-hour rolling window

Full details in the [Instagram publishing guide](/blog/post-to-instagram-via-api/).

### Facebook: Resumable upload with file handle

Facebook uses different endpoints for different content types and its own Resumable Upload API for video.

**Photos.** POST to `/{page-id}/photos` with a `url` parameter pointing to a publicly accessible image. Or upload directly as `multipart/form-data`.

**Videos.** Three-step Resumable Upload API:

1.  POST to `/{app-id}/uploads` with `file_name`, `file_length`, `file_type`. Returns an `upload_session_id`
2.  POST the binary to `/upload:{upload_session_id}` with `file_offset: 0` header. Returns a file handle `h`
3.  POST to `/{page-id}/videos` with `fbuploader_video_file_chunk` set to the file handle

Interrupted uploads can be resumed: GET `/upload:{session_id}` returns the current `file_offset`, and you continue from there.

**Limits:**

-   Photos: must be publicly accessible URL or direct upload
-   Videos: via Resumable Upload API
-   Scheduled posts: 10 minutes to 30 days from request time

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. Images and videos each have their own API, and videos require tracking ETags from each chunk upload.

**Images.** Two-step process:

1.  POST to `/rest/images?action=initializeUpload` with the owner URN. Returns an `uploadUrl` and image URN
2.  PUT the binary to the `uploadUrl`
3.  Poll `/rest/images/{image-urn}` until status is `AVAILABLE`

Supported: JPG, PNG, GIF (up to 250 frames). Max 36,152,320 pixels.

**Videos.** Four-step process:

1.  POST to `/rest/videos?action=initializeUpload` with owner URN, `fileSizeBytes`, `uploadThumbnail: true`. Returns a video URN, `uploadToken`, and `uploadInstructions` — an array of parts with byte ranges and upload URLs
2.  Upload each 4 MB chunk to its assigned URL. Save the `ETag` from each response header
3.  Optional: upload custom thumbnail to `thumbnailUploadUrl`, captions to `captionsUploadUrl`
4.  POST to `/rest/videos?action=finalizeUpload` with the video URN, `uploadToken`, and `uploadedPartIds` (the array of ETags)

The finalize step will fail if any ETag is missing or incorrect. Poll `/rest/videos/{video-urn}` until status is `AVAILABLE`.

**Limits:**

-   Videos: MP4 only, 3 seconds to 30 minutes, 75 KB to 500 MB
-   Multi-image posts: 2-20 images

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 model as Instagram, but with a different base URL (`graph.threads.net` instead of `graph.facebook.com`) and different constraints.

**Images and videos.** POST to `/{threads-user-id}/threads` with `media_type` (`TEXT`, `IMAGE`, or `VIDEO`), the media URL, and optional `text`. Publish with POST to `/{threads-user-id}/threads_publish`.

**Critical timing constraint.** You must wait at least 30 seconds between creating a container and publishing it. This is unique to Threads.

**Carousels.** Same pattern as Instagram: create 2-20 item containers with `is_carousel_item: true`, then create a carousel container with the children IDs.

**Limits:**

-   Images: JPEG or PNG, 8 MB max, width 320-1440px
-   Videos: MOV or MP4, max 1920px width, 5 minutes max, 1 GB max
-   250 posts per 24-hour period

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, both initiated through the same endpoint.

**Pull from URL.** POST to `/v2/post/publish/video/init/` with `source_info.source` set to `PULL_FROM_URL` and `source_info.video_url`. TikTok fetches the video from your server.

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

**Photos.** Different endpoint: POST to `/v2/post/publish/content/init/` with `media_type: "PHOTO"`. Up to 35 images per post.

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

**Limits:**

-   Videos: MP4, WebM, QuickTime
-   Photos: up to 35 per post
-   6 publish requests per minute per user
-   ~15 posts per day per creator

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. Video metadata and video data are sent in separate requests.

**Step 1 — Initiate.** POST to `https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status` with video metadata (title, description, category, privacy) as JSON. Include `X-Upload-Content-Length` and `X-Upload-Content-Type` headers. Returns a `Location` header with the resumable upload URI.

**Step 2 — Upload.** PUT the video binary to the resumable URI. For large files, send in chunks with `Content-Range` headers.

**Step 3 — Handle interruptions.** Send an empty PUT with `Content-Range: bytes */{total}` to check which bytes were received, then resume from the last byte.

**Custom thumbnails.** Separate POST to `/upload/youtube/v3/thumbnails/set?videoId={id}`. Costs 50 quota units.

**Processing.** Poll `https://www.googleapis.com/youtube/v3/videos?id={id}&part=status,processingDetails`. Processing statuses: `processing`, `succeeded`, `failed`, `terminated`.

**Limits:**

-   Max file size: 256 GB
-   Quota: uploads use a dedicated bucket of 100 `videos.insert` calls/day; other endpoints share 10,000 units/day
-   Formats: most video formats accepted
-   Thumbnails: JPEG, PNG, GIF, BMP, max 2 MB

Full details in the [YouTube publishing guide](/blog/youtube-upload-api-guide/).

## What are the media format requirements per platform?

### Images

Platform

Formats

Max size

Notes

X

JPEG, PNG, WebP

5 MB

Up to 4 per post

Instagram

JPEG only

—

No extended JPEG (MPO, JPS)

Facebook

Common formats

—

Via URL or direct upload

LinkedIn

JPG, PNG, GIF

36M pixels

Up to 20 per post

Threads

JPEG, PNG

8 MB

Width 320-1440px

TikTok

Common formats

—

Up to 35 per post

YouTube

JPEG, PNG, GIF, BMP

2 MB

Thumbnails only (1280x720 recommended)

### Videos

Platform

Formats

Max size

Max duration

Upload method

X

MP4, MOV

512 MB

—

Chunked (INIT/APPEND/FINALIZE)

Instagram

MP4

—

Varies by type

URL pull or resumable upload

Facebook

MP4

—

—

Resumable Upload API

LinkedIn

MP4

500 MB

30 minutes

Chunked with ETag tracking

Threads

MOV, MP4

1 GB

5 minutes

URL pull

TikTok

MP4, WebM, MOV

—

Varies

URL pull or chunked upload

YouTube

Most formats

256 GB

12 hours (default)

Resumable PUT

## What video encoding requirements does each platform enforce?

Upload protocols are only half the problem. Each platform also enforces specific codec and encoding requirements — and most will reject your video outright if it does not match, often with an unhelpful error message.

X rejects videos encoded with VP9. If your source file is a WebM with VP9, the upload will fail at the FINALIZE step or during processing. LinkedIn only accepts MP4 with H.264. Instagram requires H.264 video with AAC audio. Threads has the same constraint. TikTok accepts more container formats but still expects H.264 or HEVC internally.

In practice, this means any system that publishes video across multiple platforms needs a transcoding layer. Before uploading, you have to inspect the source file’s codec, container format, audio encoding, frame rate, and resolution — then transcode as needed per platform.

For a direct API integration, this typically means running ffmpeg:

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

This converts a VP9/WebM file to H.264/AAC in an MP4 container — the format accepted by every platform. But codec is just the start. Some platforms require specific frame rate ranges (TikTok: 23-60 FPS, Threads: 23-60 FPS). Some require specific pixel dimensions (Threads: max 1920px width, Instagram: aspect ratio constraints). Some care about color space (Instagram: sRGB). A single ffmpeg command rarely covers all platforms at once.

Building a cross-platform publishing system means building a transcoding pipeline. You need to detect the source format, determine what each target platform requires, transcode to the right spec for each, and handle failures when the source material cannot be converted cleanly. This is infrastructure work that has nothing to do with social media APIs — but without it, your uploads will fail.

Postproxy handles transcoding automatically. You provide any common video format and Postproxy converts it to the right codec, container, frame rate, and resolution for each target platform before uploading. Your system never has to run ffmpeg or know what X rejects and LinkedIn requires.

## How does processing and status polling differ across platforms?

After upload, most platforms require processing time before media is ready. Each platform surfaces processing status differently.

-   **X**: Poll `/2/media/upload/{media_id}`, wait `check_after_secs` between requests
-   **Instagram**: Poll `/{container-id}?fields=status_code`, once per minute, max 5 minutes
-   **Facebook**: Video processing is generally fast, no explicit polling endpoint documented for page videos
-   **LinkedIn**: Poll `/rest/images/{urn}` or `/rest/videos/{urn}` for `AVAILABLE` status
-   **Threads**: Wait minimum 30 seconds before publishing, then poll container status
-   **TikTok**: Poll `/v2/post/publish/status/fetch/` with `publish_id`. Under 30 seconds for files up to 512 MB
-   **YouTube**: Poll `/youtube/v3/videos?part=status,processingDetails`. Processing time varies by video length and resolution

Each polling mechanism has different intervals, different status values, and different timeout behaviors. Building a generic “wait for ready” function across all platforms requires per-platform adapters.

## The same video, through Postproxy

Here is what uploading and 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"]     }'
```

One request. One media URL. Postproxy handles the chunked upload to X, the container creation on Instagram and Threads, the resumable upload to Facebook, the ETag-tracked upload to LinkedIn, the pull-from-URL or chunked upload to TikTok, and the resumable PUT to YouTube.

## What Postproxy handles

Postproxy abstracts away seven different upload protocols so your system does not have to implement any of them:

-   Chunked INIT/APPEND/FINALIZE for X
-   Container creation and URL-based media pull for Instagram and Threads
-   Resumable Upload API with file handles for Facebook
-   Chunked upload with ETag tracking and finalization for LinkedIn
-   Pull-from-URL and chunked upload for TikTok
-   Resumable upload sessions for YouTube
-   Automatic video transcoding to each platform’s required codec, container, frame rate, and resolution
-   Per-platform format validation, processing status polling, and timeout handling

Your system provides a URL. Postproxy handles transcoding, uploading, and every platform-specific implementation detail.

Connect your accounts and start publishing 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-integrationautomationinfrastructureworkflows

```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": "Image & Video Uploads Across Social APIs",
    "description": "How each social media platform handles media uploads differently — chunked, container, resumable, pull-from-URL — and what it takes to support all of them.",
    "datePublished": "2026-02-11T00: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/handling-image-and-video-uploads-across-social-media-apis.png"
    ],
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://postproxy.dev/blog/handling-image-and-video-uploads-across-social-media-apis/"
    },
    "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/handling-image-and-video-uploads-across-social-media-apis/",
    "url": "https://postproxy.dev/blog/handling-image-and-video-uploads-across-social-media-apis/",
    "name": "Image & Video Uploads Across Social APIs | Postproxy",
    "description": "How each social media platform handles media uploads differently — chunked, container, resumable, pull-from-URL — and what it takes to support all of them.",
    "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": "Image & Video Uploads Across Social APIs"
      }
    ]
  }
]
```
