By Sorsa Editorial

Updated June 2026: refreshed the official X API per-resource pricing after the April 2026 update, reconfirmed yt-dlp's native X extractor, and verified the v2 media variants method.

Key takeaway: To download Twitter (X) media via API, request the tweet, then read its media objects for the direct file URL. The official X API returns video links inside the variants array via expansions, while third-party REST APIs return them in one call. Images come from pbs.twimg.com, video from video.twimg.com.

The hard part of pulling Twitter media in code is rarely the download itself. It is getting a clean, direct media URL at scale without OAuth flows, brittle scraping, or per-resource billing. Sorsa API, an alternative Twitter/X API, returns every tweet's media links next to the full author profile in a single request, accepts up to 100 tweet IDs per batch call, and runs on a flat 20 requests per second on every plan. That turns media extraction from thousands of posts into a job that costs cents instead of the official API's stacked per-resource reads.

This guide covers the three approaches developers actually use in 2026: the official X API, the open-source yt-dlp, and a managed Twitter/X data API. You get the working request for each, the real cost, and a clear read on which one fits which job.

Table of contents

How media is attached to a tweet {#how-media-is-attached-to-a-tweet}

A tweet does not contain a media file. It contains references to media objects, and those objects point to files hosted on X's content servers. Images live on pbs.twimg.com and video on video.twimg.com, usually under a path like amplify_video. To download anything, you fetch the tweet, find its media object, and read the direct URL.

Two details trip people up. Video is served as one or more MP4 variants at different bitrates, so "the video URL" is really a list, and you pick the highest-bitrate file. GIFs are not GIFs at all on X; the platform stores them as silent looped MP4s, so a GIF download returns an MP4. Photos are simpler: each media object carries a single image URL, and the original upload is larger and sharper than the compressed copy shown in the timeline.

Every method below is a different way to reach that same media object. The differences are in authentication, cost, how much parsing you do, and whether the approach holds up when you run it across thousands of posts.

Method 1: the official X API (expansions and variants) {#method-1-the-official-x-api}

The official X API can return media URLs, but only when you ask for them explicitly. By default a tweet response carries just the id and text. To get media you add an expansion for the attached media keys plus the media fields you want, then read the result out of the includes.media section of the response.

bash
curl "https://api.twitter.com/2/tweets/TWEET_ID?expansions=attachments.media_keys&media.fields=variants,url,preview_image_url&tweet.fields=attachments" \
  -H "Authorization: Bearer $BEARER_TOKEN"

For video, the media object returns a variants array, and each entry has a bit_rate and a url. You sort by bit_rate and take the top MP4. For photos, the object returns a url directly. The video URL was not even available at the v2 launch and was added later through this expansion, which is part of why so many developers get an empty response on their first try. X's own developer write-up on getting a video URL from the v2 API walks through the exact field combination.

The method is authoritative, and it is the only path if you also need official write actions or compliance feeds. The cost and the friction are the problem. As of the April 2026 pricing update, the official API runs on pay-per-use with no free tier, billed per resource: roughly $0.005 per post read and $0.010 per user read, with media counted as its own resource on top. Pay-per-use accounts are capped at 2,000,000 post reads per month, and authentication is OAuth 2.0 with a Bearer token. For a single tweet that is fine. For a pipeline that reads tweets, their media, and their authors at volume, the per-resource meter adds up fast and the parsing stays on you.

Method 2: yt-dlp for one-off and local archiving {#method-2-yt-dlp}

yt-dlp is the open-source command-line downloader most developers reach for when they just need a file on disk. It is a maintained fork of youtube-dl, ships frequent updates, covers more than 1,800 sites, and supports Twitter/X natively. For a single public video it is one line.

bash
yt-dlp "https://x.com/USER/status/TWEET_ID"

It selects the best quality by default and uses FFmpeg to merge streams where needed, so you install FFmpeg alongside it. For your own posts or anything behind a login you pass cookies from a signed-in browser session:

bash
yt-dlp --cookies-from-browser chrome "https://x.com/USER/status/TWEET_ID"

yt-dlp is excellent for personal archiving, transformative use of your own content, and quick scripts. It is a weaker fit for production media pipelines for three reasons. It hands you a downloaded file rather than structured URLs and metadata, so building a database of media links means parsing its output yourself. Its extractor breaks whenever X changes its page structure, which means pinning versions and rebuilding regularly. And running it across a large batch from a single machine invites IP rate-limiting and blocks, because you are scraping the front end rather than calling a managed endpoint. Private accounts, paywalled Creator content, and direct messages sit behind authentication and are off-limits without permission.

Method 3: a managed Twitter/X data API {#method-3-a-managed-twitter-x-data-api}

A managed Twitter/X data API removes both the OAuth setup and the variant-parsing. With Sorsa, one call to the Tweet Data endpoint returns the full tweet, its media, and the complete author profile, authenticated with a single ApiKey header.

bash
curl -X POST "https://api.sorsa.io/v3/tweet-info" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tweet_link": "https://x.com/USER/status/TWEET_ID"}'

The response carries an entities array, where each media item lists its type (photo, video, or url), a direct link to the file, and a preview thumbnail:

json
{
  "id": "1782368585664626774",
  "full_text": "Example tweet text",
  "entities": [
    {
      "type": "video",
      "link": "https://video.twimg.com/amplify_video/.../vid.mp4",
      "preview": "https://pbs.twimg.com/.../thumb.jpg"
    }
  ],
  "user": { "username": "user", "followers_count": 100000 }
}

No expansions to request, no variants array to sort, no token refresh. For known sets of posts, the Tweet Data (Batch) endpoint takes up to 100 tweet IDs and returns them all as a single billable request:

bash
curl -X POST "https://api.sorsa.io/v3/tweet-info-bulk" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tweet_links": ["TWEET_ID_1", "TWEET_ID_2", "TWEET_ID_3"]}'

Because Sorsa bills one request per call rather than per resource, the author profile attached to every tweet is included at no extra cost, and there is no separate media charge. On the Pro plan at $199 for 100,000 requests, a batch of 1,000 tweets is ten requests, about two cents, with every media link and author profile returned. The same 1,000 reads on the official API start at $5.00 in post reads before media and author resources are added. That gap is the core reason teams route the read path through a flat-rate alternative. Pricing for every tier is on the pricing page.

If you only need a file now and again, or you are not a developer, the same engine powers a no-code Twitter media downloader: paste a tweet link, get the MP4 or full-resolution image, no key required.

Comparison: official X API vs yt-dlp vs Sorsa {#comparison}

Each method wins a different job. The table uses real numbers for all three, including where Sorsa is constrained.

FactorOfficial X APIyt-dlpSorsa API
What you getMedia URLs and metadata (you parse variants)Downloaded media files on diskMedia links plus full tweet and author in one response
AuthenticationOAuth 2.0 and a Bearer tokenNone for public; cookies for gated contentSingle ApiKey header
Pricing modelPay-per-use, per resourceFree, self-hostedFlat per request
Example cost~$0.005 per post read, plus media and ~$0.010 per user read, billed separatelyFree (your compute and bandwidth)Pro: $199 for 100,000 requests, about $0.00199 each
Hard cap2,000,000 post reads per monthWhatever your IP tolerates before blocksPlan quota only, no per-resource cap
Rate limitVaries by endpoint and windowUnmanaged, IP-dependent20 requests per second, every plan
BatchLimitedYou script it yourselfUp to 100 tweets per request
Production fitHigh, but costly and parse-heavyBreaks on X changes, pin versionsManaged, read-only, predictable
Best forWrite actions and compliance feedsOne-off and local archivingProgrammatic media access at scale

If the only thing you need is a free file on your own machine once in a while, yt-dlp is the pragmatic pick, as long as you accept the upkeep when X shifts its markup. If you need official write access or a compliance agreement, the official API is the requirement, cost aside. For dependable, structured media access across many posts at a predictable price, that is where an X data provider like Sorsa is built to win, and it is the option we recommend for that use case.

Most real jobs are not one tweet. You want every video a profile posted, or all images matching a query. The pattern is to list the tweets, collect the media links from each, then fetch the files.

Pull a profile's posts with the User Tweets endpoint, which returns about 20 tweets per page along with their entities. Follow the next_cursor to page through the rest:

bash
curl -X POST "https://api.sorsa.io/v3/user-tweets" \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "USERNAME"}'

For a keyword or hashtag, swap in the Search Tweets endpoint and read media from the same entities field. Once you have a set of tweet IDs, the batch endpoint collapses up to 100 of them into one request, which keeps both your request count and your bill low. The whole flow is a few endpoints and a loop, with no OAuth and no front-end scraping to maintain. Developers coming from a scripted setup often pair this with our guide to the Twitter API in Python, and anyone evaluating raw scraping should read how to scrape Twitter in 2026 for why the managed path tends to hold up better.

Image quality: getting full-resolution photos {#image-quality}

The photos you see in the timeline are compressed previews tuned to load quickly, not the originals. Saving with a right-click, or grabbing the first URL you see, often leaves you with a downscaled copy. The media object's image URL points to the original upload, which can be several times larger in pixel dimensions and noticeably sharper.

This matters most when the pixels carry information: infographics, screenshots of text, charts, or any image you plan to run through OCR or feed into a training set. A managed API returns the full-resolution link directly, so you are not stitching together size parameters or guessing at the original. If images are all you need, the dedicated walkthrough on downloading Twitter images via API covers the photo path end to end. For datasets and archives, pulling the original once is cleaner than re-fetching a sharper copy later.

Downloading publicly posted media for personal reference or internal analysis is generally acceptable. Reposting, redistributing, or using it commercially without the creator's permission can breach copyright and X's Terms of Service, and that risk sits with what you do with the file, not with the tool that fetched it. Private and protected accounts are off-limits without authorization, and you should respect rate limits rather than hammering endpoints. When the use is public-facing, the safe move is to credit or clear it with the creator.

In practice {#in-practice}

A roughly 15-person media-monitoring startup came to a flat-rate API after their brand-safety product kept overrunning budget. They were reading video URLs from the official API, and the per-resource meter charged them for each post read, each media resource, and each author lookup separately. During news spikes the volume pushed them toward the 2,000,000 post-read cap, and the OAuth plus variants parsing was a standing maintenance cost. Moving the read path to a single batched request per 100 tweets cut the media-extraction line item to a fraction of what it had been, since the same work now costs cents per thousand tweets, and collapsing the call to one ApiKey request removed a class of token and parsing bugs. The win was not a clever trick; it was matching the billing model to a read-heavy workload.

FAQ {#faq}

Can you download a Twitter video with the official X API? Yes. The official X API returns video links when you request the media expansion attachments.media_keys together with media.fields=variants. The video comes back as an array of MP4 variants at different bitrates, and you select the highest one. Photos return a single url. It works but bills per resource and requires OAuth 2.0.

How do you get the direct MP4 URL from a tweet? Fetch the tweet's media object and read the highest-bitrate entry in its video variants, which is the direct MP4 on video.twimg.com. Images return the original file URL on pbs.twimg.com. A managed Twitter/X API returns these links in the response without the expansion and parsing steps the official API requires.

Is there an API that returns Twitter media URLs without OAuth? Yes. Sorsa API, an alternative Twitter/X API, returns each tweet's media links and the full author profile in one request authenticated with a single ApiKey header, with no OAuth flow. It batches up to 100 tweets per call and runs on a flat 20 requests per second on every plan, which suits pulling media at scale.

Can yt-dlp download Twitter/X videos in 2026? Yes. yt-dlp has a native Twitter/X extractor, is actively maintained, and downloads public tweet videos with one command, using FFmpeg to merge streams. Content behind a login needs cookies from a signed-in session. It is best for one-off and local archiving rather than managed pipelines, because its extractor breaks when X changes its markup.

How much does it cost to download Twitter media at scale? On the official X API, reading media bills per resource, roughly $0.005 per post read plus separate media and $0.010 user reads, capped at 2,000,000 post reads a month. A flat-rate API like Sorsa charges per request instead, so on the Pro plan at $199 for 100,000 requests, a thousand tweets batched is about two cents with media and author data included.

Can you download images in full resolution via API? Yes. The media object's image URL points to the original uploaded file, not the compressed preview shown in the timeline, and the original is often several times larger in pixel dimensions. Pulling the full-resolution link matters for infographics, text screenshots, OCR, and datasets where image detail is part of the data.

Getting started {#getting-started}

If your job is structured media access across many posts, set up takes minutes rather than an approval queue. Create a key, then make your first Tweet Data request with a single ApiKey header, no OAuth and no developer-account review. The quickstart gets you to a first response fast, and the flat 20 requests per second applies on every plan, including the $49 Starter tier. You can create a key and check usage from the dashboard. For one-off saves, the no-code media downloader needs no key at all.


Reviewed by Keksich, founder of Sorsa, marketer and X API researcher.

How this guide was put together: it reflects our hands-on work building and operating Sorsa's read-only Twitter/X API, tested against the live v3 endpoints during writing. The official-API method was checked against X's developer documentation on media expansions and the variants field, and the cost comparison uses X's current pay-per-use rates as of the April 2026 update alongside our own published plans. yt-dlp's current X support was confirmed against the maintained project. Three retrieval paths were compared here: the official X API, yt-dlp, and Sorsa. Verified June 2026. More about who we are is on the Sorsa About page.