# Universal Media Extractor (`abotapi/universal-media-extractor`) Actor

Extract videos, audio, and metadata from 1000+ websites including YouTube, TikTok, Twitter/X, Instagram, Vimeo, Facebook, Twitch, and many more. Stream directly to your cloud storage or get direct download URLs for your pipelines.

- **URL**: https://apify.com/abotapi/universal-media-extractor.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Social media, Developer tools, Videos
- **Features:** Limited permissions
- **Stats:** 48 total users, 4 monthly users, 94.1% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Universal Media Extractor: Video, Audio and Metadata from Any Link

This actor turns a video or audio link from YouTube, TikTok, Twitter/X, Instagram, Vimeo and 1000+ other sites into structured metadata, direct format URLs, or a downloaded file in your own storage. Extract mode reads the page and returns titles, descriptions, stats and direct links without downloading anything. Download mode saves the file to Apify storage or your own bucket. Stream mode sends the file straight to your S3 or Google Cloud Storage bucket without saving it in Apify storage. Export what you get to JSON, CSV or Excel, or read it through the API.

### Why This Scraper?

- **One input field, 1000+ sites.** Paste almost any video or audio page link, no separate setup for each platform.
- **Three modes for three jobs.** Pull metadata and direct URLs only, save the file to Apify storage, or send it straight to your own S3 or Google Cloud Storage bucket.
- **Format control.** Pick the best available quality, audio only, or a resolution cap, with a plain format string.
- **Playlist and channel aware for listings.** Flatten a playlist or channel link into its video URLs in one call, without opening every video.
- **Metadata before you commit to storage.** Extract mode returns titles, descriptions, view counts and direct format URLs without downloading the file, so you can decide what is worth saving.
- **Optional proxy routing.** Route requests through Apify Proxy for sites that block direct connections, or run without a proxy by default.
- **Built for automation.** Call it from your own code, a schedule, or a no-code tool through the Apify API.

### Use Cases

- **Content archiving:** save a copy of a video or audio file to your own S3 or Google Cloud Storage bucket for backup or compliance records.
- **Media processing pipelines:** get direct format URLs and metadata to feed into transcription, captioning or machine learning workflows without storing the file yourself.
- **Catalog and research:** collect titles, descriptions, view counts and other metadata across many links for analysis or reporting, without downloading anything.
- **App and bot integrations:** pull thumbnails, durations and direct URLs to reference media inside your own product.
- **Playlist triage:** flatten a playlist or channel link into its video URLs first, then process only the ones you need.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `"sample123abc"` |
| `title` | `"Sample Video Title"` |
| `description` | `"A short sample description of the video."` |
| `duration` | `212` |
| `view_count` | `1500000` |
| `like_count` | `42000` |
| `upload_date` | `"20240115"` |
| `uploader` | `"Sample Channel"` |
| `channel` | `"Sample Channel"` |
| `thumbnail` | `"https://example.com/thumbnail.jpg"` |
| `thumbnails` | list of thumbnail URLs at different sizes |
| `webpage_url` | `"https://www.youtube.com/watch?v=sample123abc"` |
| `ext` | `"mp4"` |
| `extractor` | `"youtube"` (name of the site handler that read the link) |
| `tags` | `["music", "sample"]` |
| `is_live` | `false` |
| `formats` | list of available formats, each with a format id, extension, resolution and direct URL |
| `storage_url` | link to the saved file (download and stream modes only) |
| `storage_type` | `"apify"`, `"s3"`, or `"gcs"` (download and stream modes only) |
| `filename` | `"sample123abc.mp4"` (download and stream modes only) |

Extract mode returns the fields above at the top level of the record, with no `storage_url`, `storage_type` or `filename` since nothing is downloaded. Download and stream mode records carry `storage_url`, `filename` and `storage_type` at the top level, with the rest of the metadata nested under an `info` object.

### How to Use

1. Paste one link into **url**, or several into **urls**. Supported link shapes: a watch link (`youtube.com/watch?v=`), a short link (`youtu.be/`), a Shorts link (`/shorts/`), an embed link (`/embed/`), a playlist link (`/playlist?list=`) and a channel link (`/@handle/videos`).
   If you leave both **url** and **urls** empty, the actor runs one example of each of those six shapes and delivers up to **6 result rows** (one per distinct video): the four example video links each point to a different video, plus one listing row for the first video of the example playlist and one for the first video of the example channel. If the playlist's or the channel's first video is the same video as one already returned, it is returned once, so a default run never delivers more than 6 rows. In every mode (also `download` and `stream`) the playlist and channel examples are only listed, never downloaded, so a default run saves at most four files. If you set **url**, only your link is processed.
