# X (Twitter) Video & Image Downloader (`guezi/x-media-downloader`) Actor

Download every video, photo and GIF from X (Twitter) accounts or single posts. One row per file: direct link, size, format, duration, plus the post text and engagement behind it. Videos at the best available quality, photos at original size. Filter by type, width or date. No login needed.

- **URL**: https://apify.com/guezi/x-media-downloader.md
- **Developed by:** [Kawtar](https://apify.com/guezi) (community)
- **Categories:** Social media, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.90 / 1,000 media file collecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## X (Twitter) Video & Image Downloader

Turn any list of X (Twitter) accounts and post links into a clean download list:
**one row per file**, each with a direct link to the actual video, photo or GIF,
its size, its format and the post it came from. Videos come at the best quality
X publishes, photos at their original size, and every row carries the post text,
the engagement numbers and the account behind it. No X account and no login
needed — paste your list and run it.

### What you can do with it

- **Archive an account's media.** Pull every photo and video an account has
  posted recently into one spreadsheet, ready to feed a download manager.
- **Build a creative swipe file.** Collect the visuals your competitors are
  running, with the like and view numbers next to each file so you can see what
  worked.
- **Grab the media from specific posts.** Paste the links to a handful of posts
  and get every file attached to them, including all four images of a carousel.
- **Source stock-quality footage you already have rights to.** Filter to videos
  at least 1920 pixels wide and take only the high-resolution ones.
- **Feed a training or research set.** Where the author wrote alt text for an
  image, it comes through on the row as a human-written description. Many
  authors write none, so treat it as a bonus rather than a guarantee.
- **Watch a brand's visual output over time.** Schedule a daily run over the
  accounts you track and collect whatever new media appears.

### What you get

One row per file. Real example from a run over `NASA`:

```json
{
  "directUrl": "https://video.twimg.com/amplify_video/2084731730880000000/vid/avc1/1920x1080/A0cYnykQL46G8plU.mp4?tag=29",
  "type": "video",
  "fileExtension": "mp4",
  "width": 1920,
  "height": 1080,
  "fileName": "NASA_2084734580737634576_1.mp4",
  "altText": null,
  "previewImageUrl": "https://pbs.twimg.com/media/HO52IohWAAERmBy.jpg",
  "videoDurationSeconds": 614.8,
  "videoBitrate": 10368000,
  "videoAspectRatio": "16:9",
  "mediaNumber": 1,
  "mediaInPost": 1,
  "mediaPageUrl": "https://x.com/NASA/status/2084734580737634576/video/1",
  "postId": "2084734580737634576",
  "postUrl": "https://x.com/NASA/status/2084734580737634576",
  "postText": "Lunar landers, assemble. In clean rooms across the country, commercial lunar landers are preparing for upcoming missions in support of @NASAMoonBase.",
  "createdAt": "2026-08-04T20:13:51.000Z",
  "likes": 3606,
  "reposts": 523,
  "replies": 180,
  "views": 1335789,
  "isRepost": false,
  "username": "NASA",
  "displayName": "NASA",
  "profileUrl": "https://x.com/NASA",
  "followers": 92245566,
  "inputTarget": "https://x.com/NASA/status/2084734580737634576",
  "scrapedAt": "2026-08-05T21:48:24.056Z"
}
```

A post holding four photos becomes four rows, numbered `1` to `4`, each with its
own link and its own dimensions.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `targets` | array of text | — | **Required.** Accounts and post links, mixed freely in one list. An account name, an `@name`, a profile link or a link to a single post all work. |
| `maxPostsPerProfile` | integer | `50` | How many media-carrying posts to take from each account, newest first. A post can hold up to four files, so this limits posts, not files. |
| `mediaTypes` | multi-select | all three | Which kinds of file to keep: photos, videos, GIFs. |
| `minWidth` | integer | `0` | Skip files narrower than this, measured on the file you would actually download. |
| `postedAfter` | text | — | Keep only media from posts published on or after this date (`YYYY-MM-DD`). |
| `postedBefore` | text | — | Keep only media from posts published on or before this date (`YYYY-MM-DD`). |
| `includeReposts` | boolean | `false` | Also collect files from reposts. Applies to post links as well as accounts, so a link to a repost is skipped while this is off. The original author is credited on the row. |
| `accountsAtOnce` | integer | `3` | How many accounts to work through at the same time. |
| `countryCode` | select | `us` | Which country to browse from. |

### Output fields

| Field | Description |
| --- | --- |
| `directUrl` | The file itself. Open it, or hand it to any download tool. |
| `type` | `photo`, `video` or `gif`. |
| `fileExtension` | `jpg`, `png` or `mp4` — what you will be saving. |
| `width`, `height` | Size of the file being handed over, in pixels. |
| `fileName` | A suggested, safe file name: account, post and position. |
| `altText` | The description the author wrote for the image, when they wrote one. |
| `previewImageUrl` | Poster frame for videos and GIFs. |
| `videoDurationSeconds`, `videoBitrate`, `videoAspectRatio` | Video detail, where X publishes it. |
| `videoStreamUrl` | The streaming version of the same video, for tools that prefer it. |
| `mediaNumber`, `mediaInPost` | Which file this is, and how many the post holds. |
| `mediaPageUrl` | The file's own page on x.com. |
| `postId`, `postUrl`, `postText`, `createdAt` | The post the file came from. |
| `likes`, `reposts`, `replies`, `views` | Engagement on that post at collection time. |
| `possiblySensitive` | `true` when X marks the post as sensitive content. |
| `isRepost`, `repostedBy` | Set when the file reached you through a repost. |
| `username`, `displayName`, `profileUrl`, `followers`, `userId` | The account that published the file. |
| `inputTarget` | The exact value you typed, so results join back to your list. |
| `unavailable`, `unavailableReason` | Set when a target yields nothing, so your input list keeps its shape. |

### Pricing

This actor is **pay per result** and a result is one media file. You are charged
for each file that lands in your dataset and for nothing else — no time-based
charge, and no charge for accounts or posts that turn out to hold no media
beyond the single row that tells you so.

Filters run before anything is charged, so a job restricted to videos at least
1920 pixels wide bills only for the files that clear the bar. A sweep of 20
accounts at 50 posts each typically produces somewhere between 1,000 and 2,500
files, depending on how many carousels those accounts post.

### Limits & what this actor cannot do

- **It gives you links, not the files themselves.** Every row carries a direct
  link that opens in a browser or any download tool. The actor does not store
  copies of the media for you.
- **The files belong to whoever made them.** Being able to download something is
  not permission to republish it. You are responsible for having the rights you
  need for whatever you do with the media.
- GIFs on X are published as short silent video files, so they arrive as `.mp4`
  with `type` set to `gif`, and without a duration.
- Photos arrive at the largest size X stores. That is often, but not always, the
  size the photographer uploaded.
- Live broadcasts have no saveable file and are skipped rather than reported
  with a link that would not download.
- The same file reaching a job twice — a post supplied by link that also appears
  in its author's feed — is returned once, credited to the target you named.
- X publishes a limited window of recent public posts per account, so very large
  accounts return what X makes available rather than a complete history.
- Keyword search across all of X is not part of what X publishes openly, so this
  actor works from the accounts and post links you supply rather than a search
  term. The media *tab* of an account is likewise not publicly available; media
  is collected from the account's posts.
- Protected (private) accounts are not publicly viewable and are reported as
  unavailable rather than returned empty.
- Deleted, suspended and withheld posts are reported as unavailable rather than
  silently skipped.
- Likes, reposts, replies and views are a snapshot at the moment of collection
  and keep changing afterwards.
- X publishes view counts for most recent posts but not for every older one.
  Where X publishes none, `views` is left empty rather than set to zero.
- Speed depends on the size of the job and on X's own response times.
- X's terms govern automated access. You are responsible for using the data
  lawfully, in line with the source site's terms, and for handling any personal
  data in line with applicable privacy law.

### FAQ

**Do I need an X account?**
No. Nothing is required from you beyond the list of accounts and post links you
want media from.

**Does it need my login, password or a paid X subscription?**
No. None of those are used at any point.

**Does it download the files onto my computer?**
It hands you a direct link to each file, plus a suggested name for it. Point a
download manager, a spreadsheet formula or your own workflow at the `directUrl`
column and the files come down at full quality.

**What quality are the videos?**
The best downloadable version X publishes for that post — the same one you would
get from the highest-quality option on the site.

**Can I get every photo an account ever posted?**
Not always. X publishes a limited window of recent posts per account. For most
accounts that is a deep archive; for very high-volume accounts it stops short of
the beginning.

**Can I schedule it?**
Yes. Schedule it on Apify to run hourly, daily or weekly and keep collecting new
media as it appears.

**How do I get the results into my tools?**
Export from Apify as CSV, Excel, JSON, XML or an HTML table, push into Google
Sheets, or pull them straight into your own system.

# Actor input Schema

## `targets` (type: `array`):

What to pull media from. Mix freely: an account name, an @name, a link to a profile, or a link to a single post. Each account contributes the media from its recent posts; each post link contributes just that post's media.

## `maxPostsPerProfile` (type: `integer`):

How many media-carrying posts to take from each account, newest first. One post can hold up to four files, so this is a limit on posts, not on files. Ignored for post links. X publishes only a limited recent window per account, so a high value returns everything X makes available rather than the account's full history.

## `mediaTypes` (type: `array`):

Which kinds of file to return. Leave all three selected to collect everything.

## `minWidth` (type: `integer`):

Skip files narrower than this, measured on the file you would download. Set 0 to keep every size. Files whose size X does not publish are skipped while this is set.

## `postedAfter` (type: `string`):

Only keep media from posts published on or after this date (YYYY-MM-DD). Leave empty for no lower bound. A value that is not a readable date is reported in the log and no lower bound is applied.

## `postedBefore` (type: `string`):

Only keep media from posts published on or before this date (YYYY-MM-DD). Leave empty for no upper bound. A value that is not a readable date is reported in the log and no upper bound is applied.

## `includeReposts` (type: `boolean`):

Also collect files from reposts. This applies to post links as well as accounts, so while it is off a link to a repost is reported as skipped rather than collected. The original author is credited on every row.

## `accountsAtOnce` (type: `integer`):

How many accounts to work through in parallel. Raise it for large lists.

## `countryCode` (type: `string`):

The country to browse from. Affects which posts X shows publicly in a few regions.

## Actor input object example

```json
{
  "targets": [
    "nasa"
  ],
  "maxPostsPerProfile": 50,
  "mediaTypes": [
    "photo",
    "video",
    "gif"
  ],
  "minWidth": 0,
  "postedAfter": "2026-01-01",
  "postedBefore": "2026-12-31",
  "includeReposts": false,
  "accountsAtOnce": 3,
  "countryCode": "us"
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "targets": [
        "nasa",
        "https://x.com/NASA/status/2084734580737634576"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("guezi/x-media-downloader").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "targets": [
        "nasa",
        "https://x.com/NASA/status/2084734580737634576",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("guezi/x-media-downloader").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "targets": [
    "nasa",
    "https://x.com/NASA/status/2084734580737634576"
  ]
}' |
apify call guezi/x-media-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,guezi/x-media-downloader"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nfMV6BNhAY9CiBueU/builds/5uIMlEuTQDjL1aBhX/openapi.json
