Handling image and video uploads across social media APIs

How each social media platform handles media uploads differently — chunked, container, resumable, pull-from-URL — and what it takes to support all of them.

Handling image and video uploads across social media APIs

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.

The five upload models

Across eight 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.

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.

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.

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.

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.

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.

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: 10,000 units/day per project. Uploads cost 100 units each (~100 uploads/day)
  • Formats: most video formats accepted
  • Thumbnails: JPEG, PNG, GIF, BMP, max 2 MB

Full details in the YouTube publishing guide.

Format requirements at a glance

Images

PlatformFormatsMax sizeNotes
XJPEG, PNG, WebP5 MBUp to 4 per post
InstagramJPEG onlyNo extended JPEG (MPO, JPS)
FacebookCommon formatsVia URL or direct upload
LinkedInJPG, PNG, GIF36M pixelsUp to 20 per post
ThreadsJPEG, PNG8 MBWidth 320-1440px
TikTokCommon formatsUp to 35 per post
YouTubeJPEG, PNG, GIF, BMP2 MBThumbnails only (1280x720 recommended)

Videos

PlatformFormatsMax sizeMax durationUpload method
XMP4, MOV512 MBChunked (INIT/APPEND/FINALIZE)
InstagramMP4Varies by typeURL pull or resumable upload
FacebookMP4Resumable Upload API
LinkedInMP4500 MB30 minutesChunked with ETag tracking
ThreadsMOV, MP41 GB5 minutesURL pull
TikTokMP4, WebM, MOVVariesURL pull or chunked upload
YouTubeMost formats256 GB12 hours (default)Resumable PUT

Video encoding: the hidden requirement

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:

Terminal window
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.

Processing and status polling

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 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"]
}'

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.

Ready to get started?

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