# Twitter Video Downloader - X Video to MP4, Tweet Video Saver (`epctex/twitter-video-downloader`) Actor

Download Twitter and X videos as MP4 from tweet URLs or IDs. 720p costs $0.40 per 1,000 seconds, no proxy costs, 3 quality tiers. Saves to Apify, AWS S3, Google Cloud, Spaces or Azure for marketing, newsroom and research teams.

- **URL**: https://apify.com/epctex/twitter-video-downloader.md
- **Developed by:** [epctex](https://apify.com/epctex) (community)
- **Categories:** Social media, Videos
- **Stats:** 251 total users, 0 monthly users, 96.4% runs succeeded, 14 bookmarks
- **User rating**: 4.91 out of 5 stars

## Pricing

from $0.20 / 1,000 sd video seconds

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Twitter Video Downloader: X Video to MP4

Twitter Video Downloader is an Apify actor that downloads MP4 videos from tweet URLs (`twitter.com`, `x.com`) or tweet IDs into Apify storage, AWS S3, Google Cloud, DigitalOcean Spaces or Azure. 720p video costs **$0.0004 per second, or $0.40 per 1,000 seconds**, with no platform or proxy costs.

The Twitter Video Downloader accepts 2 input identifiers, offers 3 quality tiers, writes to 5 storage targets, returns 12 fields per video and bills through 3 pay-per-event charge events. Every row carries 1 of 6 status values and states exactly what it cost.

### What Does the Twitter Video Downloader Extract?

The Twitter Video Downloader extracts the video files attached to a tweet and delivers each one as a stored MP4 with a stable download link. The table below lists the 6 capabilities the actor provides.

| Capability | Twitter Video Downloader behavior |
|---|---|
| Video file | Downloads the `video/mp4` variant of each video in the tweet. HLS playlists (`application/x-mpegURL`) are never selected. |
| Quality tier | Picks `low`, `medium` or `high` from the tweet's own bitrate ladder. |
| Re-hosting | Stores the file in the chosen storage target, so the output link replaces Twitter's short-lived media URL. |
| Multi-video tweets | Downloads every video in a tweet, numbered by `videoPosition` starting at 1. |
| Per-row cost | Writes `durationSeconds`, `unitPricePerSecond` and `totalCost` on every charged row. |
| Per-row status | Writes 1 of 6 status values on every row, including tweets that produced no video. |

The Twitter Video Downloader returns video files only. Tweet text, author profiles, engagement counts and thumbnails are outside its output.

### Which Tweet Inputs Does the Twitter Video Downloader Accept?

The Twitter Video Downloader accepts 2 input identifiers: tweet URLs in `videoUrls` and raw tweet IDs in `videoIds`. A run needs at least 1 entry across the 2 lists, and both lists can be combined in one run.

- **`videoUrls`** takes tweet URLs such as `https://x.com/tina__nigro/status/1677753111161569281`. The URL needs an `http` or `https` scheme, a `twitter` or `x` hostname label (so `twitter.com`, `x.com` and `mobile.twitter.com` all resolve), and a `/status/{digits}` path.
- **`videoIds`** takes numeric tweet IDs such as `1677753111161569281`. The Twitter Video Downloader reports each ID as `https://x.com/i/status/{id}` in `sourceUrl`.

The Twitter Video Downloader removes duplicates by tweet ID across both lists, so a `twitter.com` link, an `x.com` link and a raw ID pointing to the same tweet are downloaded and charged once. The first occurrence keeps its `queryIndex`.

### Who Uses the Twitter Video Downloader?

Listed below are the 5 roles that run the Twitter Video Downloader against X (Twitter) video content.

- **Social media and marketing teams** archive campaign and competitor videos to their own S3 or Google Cloud bucket, then read `downloadUrl` and `sourceUrl` to link each stored file back to its tweet.
- **Media monitoring and newsroom teams** collect videos from breaking-news tweets by URL, then use `status` values `unavailable` and `noVideoContent` to separate deleted tweets from tweets without video.
- **Machine learning and research teams** build video datasets at a fixed `quality` tier, then read `durationSeconds` and `videoPosition` to index clips per tweet.
- **Compliance and legal archiving teams** preserve evidence copies in Azure or DigitalOcean Spaces under a set `azurePath` or `digitalOceanPath`, then keep `jobId` as the download record.
- **Developers and automation builders** call the Twitter Video Downloader through the Apify API on a paid plan with `videoIds`, then reconcile spend per row with `totalCost` and `inputSource.queryIndex`.

Each of these roles pays per second of video delivered, and the rate depends on resolution.

### What Does the Twitter Video Downloader Cost?

The Twitter Video Downloader charges 3 event types, one per resolution band: **$0.0002, $0.0004 and $0.0008 per second of video**. Pricing is pay-per-event with no platform or proxy costs on top.

The rate card below lists the 3 charge events.

| Event | Resolution (short side) | Price per second |
|---|---|---|
| `tw-video-download-sd` | below 720p | $0.0002 |
| `tw-video-download-hd` | 720p up to below 1080p | $0.0004 |
| `tw-video-download-fhd` | 1080p and above | $0.0008 |

The Twitter Video Downloader applies 4 billing rules:

- **Duration** is rounded up to the next whole second, with a minimum of 1 second. A clip with no reported duration, such as an animated GIF, is charged as 1 second.
- **Resolution** is read from the delivered file, not from the requested `quality`. `high` on a tweet whose best file is 480p is charged at the SD rate. When the file's frame size cannot be read, the event follows `quality`: `low` bills SD, `medium` bills HD, `high` bills FHD.
- **Orientation** does not change the band, because the short side is measured. A vertical 720x1280 video bills as HD.
- **Timing** is once per video, right after the video is confirmed downloadable and right before its download job starts.

#### Twitter Video Downloader Cost Examples

The table below prices 5 scenarios against the rate card above.

| Scenario | Charge event | Calculation | Total cost |
|---|---|---|---|
| 13-second 720p clip | `tw-video-download-hd` | 13 × $0.0004 | **$0.0052** |
| 60-second 720p video | `tw-video-download-hd` | 60 × $0.0004 | **$0.024** |
| 2-minute 1080p video | `tw-video-download-fhd` | 120 × $0.0008 | **$0.096** |
| Animated GIF, no duration, 480p | `tw-video-download-sd` | 1 × $0.0002 | **$0.0002** |
| 1,000 videos of 30 seconds at 720p | `tw-video-download-hd` | 30,000 × $0.0004 | **$12.00** |

#### Twitter Video Downloader Unit Economics

The 4 figures below are derived from the rate card above. Every figure is the full charge, because the Twitter Video Downloader has no separate per-item or per-start event.

| Metric | SD (below 720p) | HD (720p up to below 1080p) | FHD (1080p and above) |
|---|---|---|---|
| Cost per minute of video | $0.012 | $0.024 | $0.048 |
| Cost per hour of video | $0.72 | $1.44 | $2.88 |
| Cost per 1,000 seconds | $0.20 | $0.40 | $0.80 |
| Seconds of video per $1 | 5,000 | 2,500 | 1,250 |

The FHD rate is **4 times** the SD rate and **2 times** the HD rate.

#### When Does the Twitter Video Downloader Not Charge?

The Twitter Video Downloader charges nothing in 5 cases:

- The input is not a supported tweet URL or a valid tweet ID (`invalidUrl`).
- The tweet is private, deleted, age-restricted or does not exist (`unavailable`).
- The tweet has no video, or its video has no MP4 variant (`noVideoContent`).
- The run reached its soft deadline before the video started (`timedOut`).
- The whole video does not fit within the remaining max cost per run. The video is skipped, nothing is charged for it and the run stops picking up new tweets.

A download that fails or times out after it starts is still charged, and its row is written with `status` set to `failed` or `timedOut`.

### How Does the Twitter Video Downloader Demo Mode Work?

The Twitter Video Downloader runs in demo mode for Free-plan users, so the actor can be tried without a subscription. Demo mode applies 3 limits:

- **5 runs per month** on the Free plan.
- **10 items per run.** This cap overrides any `maxItems` value, so a Free-plan run never returns more than 10 items.
- **No API access.** The Twitter Video Downloader API is not available on the Free plan.

A paid plan removes these limits for unrestricted use.

### How Is the Twitter Video Downloader Configured?

The Twitter Video Downloader exposes 28 input fields in its input form: 4 core fields, 4 delivery fields and 20 storage fields split across 4 cloud providers. `quality` and `storageType` are required, and 1 of `videoUrls` or `videoIds` has to contain an entry.

The table below lists the 4 core fields and the 4 delivery fields.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `videoUrls` | array | 1 of `videoUrls` or `videoIds` | none | Tweet URLs to download videos from. |
| `videoIds` | array | 1 of `videoUrls` or `videoIds` | none | Raw tweet IDs to download videos from. |
| `quality` | string | Yes | `high` | Quality tier: `low`, `medium` or `high`. |
| `storageType` | string | Yes | `apify` | Storage target: `apify`, `aws`, `gcp`, `digitalocean` or `azure`. |
| `outputConnector` | string | No | none | MCP connector to deliver the results to. See [How Do You Send Twitter Video Downloader Results to Notion, Todoist and More?](#how-do-you-send-twitter-video-downloader-results-to-notion-todoist-and-more) |
| `deliverTarget` | string | No | none | Where the connector writes: a page or database URL or ID (Notion example: `https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b`). Some services need none. |
| `deliverMode` | string | No | `perPost` | `perPost` writes one record per video, `summary` one roll-up per run. |
| `deliverMaxItems` | integer | No | none | Maximum number of records `perPost` delivers. Ignored in `summary`. |

The Twitter Video Downloader also reads `maxItems` (integer) from API input. `maxItems` caps the number of downloaded videos on paid plans, and the lower of `maxItems` and the plan limit applies. On the Free plan, the 10-items-per-run demo cap overrides `maxItems`. `maxItems` does not appear in the input form.

#### How Does the Twitter Video Downloader Pick a Quality Tier?

The Twitter Video Downloader sorts a video's MP4 variants by bitrate and picks a position in that list: `low` takes the smallest file, `high` takes the highest bitrate, and `medium` takes the middle entry. Twitter exposes a variable number of bitrate tiers per tweet rather than fixed pixel resolutions, so `quality` selects a rank, not an exact resolution. On a tweet with only 1 MP4 variant, all 3 tiers return the same file.

#### Which Storage Targets Does the Twitter Video Downloader Support?

The Twitter Video Downloader supports 5 storage targets. The table below lists the fields each one uses.

| `storageType` | Provider | Required fields | Optional fields |
|---|---|---|---|
| `apify` | Apify key-value store | none | none |
| `aws` | Amazon Web Services S3 | `s3AccessKeyId`, `s3SecretAccessKey`, `s3Region`, `s3Bucket` | `s3Path` |
| `gcp` | Google Cloud Storage | `googleCloudProjectId`, `googleCloudServiceKeyJson`, `googleCloudBucketName` | `googleCloudPath` |
| `digitalocean` | DigitalOcean Spaces | `digitalOceanAccessKeyId`, `digitalOceanSecretAccessKey`, `digitalOceanRegion`, `digitalOceanBucketName` | `digitalOceanEndpoint`, `digitalOceanAcl`, `digitalOceanPath` |
| `azure` | Microsoft Azure Storage | `azureAccountName`, `azureAccountKey`, `azureContainerName` | `azurePath` |

With `storageType` set to `apify`, the Twitter Video Downloader creates a named key-value store called `tw-download-{runId}` and uploads every video of the run into it. No cloud account or credentials are required. A named store is kept after the run ends.

The Twitter Video Downloader marks 4 credential fields as secret: `s3SecretAccessKey`, `googleCloudServiceKeyJson`, `digitalOceanSecretAccessKey` and `azureAccountKey`. `googleCloudServiceKeyJson` takes the service account credentials as a JSON object. `digitalOceanRegion` takes a Spaces region such as `nyc3`, and `digitalOceanAcl` defaults to private. Each `*Path` field sets a folder inside the bucket or container.

### Which Input Rules Does the Twitter Video Downloader Enforce?

The Twitter Video Downloader enforces 6 input rules. The table below lists each rule and its outcome.

| Rule | Outcome when broken |
|---|---|
| At least 1 entry across `videoUrls` and `videoIds` | The run fails with `Tweet URLs or tweet IDs must be provided`. |
| Every required field of the chosen `storageType` is set | The run fails with a message naming the missing field, such as `AWS region is missing.` |
| `storageType` `apify` has an Apify token available | The run fails with `Apify token is missing.` |
| A URL has an `http` or `https` scheme, a `twitter` or `x` hostname and a `/status/{digits}` path | The entry becomes an `invalidUrl` row, uncharged, and the run continues. |
| A tweet ID contains digits only | The entry becomes an `invalidUrl` row, uncharged, and the run continues. |
| The max cost per run covers at least 1 second of SD video ($0.0002) | The run stops before any download with a message asking for a higher max cost per run. |

The Twitter Video Downloader caps spend at the lower of the plan limit and the max cost per run set on the run.

### How Do You Download a Twitter Video to Apify Storage with the Twitter Video Downloader?

The Twitter Video Downloader stores videos in Apify storage when `storageType` is `apify`, which needs no credentials. The input below downloads the highest-bitrate MP4 of 1 tweet.

```json
{
  "videoUrls": [
    "https://x.com/tina__nigro/status/1677753111161569281"
  ],
  "quality": "high",
  "storageType": "apify"
}
```

### How Do You Download Twitter Videos to AWS S3 with the Twitter Video Downloader?

The Twitter Video Downloader uploads to an S3 bucket when `storageType` is `aws` and the 4 required S3 fields are set. The input below combines 1 URL and 1 raw tweet ID at `medium` quality into the `twitter/videos` folder.

```json
{
  "videoUrls": [
    "https://twitter.com/tina__nigro/status/1677753111161569281"
  ],
  "videoIds": [
    "1677753111161569281"
  ],
  "quality": "medium",
  "storageType": "aws",
  "s3AccessKeyId": "YOUR_ACCESS_KEY_ID",
  "s3SecretAccessKey": "YOUR_SECRET_ACCESS_KEY",
  "s3Region": "us-east-1",
  "s3Bucket": "my-video-archive",
  "s3Path": "twitter/videos"
}
```

The 2 entries above point to the same tweet, so the Twitter Video Downloader downloads and charges for it once.

### How Do You Send Twitter Video Downloader Results to Notion, Todoist and More?

The Twitter Video Downloader can deliver its results straight into the tools you already use, through [Apify MCP Connectors](https://apify.com/store/collections/mcp-connectors).

1. Authorize a connector (Notion, Todoist, or any service whose MCP server offers non-destructive write tools) once in [Console → Settings → API & Integrations → MCP connectors](https://console.apify.com/settings/integrations).
2. Pick it in the **Send results to Notion, Todoist, …** field.
3. Fill in where and how to write:
   - **Destination target:** the page or database URL or ID (Notion example: `https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b`). Share it with your connector first through Notion's **••• → Connections** menu. Some services need no target.
   - **Write mode:** `perPost` writes one record per video; `summary` writes a single roll-up for the run.
   - **Max records to deliver** (optional): a cap on how many records `perPost` delivers.

Each video becomes one record with its status, source URL and download link. Videos that were not downloaded are delivered too, with their status and error, so the destination doubles as a download log. With `storageType` set to `apify`, the links point to the named key-value store, which is kept after the run ends.

**Notion:** point the target at a Notion **page** and the Actor creates a database with one typed column per field (URL, number, date, text). An **existing database** gets its matching columns filled by name. The connector can't add columns to it, so an empty database receives only titles and the rest of each record goes to the row's page body. Nested fields become columns too: the `inputSource` object becomes `inputSource.queryIndex` and `inputSource.url`, and `durationSeconds` and `totalCost` become number columns. Fields without a column go to the row's page body, so nothing is lost.

Your credentials stay with you: the Actor writes only through your own connector and only uses non-destructive write tools, so it can't delete or overwrite anything. Delivery runs after the scrape and is best-effort. If it fails, the run still finishes normally with all results in the dataset, and the run log says what went wrong. If the run is close to its timeout, delivery stops early so the run still succeeds, and the log says how many records were delivered.

### Which Fields Does the Twitter Video Downloader Output?

The Twitter Video Downloader outputs 12 fields per dataset row, 1 of them an object with 2 nested fields. The actor writes 1 row per video, and 1 row per tweet that produced no video.

| Field | Type | Description |
|---|---|---|
| `sourceUrl` | string | The tweet URL as given, or `https://x.com/i/status/{id}` for a raw ID. |
| `videoPosition` | integer or null | 1-based index of the video within the tweet. |
| `downloadUrl` | string or null | Link to the stored file. Set only when `status` is `succeeded`. |
| `quality` | string or null | The requested tier: `low`, `medium` or `high`. |
| `storageType` | string or null | The storage target the file was written to. |
| `status` | string | 1 of 6 status values, listed below. |
| `jobId` | string or null | Identifier of the download job. |
| `error` | string or null | Reason the video was not delivered. |
| `durationSeconds` | integer or null | Charged seconds, rounded up, minimum 1. Null when the video was not charged. |
| `unitPricePerSecond` | number or null | Rate of the charged event. Null when the video was not charged. |
| `totalCost` | number | `durationSeconds` × `unitPricePerSecond`, or `0` when the video was not charged. |
| `inputSource` | object | `queryIndex` (integer position in the input) and `url` (the input entry). |

#### Twitter Video Downloader Status Values

The Twitter Video Downloader writes 6 status values. 2 of them are charged.

| `status` | Meaning | Charged |
|---|---|---|
| `succeeded` | The video was downloaded and stored. `downloadUrl` is set. | Yes |
| `invalidUrl` | The entry is not a supported tweet URL or a valid tweet ID. | No |
| `unavailable` | The tweet is private, deleted, age-restricted or does not exist. | No |
| `noVideoContent` | The tweet has no video, or no MP4 variant. | No |
| `failed` | The download job could not be created or did not complete. | Yes |
| `timedOut` | The run ran out of time before or during the download. | Only when the download had started |

#### Twitter Video Downloader Output Example

The row below is a `succeeded` 13-second 720p video stored in Apify storage.

```json
{
  "sourceUrl": "https://x.com/tina__nigro/status/1677753111161569281",
  "videoPosition": 1,
  "downloadUrl": "https://api.apify.com/v2/key-value-stores/.../records/tw-1677753111161569281-1",
  "quality": "high",
  "storageType": "apify",
  "status": "succeeded",
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "error": null,
  "durationSeconds": 13,
  "unitPricePerSecond": 0.0004,
  "totalCost": 0.0052,
  "inputSource": {
    "queryIndex": 0,
    "url": "https://x.com/tina__nigro/status/1677753111161569281"
  }
}
```

The Twitter Video Downloader names each stored file `tw-{tweetId}-{videoPosition}`, so 2 videos in the same tweet never overwrite each other.

### Which Errors Does the Twitter Video Downloader Report?

The Twitter Video Downloader reports 10 error codes in the run log. **7 of the 10 codes are non-terminal**: the run logs them and moves to the next tweet or video. 3 codes terminate the run: `C098`, `C099` and `C100`.

The 7 non-terminal codes split into 3 recovery behaviors:

- **3 codes** (`C001`, `C002`, `C005`) write an uncharged row with `status` `invalidUrl`, `unavailable` or `noVideoContent`. `C005` writes its `noVideoContent` row only when a video has no MP4 variant; a queued tweet that fails to process logs `C005` and writes no row.
- **2 codes** (`C006`, `C007`) write a charged row with `status` `failed`, because the video was charged before its download job started.
- **2 codes** (`C003`, `C004`) log the tweet as unresolved and write no row for it.

The table below lists all 10 codes.

| Code | Cause | Twitter Video Downloader behavior |
|---|---|---|
| `C001` | The entry is not a tweet URL or a tweet ID. | Writes an `invalidUrl` row, uncharged. |
| `C002` | The tweet is private, deleted, age-restricted or does not exist. | Writes an `unavailable` row, uncharged. |
| `C003` | The tweet could not be resolved. | Logs the tweet, writes no row. |
| `C004` | The tweet could not be processed. | Logs the tweet, writes no row. |
| `C005` | A video has no MP4 variant, or a queued tweet failed to process. | Writes a `noVideoContent` row, uncharged, for a missing MP4 variant. Logs a failed queued tweet without a row. |
| `C006` | The download job could not be created. | Writes a `failed` row, charged. |
| `C007` | The download job did not succeed. | Writes a `failed` row, charged, with the job's error in `error`. |
| `C098` | Unhandled promise rejection. | Ends the run. |
| `C099` | Uncaught exception. | Ends the run. |
| `C100` | The run could not be initialized. | Ends the run. |

For `C098`, `C099` and `C100`, the run log asks for the run ID to be sent to support.

#### How Does the Twitter Video Downloader Handle the Run Timeout?

The Twitter Video Downloader sets a soft deadline shortly before the run timeout. At that point, tweets that have not started are written as uncharged `timedOut` rows, downloads in progress stop waiting and report `timedOut`, and every finished video keeps its row. The run then ends as succeeded instead of being killed mid-write. Runs without a timeout have no soft deadline.

### Which Twitter Video Downloader Questions Come Up Most Often?

#### Does the Twitter Video Downloader work with x.com links?

Yes. The Twitter Video Downloader accepts `x.com`, `twitter.com` and subdomains such as `mobile.twitter.com`, as long as the path contains `/status/{digits}`.

#### Does the Twitter Video Downloader need a Twitter account or cloud account?

No cloud account is needed. The Twitter Video Downloader stores files in Apify storage when `storageType` is `apify`, and it takes no Twitter login fields as input.

#### Can the Twitter Video Downloader download videos from private tweets?

No. The Twitter Video Downloader writes an uncharged `unavailable` row for private, deleted and age-restricted tweets.

#### Can the Twitter Video Downloader be used for free?

Yes, in demo mode. Free-plan users get 5 runs per month and 10 items per run, without API access.

#### Does the Twitter Video Downloader download GIFs?

Yes, when the GIF is delivered as an MP4 variant. The Twitter Video Downloader charges a GIF without a reported duration as 1 second.

#### Which file format does the Twitter Video Downloader deliver?

The Twitter Video Downloader delivers MP4. HLS playlist variants are never selected.

#### Why was the Twitter Video Downloader charged at SD when `quality` was `high`?

The Twitter Video Downloader bills by the resolution of the delivered file. `high` picks the best bitrate the tweet offers, and when that file's short side is below 720 pixels, the SD rate of $0.0002 per second applies.

#### Does the Twitter Video Downloader charge for duplicate tweets?

No. The Twitter Video Downloader removes duplicates by tweet ID across `videoUrls` and `videoIds` before downloading.

#### Does the Twitter Video Downloader download every video in a tweet?

Yes. The Twitter Video Downloader downloads every MP4 video in the tweet and numbers each one with `videoPosition`, charging each video separately.

#### How long do Twitter Video Downloader links stay valid?

The Twitter Video Downloader re-hosts every file in the chosen storage target, so `downloadUrl` follows the retention of that storage rather than Twitter's media URL. With `storageType` `apify`, the file sits in the named store `tw-download-{runId}`, which is kept after the run ends.

#### What happens when the Twitter Video Downloader reaches the max cost per run?

The Twitter Video Downloader skips any video whose full cost does not fit in the remaining budget, charges nothing for it and stops picking up new tweets. Videos already charged keep downloading and write their rows.

#### Does the Twitter Video Downloader return tweet text or author data?

No. The Twitter Video Downloader returns the 12 fields listed in its output schema, which describe the video file, its storage location, its status and its cost.

### Where Do You Get Twitter Video Downloader Support?

The Twitter Video Downloader is published by epctex. Support is available at <business@epctex.com> and through the chat box on [epctex.com](https://epctex.com), which also lists custom integrations and the full epctex product catalog. Include the run ID when reporting error codes `C098`, `C099` or `C100`.

# Actor input Schema

## `videoUrls` (type: `array`):

List of Tweet URLs to download. Provide either videoUrls or videoIds.

## `videoIds` (type: `array`):

List of Tweet IDs to download.

## `quality` (type: `string`):

Target video quality tier. Twitter does not expose fixed pixel resolutions like YouTube - each tweet's video has multiple bitrate tiers, so quality selects the tier rank rather than an exact resolution.

## `storageType` (type: `string`):

Target storage provider. Fill the matching configuration section below.

## `s3AccessKeyId` (type: `string`):

Fill in the following fields if you want to upload the video to an S3 bucket

## `s3SecretAccessKey` (type: `string`):

Fill in the following fields if you want to upload the video to an S3 bucket

## `s3Bucket` (type: `string`):

Fill in the following fields if you want to upload the video to an S3 bucket

## `s3Region` (type: `string`):

Fill in the following fields if you want to upload the video to an S3 bucket

## `s3Path` (type: `string`):

Optional folder path in the S3 bucket.

## `googleCloudProjectId` (type: `string`):

GCP project ID.

## `googleCloudServiceKeyJson` (type: `object`):

Service account credentials JSON.

## `googleCloudBucketName` (type: `string`):

Target Google Cloud Storage bucket name.

## `googleCloudPath` (type: `string`):

Optional folder path in the GCS bucket.

## `digitalOceanAccessKeyId` (type: `string`):

DigitalOcean Spaces access key ID.

## `digitalOceanSecretAccessKey` (type: `string`):

DigitalOcean Spaces secret access key.

## `digitalOceanRegion` (type: `string`):

DigitalOcean Spaces region (e.g. nyc3).

## `digitalOceanBucketName` (type: `string`):

DigitalOcean Spaces bucket name.

## `digitalOceanEndpoint` (type: `string`):

Optional Spaces endpoint URL.

## `digitalOceanAcl` (type: `string`):

Optional ACL value (defaults to private).

## `digitalOceanPath` (type: `string`):

Optional folder path in the Spaces bucket.

## `azureAccountName` (type: `string`):

Azure storage account name.

## `azureAccountKey` (type: `string`):

Azure storage account key.

## `azureContainerName` (type: `string`):

Azure container name.

## `azurePath` (type: `string`):

Optional folder path in the Azure container.

## `outputConnector` (type: `string`):

Pick an MCP connector you authorized in Console → Settings → API & Integrations → MCP connectors (e.g. Notion, Todoist — any service whose MCP server offers non-destructive write tools works best-effort). The scraped results are written through your own connector — the Actor never sees your third-party credentials. Only non-destructive write tools are used; it can never delete or overwrite your existing data.

## `deliverTarget` (type: `string`):

Where to write into the connector — paste the URL or ID. Notion example: https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b. Leave empty for services that need no target (e.g. Todoist Inbox, Slack). For Notion, share the page or database with your connector first (Notion's ••• → Connections menu), and point at a PAGE for structured columns (the Actor builds the database for you) — an empty database receives only the title (the rest of each record goes to the row's body).

## `deliverMode` (type: `string`):

perPost writes one record per scraped item; summary writes a single roll-up for the run.

## `deliverMaxItems` (type: `integer`):

Cap on how many records perPost mode writes to the connector; ignored in summary mode. Leave empty to deliver everything scraped.

## Actor input object example

```json
{
  "videoUrls": [
    ""
  ],
  "videoIds": [
    ""
  ],
  "quality": "high",
  "storageType": "apify",
  "deliverMode": "perPost"
}
```

# 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 = {
    "videoUrls": [
        ""
    ],
    "videoIds": [
        ""
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("epctex/twitter-video-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 = {
    "videoUrls": [""],
    "videoIds": [""],
}

# Run the Actor and wait for it to finish
run = client.actor("epctex/twitter-video-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 '{
  "videoUrls": [
    ""
  ],
  "videoIds": [
    ""
  ]
}' |
apify call epctex/twitter-video-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,epctex/twitter-video-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/dOP0T8S4T5DtbVkAa/builds/TAvmpthkQrkwPjnkj/openapi.json