2. Pick a **mode**: `extract` for metadata and direct links only, `download` to save the file to Apify storage or your bucket, or `stream` to send the file straight to your S3 or GCS bucket.
3. Set **format** to control quality (left on `best`, `download` mode takes the best video plus audio and `stream` mode prefers a single file), and fill **storage_type** plus **storage_config** when you choose `download` or `stream` to your own bucket.
4. Click **Start**, then read the result from the dataset, or open the link it contains to get the stored file.

**Extract metadata only:**

```json
{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "mode": "extract"
}
```

**Download up to 1080p to Apify storage:**

```json
{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "mode": "download",
  "format": "best[height<=1080]"
}
```

**Stream audio only to Google Cloud Storage:**

```json
{
  "url": "https://soundcloud.com/artist/track",
  "mode": "stream",
  "format": "bestaudio",
  "storage_type": "gcs",
  "storage_config": {
    "bucket": "my-audio-bucket",
    "credentials_json": "{...service account JSON...}"
  }
}
```

**Flatten a playlist into its video URLs, routed through Apify Proxy:**

```json
{
  "url": "https://www.youtube.com/playlist?list=PLsample0000000000",
  "mode": "extract",
  "extract_flat": true,
  "proxy": { "useApifyProxy": true }
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/universal-media-extractor").call(run_input={"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "mode": "extract"})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["duration"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/universal-media-extractor').call({ url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', mode: 'extract' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### About playlists and channels

The actor processes every link from **url** and **urls** (identical links removed), one after another. **A video is returned once per run, however many links reach it** (for example `watch?v=ID`, `youtu.be/ID` and `embed/ID`, or a single link plus a playlist that contains it): the later links add no row, no charge and no download, in every mode and whether or not incremental mode is on. The `OUTPUT` record counts them under `duplicateLinksSkippedCount`. A video link adds one result row. A playlist or channel link adds **one result row per video** (each billed as a normal result row, delivered rows only), up to **max_items_per_url** videos (default 10), after **playlist_items** (for example `1,3,5-7`) is applied. **Results stop at that cap**: when a playlist or channel has more videos, the run log shows a warning and the `OUTPUT` record lists the link under `truncated` (link, rows delivered, cap). Raising **max_items_per_url** returns, and bills, one result row for every extra video. In `extract` mode with `extract_flat` on, each row is the playlist's own listing entry; otherwise every video is read on its own, and in `download` or `stream` mode every video is saved on its own. A link or video that fails adds no row and is not billed (a bot check from the site is retried on up to two fresh Apify Proxy connections first; if it persists the reason reads "The site asked for a bot check on this connection"); the reason is saved under the `ERROR` key of the run's key-value store, and the run fails only when nothing succeeded. Several links in one run share a single run start.

**Quality, format and file names.** `preferredQuality` (144p to 2160p) caps the video height, `preferredFormat` picks a container (mp4, mkv, webm ...) or converts to audio (mp3, m4a, wav ...), and `filenameTemplateParts` (title, uploader, timestamp) names saved files. A custom `format` always wins over `preferredQuality`. In `stream` mode, choosing an audio format, a container or subtitles makes the run save the file in `download` mode first, because those need a local file. `username` and `password` are applied when set.

**Subtitles.** With `write_subtitles` on (and `subtitle_langs` to pick languages), `download` mode saves each subtitle file to the same storage as the video and lists them in the row under `subtitleFiles` (language, link and storage key). `stream` mode then switches to `download` mode. `extract` mode saves no files: its rows already include the subtitle tracks the site offers, with their links.

**Extra output fields.** Every row also carries `input` (the link it came from); download rows add `downloadedFileUrl` (same as `storage_url`) and `fileKey` (the real storage key of the file: the Key-Value Store key, or the S3 key or GCS object name including your prefix); saved file names always end with the video ID, so two videos with the same title never overwrite each other; `audioOnlyUrl` and `videoOnlyUrl` appear when the source offers separate audio-only or video-only streams.

#### Recurring runs: incremental mode and resume

Set **incrementalMode** to remember videos between runs (default `none`: nothing is remembered and every run returns every video, exactly as before).

- `new-only`: the first run returns every video; later runs return only videos not seen before.
- `changed-only`: later runs return new videos plus videos whose details changed.

Videos are remembered by site and video ID, in a named Key-Value Store of your account, per set of links (or per **stateKey** when you set one). Rows carry `changeType` (`NEW` or `UPDATED`), `changedFields`, `firstSeenAt` and `lastSeenAt`. A video that is not returned is not pushed, not billed, and in `download` or `stream` mode not downloaded again (its metadata is read first to decide). Videos that disappear from a playlist or channel are never reported. A video counts as changed when its own details change (title, description, tags, duration, availability and similar). `view_count` and `like_count` never count, and you can add more names in **ignoreFieldsForChanges** (dotted paths such as `chapters.title` work). Signed stream links, thumbnails, subtitle lists, storage links and file names are never compared, but are still returned. The remembered state is only saved for videos that were delivered or found unchanged, so a failed video is tried again next run.

**resumeFromRunId** continues one earlier run: links and playlist or channel videos already in that run's dataset are skipped, so only the rest is returned and billed. Resume and incremental mode can be combined.

### Send results into your apps (MCP connectors)

Optionally pipe the delivered result rows into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra step **after** the run: the Apify dataset is never changed, and a connector error never fails the run.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the `mcpConnectors` field ("Pipe results into your apps").
3. For **Notion**, also set `notionParentPageUrl` to the page where entries should be created.
4. `maxNotifyListings` caps how many rows are written to each connector per run (default 50, maximum 1000).

Each row is written as a short plain-text summary; the complete record stays in the dataset. Leave `mcpConnectors` empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `url` | string | (none) | Video, audio, playlist or channel URL to process. The Console prefills a sample YouTube link. |
| `urls` | array of strings | one example of each of the six shapes | More links, merged with `url`; identical links, and links that reach the same video, are returned once. When both `url` and `urls` are empty, one example per link shape is processed. |
| `mode` | string | `extract` | `extract` (metadata only), `download` (save to storage), or `stream` (send straight to cloud storage). |
| `format` | string | `best` | Format selection string, for example `bestaudio`, `bestvideo+bestaudio`, or `best[height<=720]`. Left on `best`, `download` mode uses the best video plus audio. A custom value wins over `preferredQuality`. |
| `preferredQuality` | string | (none) | Maximum video height, 144p to 2160p. |
| `preferredFormat` | string | (none) | Container (mp4, mkv, webm ...) or audio format (mp3, m4a, wav ...) for saved files. Download and stream modes. |
| `filenameTemplateParts` | array | (none) | Parts used to name saved files: title, uploader, timestamp. |
| `storage_type` | string | `apify` | Where to save the file for `download` and `stream` modes: `apify`, `s3`, or `gcs`. |
| `storage_config` | object | (none) | Cloud storage settings. For S3: bucket, region, aws_access_key, aws_secret_key, prefix, acl. For GCS: bucket, credentials_json, prefix, make_public. `acl` and `make_public` apply in `stream` mode only; `download` mode returns a signed link valid for 7 days. |
| `proxy` | object | (none, direct connection) | Apify Proxy configuration. Leave empty to connect directly; the Console suggests turning on Apify Proxy as a starting point. |
| `extract_flat` | boolean | `false` | For playlists and channels in `extract` mode, return each video's listing entry without reading its full metadata. |
| `max_items_per_url` | integer | `10` | Maximum videos taken from one playlist or channel link; the list stops there and the run says so. Each extra video is one more billed row. |
| `resumeFromRunId` | string | (none) | Run or dataset ID of an earlier run; links and playlist videos already delivered there are skipped (not returned, not billed). |
| `incrementalMode` | string | `none` | `none` returns everything every run. `new-only` and `changed-only` remember delivered videos between runs. See the incremental section above. |
| `stateKey` | string | (derived from your links) | Names the remembered set of videos (incremental mode only). |
| `ignoreFieldsForChanges` | array | `view_count`, `like_count` | Fields that never count as a change (incremental mode only). |
| `mcpConnectors` | array | `[]` | Optional MCP connectors that receive a summary of the delivered rows. See the MCP section above. |
| `notionParentPageUrl` | string | none | Notion page under which entries are created (Notion connector only). |
| `maxNotifyListings` | integer | `50` | Cap on rows written to each connector per run; does not affect the dataset. |
| `playlist_items` | string | (none) | Pick playlist items, for example `1,3,5-7`. |
| `write_subtitles` | boolean | `false` | Save subtitle files in `download` mode (listed under `subtitleFiles`). Extract rows carry the subtitle tracks the site offers. |
| `subtitle_langs` | array | (none) | Subtitle languages to save. |
| `geo_bypass` | boolean | `true` | Try to read videos that are restricted to certain countries. |
| `ignore_errors` | boolean | `false` | Passed to the downloader. A link that fails adds no row either way and the problem is saved under `ERROR`. |
| `username` | string | (none) | Account name for login-protected content. |
| `password` | string | (none) | Account password for login-protected content. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": "sample123abc",
  "title": "Sample Video Title",
  "description": "A short sample description of the video.",
  "duration": 212,
  "view_count": 1500000,
  "like_count": 42000,
  "upload_date": "20240115",
  "uploader": "Sample Channel",
  "channel": "Sample Channel",
  "thumbnail": "https://example.com/thumbnail.jpg",
  "thumbnails": [
    { "url": "https://example.com/thumbnail_small.jpg", "width": 120, "height": 90 }
  ],
  "webpage_url": "https://www.youtube.com/watch?v=sample123abc",
  "ext": "mp4",
  "extractor": "youtube",
  "tags": ["music", "sample"],
  "is_live": false,
  "formats": [
    { "format_id": "22", "ext": "mp4", "resolution": "1280x720", "url": "https://example.com/stream/720p.mp4" }
  ]
}
```

