Skip to content
curl-x
twitterclicurlhow-todeveloper

How to Download a Twitter Video With curl (One Command)

Fetch a Twitter/X post URL with curl and get the actual MP4 back — no browser, no API key. One command, real filename, works with wget too.

Share:

Want to try it now? Paste a post link from any supported platform to download its media instantly.

Open Downloader

The short answer: swap the domain in a public tweet URL for curl-x.com and fetch it with curl. Run curl -OJ https://www.curl-x.com/username/status/1234567890123456789 and curl saves the tweet's video (or photo) to disk with a real filename — no browser, no login, no API key. This works because curl-x detects command-line clients and streams the media file directly instead of returning an HTML page.

Key takeaway: curl-x recognizes curl, wget, httpie, aria2, axel, and lftp by their User-Agent on a tweet's bare post URL (/username/status/tweetId) and rewrites the request to a /download route that streams the post's primary media as an attachment with Content-Disposition: attachment; filename="…". It only works on public posts, it only streams one file per request (the post's primary media, not every photo in a multi-image tweet), and there's no /photo/1 or /video/1 shortcut for CLI clients — those paths are browser-only preview pages.

Table of contents

Why this even works

curl-x is named after this trick on purpose. A normal browser visiting a tweet URL gets the usual HTML downloader page — preview, quality picker, download button. But a request from a terminal client identifies itself with a User-Agent containing curl or Wget (the exact version number depends on what's installed on your system), and a small piece of middleware checks for that before the page renders.

When it sees a known CLI User-Agent hitting a bare post path — /username/status/tweetId for Twitter/X, plus equivalent paths for Instagram, Threads, Facebook, Reddit, and TikTok — it rewrites the request internally to that post's /download route. That route resolves the tweet's public media and streams the file straight back, with a proper filename in Content-Disposition, so curl -OJ and wget --content-disposition save an actual video or image instead of an HTML page.

Browsers never see this behavior because Chrome, Safari, and Firefox send Mozilla/… User-Agents. Social-preview crawlers (Twitterbot, facebookexternalhit, Slackbot, Discordbot) are excluded too, so link unfurls still get Open Graph tags instead of a video dump.

The one-command version

Take any public tweet URL and change the domain to curl-x.com:

curl -OJ https://www.curl-x.com/NASA/status/1234567890123456789
  • -O saves the file under its remote name instead of printing bytes to your terminal
  • -J tells curl to prefer the filename from the Content-Disposition header over the URL path

Together, -OJ is what turns this into a real "save to disk" command instead of a stream of binary garbage in your shell. The saved filename looks like curl-x.com_1234567890123456789.mp4 (or .jpg for a photo-only tweet) — the numeric tweet ID is preserved so you can trace it back to the source post later.

If the tweet has no downloadable native video or image (text-only, link card, or an embed that isn't hosted on Twitter/X), you'll get a plain-text 404 body instead of a file: No downloadable media found for this post.

The wget equivalent

wget needs one flag to get the same behavior:

wget --content-disposition https://www.curl-x.com/NASA/status/1234567890123456789

--content-disposition tells wget to name the saved file from the response header instead of guessing from the URL. Without it, wget will save the file as 1234567890123456789 with no extension, which still works but is annoying to open. wget's User-Agent is on the same allowlist as curl, so no other change is needed.

Checking what you'll get first, with curl -I

If you want to confirm a link actually has downloadable media — or check the file size and content type — before pulling the whole thing, send a HEAD request instead of a GET:

curl -I https://www.curl-x.com/NASA/status/1234567890123456789

The download route answers HEAD the same way it answers GET, minus the body, so you'll see headers like:

HTTP/2 200
content-type: video/mp4
content-length: 8421093
content-disposition: attachment; filename="curl-x.com_1234567890123456789.mp4"

A 404 status here with a plain-text body means there's no native media to fetch. A 502 means something went wrong upstream — either the post itself couldn't be fetched (commonly because it's private, deleted, or a momentary rate limit) or the media file's own source failed partway through the request. Either way, you find out before downloading anything, which is handy in scripts where you don't want to write empty or error files to disk.

What about /photo/1 and /video/1?

The curl-x web app has dedicated browser pages for individual items in a tweet — /username/status/tweetId/photo/1, /photo/2, /video/1, and so on — for picking a specific photo or video visually. Worth being direct about a limitation here: those indexed paths are browser-only. They have no sibling /download route and aren't on the CLI detection allowlist, so fetching /photo/1 with curl just returns the HTML preview page, not a file.

If you need a specific item out of a multi-photo tweet rather than whatever curl-x picks automatically, open the tweet's page in a browser and use the quality/photo picker there, or read our guide on downloading multiple photos from a Twitter post. The one-command curl trick is built for the common case — one video, or one hero image — not for batch-selecting individual images from a gallery tweet.

Which media do you actually get on a multi-photo tweet?

curl-x's download route always resolves to the tweet's primary media item — in practice, the first image or video attached to the post — and picks the best downloadable version of that one item: the highest-bitrate MP4 variant for video, or Twitter's "large" (1920px) rendition for a photo — a high-resolution JPEG, though not necessarily the single largest file Twitter has on hand for that image. On a single-video or single-photo tweet this is exactly what you'd expect. On a tweet with four photos, curl -OJ gets you photo #1 only; it will not loop through and fetch all four. If you specifically need every image, the browser UI (or the multi-photo guide linked above) is the right tool, not the curl shortcut.

The ?lang= query and other headers

curl-x's post pages support a ?lang= query parameter that switches the rendered HTML language for browsers (used for hreflang alternates across 19 locales). It has no effect on the CLI download path: the file you get back is the same bytes regardless of ?lang=, and the download route's plain-text error messages stay in English regardless of locale. If you're scripting against curl-x, there's no need to set ?lang= or any locale header — it doesn't change what gets streamed.

Scripting it: a loop over a file of tweet URLs

Because the whole thing is just an HTTP GET with a recognizable User-Agent, it composes naturally into shell scripts. Given a text file urls.txt with one tweet URL per line, swap each twitter.com/x.com URL for curl-x.com, keeping the path identical, then loop:

while read -r url; do
  curlx_url="${url/x.com/curl-x.com}"
  curlx_url="${curlx_url/twitter.com/curl-x.com}"
  curl -sfOJ "$curlx_url" && echo "saved: $curlx_url"
done < urls.txt

-s silences curl's progress meter, -f makes curl fail (non-zero exit, no file written) on HTTP errors like 404/502 instead of saving an error page. This is the same trick behind the curl-x.com/<tweetpath> domain-swap in our tweetpath shortcut guide — that post covers the browser version; this is the terminal version.

Troubleshooting

I get an HTML file instead of a video. Your client isn't sending a recognized CLI User-Agent. Some HTTP libraries default to a generic or browser-like string — set it explicitly, e.g. curl -A "curl/8.0" ... (any string containing curl works, since matching is a substring check), if you're wrapping curl in something that overrides the default.

I get a 400 with "Invalid or unsupported URL." The path doesn't match a real tweet URL — check for a typo in the username, a missing /status/, or a non-numeric tweet ID.

I get a 404 with "No downloadable media found for this post." The tweet is text-only, a link card, or the visible video actually belongs to a quoted post — open the original quoted tweet's URL instead (see why quote tweets sometimes break video downloads). Still not sure if a tweet even has native video? Read how to tell whether a tweet has native video or just an embed before scripting against it.

I get a 502. Most often the post itself couldn't be fetched — private, deleted, suspended, or a momentary rate limit upstream. It can also mean the tweet resolved fine but the media file's own CDN source failed mid-request. Confirm the tweet still loads in a normal browser first; if it does and you still get a 502, it's likely a transient hiccup on the media source, so retry in a moment.

FAQ

Does this need an API key or login?

No. curl-x doesn't use Twitter's official API or any API keys — it works from the same public post URL a browser would use. It only works on public posts; private or protected accounts are never accessible.

Both. Keep the /username/status/tweetId path exactly the same and swap either twitter.com or x.com for curl-x.com — same post, same result.

Can I get a specific photo out of a multi-image tweet with curl?

Not directly. The curl download route always streams the tweet's primary (first) media item. Per-item paths like /photo/2 exist for the browser UI only and aren't wired into CLI detection, so curl won't reach them.

Why does curl -O alone save a file with the wrong name?

-O alone names the file from the URL path, which for a tweet URL is just the tweet ID with no extension. Add -J so curl reads the real filename (with extension) from the Content-Disposition header curl-x sends back.

Will this work in a cron job or CI pipeline?

Yes, as long as the client sends a User-Agent containing curl, wget, httpie, aria2, axel, or lftp. It's a plain HTTPS GET/HEAD request with no session state or cookies required, so it runs fine unattended.

Bottom line

If you have a public tweet URL, downloading its video with curl really is one command: curl -OJ https://www.curl-x.com/username/status/tweetId. Add -J so the filename comes out right, use wget --content-disposition if that's your tool of choice, and curl -I first if you want to check what's there before committing to the download. It streams the tweet's primary media only — for picking a specific image out of a gallery post, or for browsing quality options visually, go back to curl-x in a normal browser.

Ready to download from any platform?

Try curl-x — free, fast, and no login required.

Download Now
Share: