X API Chunked Media Upload
How chunked media upload works on the X API, and when you need it.
X requires video, animated GIFs, and images over roughly 5 MB to go through media/upload in four steps: INIT declares the byte size and media_category and returns a media_id, APPEND sends chunks up to 5 MB each, FINALIZE starts processing, and STATUS is polled until succeeded. PostGate’s media endpoint wraps the whole state machine.
X still requires video, animated GIFs, and any image over roughly 5 MB to go through media/upload in chunks rather than as one request body. It's a four-step protocol — INIT, APPEND, FINALIZE, STATUS — and PostGate's media endpoint wraps it so you don't implement the state machine yourself. This page covers both: the underlying X protocol and PostGate's equivalent.
Why does chunked upload exist at all?
What are the four steps on X's side?
INIT declares the total byte size and media_category (e.g. tweet_video) and returns a media_id. APPEND sends each chunk — up to 5 MB per segment — tagged with a segment_index, repeated until every byte is uploaded. FINALIZE tells X the upload is complete and processing can start. STATUS is then polled (for video and GIF; images finalize synchronously) until processing_info.state reaches succeeded, at which point the media_id can be attached to a post.# INIT
POST /1.1/media/upload.json
command=INIT&total_bytes=52428800&media_type=video/mp4&media_category=tweet_video
-> { "media_id_string": "10000000000000001" }
# APPEND (repeat per chunk)
POST /1.1/media/upload.json
command=APPEND&media_id=10000000000000001&segment_index=0
media=<chunk-bytes>
# FINALIZE
POST /1.1/media/upload.json
command=FINALIZE&media_id=10000000000000001
-> { "processing_info": { "state": "pending", "check_after_secs": 5 } }
# STATUS (poll)
GET /1.1/media/upload.json?command=STATUS&media_id=10000000000000001
-> { "processing_info": { "state": "succeeded" } }PostGate's equivalent: one media resource, not a state machine
ready only once the file is actually attachable — the polling loop happens on PostGate's side, not yours. See the Media resource reference for the full multipart flow shared across every platform.curl -X POST https://postgate.studio/api/media \
-H "Authorization: Bearer $POSTGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "upload",
"filename": "launch-clip.mp4",
"mime": "video/mp4",
"bytes": 52428800
}'What X actually accepts, enforced up front
Attach it to a post once ready
POST /api/posts call used for any platform — see X API: Post a Tweet for the full request shape.curl -X POST "https://postgate.studio/api/posts?profile_group_id=grp_acme123" \
-H "Authorization: Bearer $POSTGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": { "body": "New clip is live." },
"profiles": ["pro_x123"],
"media": ["med_01JXYZ"]
}'Frequently asked questions
When is chunked upload required on X?
For video, animated GIFs, and any image over roughly 5 MB — X requires those to go through media/upload in chunks rather than as one request body.
Do I need to poll STATUS for image uploads?
No — images finalize synchronously. STATUS polling applies to video and GIF, which go through asynchronous processing (transcoding, thumbnail extraction) after FINALIZE; poll until processing_info.state reaches succeeded, at which point the media_id can be attached to a post.
What media limits does X enforce for a post?
For an X target, PostGate checks images up to 5 MB, MP4 video up to 512 MB and 140 seconds, and a maximum of 4 media items per post before it spends a single write call. A file that fails validation never reaches X, so it never gets billed as a post attempt.
How does PostGate replace X’s chunked upload state machine?
Create a media reservation with filename, MIME type, and byte count, upload each part to the signed URL you get back, then complete it with the returned ETags. PostGate reruns X’s own processing and reports ready only once the file is actually attachable — the polling loop happens on PostGate’s side, not yours.