### Plan Requirement

Runs on any Apify plan. The proxy field is empty by default, so requests go out directly. In our check, a plain YouTube watch page loaded from a datacenter connection without a proxy, so YouTube extract mode needs no extra setup. We did not measure the stream and download endpoints, or other sites, so if a site refuses the direct connection, switch on Apify Proxy under Connection (a residential group is the usual fix, and proxy traffic is billed to your account separately).

**Pricing.** This actor is pay per event. Two events are charged: **apify-actor-start**, once per run (several links in one run share one start), and **result**, once per row delivered to the dataset. Failed links add no row and are never billed for a result. Playlist and channel links add one billed row per video. The Pricing tab shows the current rates for your plan.

### FAQ

#### How much does it cost?

Per run you pay the start event once, plus the result event for each row in the dataset. Extract, download and stream modes bill the same events: one row per delivered link or video. A failed link adds no row, so no result charge. In incremental mode, videos that are not returned (already seen, or unchanged) add no row and no charge. A video reached through several links in one run is one row and one charge. A run with no `url` or `urls` delivers at most six rows (four example videos, plus the first video of the example playlist and of the example channel). Compute and any proxy traffic are separate platform usage, and the Pricing tab lists the current rates.

#### Is it legal to download videos and audio with this actor?

This actor reads and downloads media the same way a browser or media player would, from the link you give it, and it does not remove copy protection or DRM. Whether you may keep, use or redistribute a downloaded file depends on the content owner's rights, the source platform's terms of service, and the copyright law that applies to you. Many places allow a personal, private copy but restrict redistribution or commercial use of someone else's work. Get your own legal advice before redistributing anything, and stick to content you own, have permission for, or that carries a license allowing reuse.

