Skip to main content
This guide walks you through uploading videos and large media files using the v2 chunked upload endpoints. For video or large media uploads, you must:
  1. INIT — POST /2/media/upload/initialize — start the session and get a media_id
  2. APPEND — POST /2/media/upload/{id}/append — upload each chunk
  3. FINALIZE — POST /2/media/upload/{id}/finalize — complete the upload
  4. STATUS — GET /2/media/upload — wait for processing when processing_info is returned
Do not send command=INIT, command=APPEND, or command=FINALIZE to POST /2/media/upload. Those command-style parameters were the previous upload protocol. The v2 flow uses the dedicated paths above. command=STATUS is still used only on the status GET.
Video duration and file size are limited by the authenticated user’s Premium / verified status and by media_category. See size and duration limits. A successful upload can still be rejected when you attach the media_id to POST /2/tweets.

Step 1: Initialize upload (INIT)

Start the upload session. Send a JSON body — not multipart form fields.
cURL
Response:
Use tweet_video for a regular Post. Use amplify_video for Ads creatives. See media categories.

Step 2: Upload chunks (APPEND)

Upload each chunk to POST /2/media/upload/{id}/append. Keep each segment at or below 5 MB (the server maximum is 8 MB). Segments are indexed from 0 and increase by one for each chunk.
An upload session accepts up to 10,000 segments, so valid segment_index values are 0 through 9999. This applies to every media category and account tier. A 16 GB video takes about 2,048 APPEND requests at 8 MB per segment, or about 3,277 at 5 MB.
cURL
Chunking advantages:
  • Improved reliability on slow networks
  • Uploads can be paused and resumed
  • Failed chunks can be retried individually

Pace APPEND requests for large files

APPEND requests count against the POST /2/media/upload/:id/append rate limit. For self-serve tiers (Basic, Pro, and pay-per-use), the limit is 1,875 requests per 15 minutes per user. Enterprise packages have higher limits. Treat the x-rate-limit-limit header as the authoritative limit for your plan. A multi-GB upload can reach this limit before it finishes. An upload session stays valid for 24 hours, and each APPEND response returns the session’s expires_at. Pausing until x-rate-limit-reset doesn’t expire the upload.
  • Read the x-rate-limit-limit, x-rate-limit-remaining, and x-rate-limit-reset headers on each APPEND response.
  • If you receive a 429, wait until the x-rate-limit-reset time, then retry the same segment_index.
  • Don’t restart from segment 0. Resume from the first segment that didn’t succeed, then call FINALIZE after the last segment.
  • Use larger segments (up to 8 MB) to reduce the number of APPEND requests. At 8 MB, a 16 GB upload spans roughly two 15-minute windows at the 1,875-request limit.

Step 3: Finalize upload (FINALIZE)

Complete the upload after all chunks are sent:
cURL
Response:
Example response
If processing_info is returned, proceed to Step 4 to wait for processing. If not, the media is ready to use.

Step 4: Check status (STATUS)

If processing_info was returned, poll until processing completes:
cURL
Processing states: pending → in_progress → succeeded or failed

Step 5: Create Post with media

Once processing is complete, create a Post with the media. Duration and size are checked again against the posting user’s entitlement.
cURL
If the video is longer than the posting user is allowed to attach, the response is 403 Forbidden:
N is the posting user’s duration cap (20 minutes by default, 125 minutes for Premium / verified).

Media categories

If you omit media_category, the upload is treated as Post media (tweet_image, tweet_video, or tweet_gif) based on content type.

Next steps

Best practices

File constraints, codecs, and duration limits

Create Posts

Post with media

Initialize

INIT endpoint reference

Create Post

POST /2/tweets reference