X (Twitter) API Guide — 2026
X (Twitter) API Media Upload: Chunked Upload, Processing, and Why Video/GIF Posts Fail (2026)
Posting a plain text update through the X API is a single call. Posting a photo, GIF, or video is not — media has to go through a completely separate upload session, with its own authentication requirements, its own format rules per media type, and in most cases an asynchronous processing queue that has to finish before the media can ever be attached to a post. Most integration failures we see with X API media are not bugs in the posting endpoint. They happen upstream, in the upload session itself.
Why Media Upload Is a Separate System, Not a Post Parameter
You cannot attach a raw image or video file to a create-post call. Per X's own API reference, media has to be uploaded first through POST /2/media/upload, which returns a media_id — and only that ID gets passed into the post creation request afterward. For a single image under 5 MB this can be a one-shot call. For video or GIF, X's documentation requires the full chunked workflow: initialize a session, upload the file in pieces, finalize it, and then — for video and GIF specifically — wait for server-side processing to report success before the media can be used at all.
That processing step is the part most self-built integrations get wrong. Skip it, and the post creation call fails with a media error even though the upload itself returned a success response.
The Four Stages a Video or GIF Upload Has to Pass Through
According to X's chunked media upload reference, every non-trivial media upload moves through four named stages before it is postable:
1
INIT
Opens the upload session and returns a media_id. Requires declaring the exact media type, total file size, and the correct media_category upfront.
2
APPEND
Uploads the file in sequential, indexed segments rather than one payload — built for reliability on unstable connections, not for convenience.
3
FINALIZE
Closes the session. For images this can return a ready-to-use media_id immediately. For video and GIF it returns a processing_info block instead.
4
STATUS
Must be polled whenever processing_info is present. X's documented states move from pending to in_progress to succeeded or failed — attach too early and the post call rejects the media.
X's own documentation also flags that both the upload session and the finalized media carry expiry windows measured in seconds — a media_id that sits unused past that window can no longer be attached to a post, which is a common failure point in integrations that queue posts instead of publishing them close to upload time.
Where the Format Rules Trip Up Real Integrations
X's media best-practices reference sets hard technical limits per media type — and the limits are not the same for every upload path. The plain, non-chunked upload endpoint only reliably accepts still images and subtitle files under X's own request schema; video and GIF are documented as requiring the chunked path specifically.
Images
- Formats: JPG, PNG, GIF, WEBP
- Size limit: up to 5 MB per file
- Per post: up to 4 images, or 1 GIF, or 1 video — never mixed
Animated GIF
- Size limit: up to 15 MB
- Resolution / frame ceilings: documented pixel and frame-count limits apply even under the size cap — a GIF can pass the size check and still fail at post creation
- Large GIFs: require the chunked path with the correct media_category to process asynchronously
Video
- File size: up to 512 MB
- Duration: between 0.5 and 140 seconds
- Encoding: specific codec, pixel-format, and audio-profile requirements documented by X — non-conforming encodes are a frequent silent processing failure
Common Reasons Media Uploads Fail or Get Rejected
!
Post creation call fires before the STATUS check confirms processing has actually succeeded.
!
Wrong or missing media_category on the INIT request — X's schema ties allowed categories to the upload path being used.
!
Video or GIF sent through the one-shot upload path instead of the chunked workflow it requires.
!
Encoding that passes a basic file-size check but fails X's documented codec, aspect-ratio, or pixel-format constraints.
!
Media_id used after its upload or finalize expiry window has already closed.
!
Authentication mismatch between the media upload call and the post creation call — X's own endpoint spec lists more than one accepted token type for media.write, and mixing them incorrectly across a single flow is a recurring integration bug.
What a Correctly Built Media Upload Flow Delivers
One authenticated upload path that handles images, GIFs, and video without silent processing failures
Correct media_category and format handling per media type, matched to X's documented constraints
Processing status checked and confirmed before every post creation call, not assumed
Media published inside the expiry window instead of failing on queued or delayed posts
None of this is guesswork you should have to repeat for every app. If your
X API developer account and
webhook or automation layer are already in place, the media upload flow is usually the last integration gap standing between a working demo and a product that reliably posts photos and video for real users.