#### Can I schedule this to catch new uploads automatically?

Yes. Schedule the actor from the **Schedules** tab and set **incrementalMode** to `new-only` (only videos not seen before) or `changed-only` (new videos plus videos whose details changed) on a playlist or channel link. The default, `none`, tracks nothing and returns every video every run. See the incremental section above; **resumeFromRunId** continues one interrupted run.

#### Why did my download run return a link instead of a file I can open directly?

Download and stream mode save the file to storage and give you a link (`storage_url`) plus metadata, not the raw file bytes inside the dataset record. Open the link to get the actual video or audio file. Also, `stream` mode only sends data straight to S3 or GCS; with `storage_type` set to `apify`, a `stream` mode run automatically uses `download` mode instead, since direct streaming isn't offered for that storage. A `format` that combines separate video and audio streams, such as `bestvideo+bestaudio`, also makes a `stream` run use `download` mode.

#### Why did my run fail instead of returning an empty result?

If none of the links can be read, for example a link is invalid, the content was removed or made private, or the site refuses the connection or asks for a bot check on every attempt, the run fails. Older versions added an error row to the dataset; this version does not, so a failed link is never billed for a result row. The problem is shown in the run status message and saved under the `ERROR` key of the run's key-value store, and the `OUTPUT` key holds a run summary (links requested, rows delivered, failures). Try again after checking the link, or add a proxy under Connection if the site is blocking the default connection.

