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.
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:
- Chunked upload (X) — INIT, APPEND in chunks, FINALIZE, then poll for processing
- Container model with URL pull (Instagram, Threads) — Provide a public URL, the platform fetches the file
- Resumable upload with file handle (Facebook) — Upload to a session endpoint, get a handle, attach to a post
- Chunked upload with ETag tracking (LinkedIn) — Upload in parts to assigned URLs, track ETags, finalize
- 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:
- POST to
/{app-id}/uploadswithfile_name,file_length,file_type. Returns anupload_session_id - POST the binary to
/upload:{upload_session_id}withfile_offset: 0header. Returns a file handleh - POST to
/{page-id}/videoswithfbuploader_video_file_chunkset 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:
- POST to
/rest/images?action=initializeUploadwith the owner URN. Returns anuploadUrland image URN - PUT the binary to the
uploadUrl - Poll
/rest/images/{image-urn}until status isAVAILABLE
Supported: JPG, PNG, GIF (up to 250 frames). Max 36,152,320 pixels.
Videos. Four-step process:
- POST to
/rest/videos?action=initializeUploadwith owner URN,fileSizeBytes,uploadThumbnail: true. Returns a video URN,uploadToken, anduploadInstructions— an array of parts with byte ranges and upload URLs - Upload each 4 MB chunk to its assigned URL. Save the
ETagfrom each response header - Optional: upload custom thumbnail to
thumbnailUploadUrl, captions tocaptionsUploadUrl - POST to
/rest/videos?action=finalizeUploadwith the video URN,uploadToken, anduploadedPartIds(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
| Platform | Formats | Max size | Notes |
|---|---|---|---|
| X | JPEG, PNG, WebP | 5 MB | Up to 4 per post |
| JPEG only | — | No extended JPEG (MPO, JPS) | |
| Common formats | — | Via URL or direct upload | |
| 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) |
| MP4 | — | Varies by type | URL pull or resumable upload | |
| MP4 | — | — | Resumable Upload API | |
| 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 |
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:
ffmpeg -i input.webm -c:v libx264 -c:a aac -movflags +faststart output.mp4This 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}, waitcheck_after_secsbetween 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}forAVAILABLEstatus - Threads: Wait minimum 30 seconds before publishing, then poll container status
- TikTok: Poll
/v2/post/publish/status/fetch/withpublish_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:
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.