#### Can I use this with AI agents or MCP?

Yes. Call it like any other Apify actor from an AI agent, an MCP client that exposes Apify actors as tools, or your own automation, through the Apify API. This actor does not push results into a third-party app on its own; the dataset records and the direct URLs they contain are what you feed into your agent or workflow.

### 🔗 Want more social media data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>📱 <a href="https://apify.com/abotapi/tiktok-live-recorder"><b>Tiktok Live Recorder</b></a><br>Record TikTok live streams to MP4 with full metadata, all stream quality URLs, and...</td><td>📱 <a href="https://apify.com/abotapi/lemon8-media-scraper"><b>Lemon8 Media Scraper</b></a><br>Lightweight Lemon8 scraper for extracting image and video URLs from posts, with optional...</td></tr>
<tr><td>🎬 <a href="https://apify.com/abotapi/twitch-scraper"><b>Twitch Scraper</b></a><br>Scrape Twitch data at scale, including channels, live streams, clips, VODs, and top...</td><td>📱 <a href="https://apify.com/abotapi/tiktok-scraper"><b>TikTok Profile, Hashtag, Search, Video &amp; Trending Scraper</b></a><br>Scrape TikTok without login. Extract profiles, bios, follower stats and videos; search by...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/viewstats-channel-analytics"><b>ViewStats Scraper</b></a><br>Scrape YouTube channel analytics from ViewStats. Look up channels by handle, URL, or...</td><td>🎬 <a href="https://apify.com/abotapi/youtube-transcript-scraper"><b>YouTube Transcript &amp; Subtitle Scraper</b></a><br>Extract transcripts and subtitles from YouTube videos in bulk using video, playlist...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/universal-media-extractor/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `urls` (type: `array`):

Several video, short, embed, playlist or channel links, processed one after another, one result row each. Merged with Video URL. Leave both empty to get one example of every supported link shape.

## `url` (type: `string`):

URL of the video, playlist or channel to process

## `mode` (type: `string`):

Processing mode: extract (metadata only), download (to storage), stream (direct to cloud)

## `format` (type: `string`):

Video/audio format selection. Examples: best, bestaudio, bestvideo+bestaudio, best\[height<=720]

## `preferredQuality` (type: `string`):

Maximum video height for download. Ignored when Format is set to something other than best.

## `preferredFormat` (type: `string`):

Container for the saved video (mp4, mkv, webm ...) or an audio format (mp3, m4a, wav ...) converted from the best audio. Download and stream modes.

## `filenameTemplateParts` (type: `array`):

Parts joined with a dash to name saved files, in order. Leave empty to name files by video ID.

## `ignore_errors` (type: `boolean`):

Continue on download errors (useful for playlists)

## `storage_type` (type: `string`):

Where to store downloaded files (for download/stream modes)

## `storage_config` (type: `object`):

Cloud storage credentials. For S3: {bucket, region, aws_access_key, aws_secret_key, prefix, acl}. For GCS: {bucket, credentials_json, prefix, make_public}

## `write_subtitles` (type: `boolean`):

Download mode (and stream mode, which then downloads): save the subtitle files next to the video and list them in the row under subtitleFiles. Extract mode returns the subtitle tracks the site offers inside the row, but saves no files.

## `subtitle_langs` (type: `array`):

List of subtitle languages to download (e.g., en, es)

## `playlist_items` (type: `string`):

Specific playlist items to process (e.g., '1,3,5-7')

## `extract_flat` (type: `boolean`):

For playlists, only extract video URLs without downloading individual video info

## `max_items_per_url` (type: `integer`):

Playlist and channel links produce one row per video, up to this many (after Playlist Items is applied). The list stops at this cap and the run log and the OUTPUT record say so; raising it bills one result row per extra video.

## `username` (type: `string`):

Account username for authenticated content

## `password` (type: `string`):

Account password for authenticated content

## `geo_bypass` (type: `boolean`):

Attempt to bypass geo-restrictions

## `proxy` (type: `object`):

Apify Proxy configuration. If left empty, the extractor connects directly with no proxy. Enable Apify Proxy or paste custom proxy URLs here if the source site needs it.

## `resumeFromRunId` (type: `string`):

Optional. ID of an earlier run of this actor (or of a dataset). Links and playlist or channel videos already delivered in that dataset are skipped, so this run returns and bills only the rest. Use it to continue one interrupted run. For recurring runs of the same links use Incremental mode below.

## `incrementalMode` (type: `string`):

none (default): every run returns every video, nothing is remembered. new-only: the first run returns every video, later runs return only videos not seen before. changed-only: later runs return new videos plus videos whose details changed (title, description, tags, duration, availability and similar); view_count and like_count never count as changes, and signed stream links, thumbnails, subtitle lists and storage links are never compared. Videos that are not returned are not billed and, in download or stream mode, not downloaded again. Unchanged and removed videos are never returned. State is kept per set of links, or per State key.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its remembered videos stable, or to share them across runs with different links. Leave empty to derive the key from the set of links.

## `ignoreFieldsForChanges` (type: `array`):

Field names that should NOT make a video count as changed. view_count and like_count are always ignored (live counters); add others here, for example description or tags. Use a dotted path (for example chapters.title) for a field inside a nested object or list. Ignored fields are still returned in the output.

## `mcpConnectors` (type: `array`):

Optionally send the scraped result rows into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify > Settings > API & Integrations, then select it here. Notion gets a page-per-result row export; other connectors receive a best-effort write or digest. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which result row pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on how many result rows are written to each selected connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/aqz-KE-bpKQ",
    "https://www.youtube.com/shorts/jNQXAC9IVRw",
    "https://www.youtube.com/embed/kJQP7kiw5Fk",
    "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI",
    "https://www.youtube.com/@YouTube/videos"
  ],
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "mode": "extract",
  "format": "best",
  "ignore_errors": false,
  "storage_type": "apify",
  "write_subtitles": false,
  "extract_flat": false,
  "max_items_per_url": 10,
  "geo_bypass": true,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": "none",
  "ignoreFieldsForChanges": [
    "view_count",
    "like_count"
  ],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `details` (type: `string`):

No description

## `files` (type: `string`):

No description

# 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 = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/aqz-KE-bpKQ",
        "https://www.youtube.com/shorts/jNQXAC9IVRw",
        "https://www.youtube.com/embed/kJQP7kiw5Fk",
        "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI",
        "https://www.youtube.com/@YouTube/videos"
    ],
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "format": "best",
    "proxy": {
        "useApifyProxy": true
    },
    "ignoreFieldsForChanges": [
        "view_count",
        "like_count"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/universal-media-extractor").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 = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/aqz-KE-bpKQ",
        "https://www.youtube.com/shorts/jNQXAC9IVRw",
        "https://www.youtube.com/embed/kJQP7kiw5Fk",
        "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI",
        "https://www.youtube.com/@YouTube/videos",
    ],
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "format": "best",
    "proxy": { "useApifyProxy": True },
    "ignoreFieldsForChanges": [
        "view_count",
        "like_count",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/universal-media-extractor").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 '{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/aqz-KE-bpKQ",
    "https://www.youtube.com/shorts/jNQXAC9IVRw",
    "https://www.youtube.com/embed/kJQP7kiw5Fk",
    "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI",
    "https://www.youtube.com/@YouTube/videos"
  ],
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "format": "best",
  "proxy": {
    "useApifyProxy": true
  },
  "ignoreFieldsForChanges": [
    "view_count",
    "like_count"
  ]
}' |
apify call abotapi/universal-media-extractor --silent --output-dataset

```

## MCP server setup

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

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/JiQc435LE9XICJYKZ/builds/Eb7Fc2SJBqc7ZZRzB/openapi.json
