# Speech to Text · Any Link or File (`steadyfetch/media-transcriber`) Actor

Never charged for a link we can't reach, can't decode, or that has no speech. Any audio or video URL to text, SRT and VTT with timestamped segments — direct file links from any host, plus 11 tested sites. About 90 languages, no API key of your own.

- **URL**: https://apify.com/steadyfetch/media-transcriber.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** AI, Agents, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 audio minutes

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?

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

## Speech to Text · Any Link or File

Any media URL to text, SRT and VTT. **Unreachable, silent and unsupported items are never charged.** Direct file links always work; sites only where listed. Paste a link to an `.mp3`, `.m4a`, `.wav`, `.mp4`, `.mov` — or a podcast, SoundCloud, Loom, Twitch VOD, Archive.org or Wistia page — and get back the transcript, the timestamps, and ready-made subtitle files. No account, no API key of your own, no subscription.

| You give it | You get back |
|---|---|
| A direct audio or video file link, from **any** host | `text` — the full transcript, in the language it was spoken |
| …or a page link on one of the 11 tested sites below | `srt` and `vtt` — subtitle files, ready to upload |
| …or a whole list of them in one run | `segments` — start/end timestamps for every line |
| …or the **Dataset ID** of any scraper run you already have | `language`, `durationSeconds`, `wordCount`, `status`, `chargedMinutes` |

**Pricing: from $3.00/1,000 audio minutes.** You are charged per audio minute actually transcribed, rounded up to the next whole minute (a 90-second clip bills 2 minutes). Nothing else — no start fee, no subscription.

**What can fail, and what it costs you: nothing.** The two most common non-deliveries are `no_speech` (the audio decoded fine but it is music, silence or a tone) and `unsupported_site` (a page link on a site that is not on the tested list below). Both come back as a row with `charged: false` and a note saying what happened — as do `unreachable`, `not_media`, `no_audio_stream`, `too_large`, `drm_or_paywall` and `budget_stop`. You are billed for decoded audio minutes and nothing else.

### Output

One JSON row per URL. Delivered rows carry the transcript and `charged: true`; anything that could not be delivered carries a `status` saying why, and `charged: false`. Real rows from a verified run (long text trimmed with … for this page):

| status | charged | chargedMinutes | siteName | title | durationSeconds | language | wordCount | text |
|---|---|---|---|---|---|---|---|---|
| `ok` | `true` | 1 | Loom | Sell Faster and Smarter with Loom | 53.45 | English | 118 | Ready to prospect more efficiently, close more pipeline, and create loyal champions? Loom … |
| `ok` | `true` | 9 | Archive.org | 1 Laying Plans - 2 Waging War | 506.78 | English | 1133 | Part 1. Laying Plans. Sun Tzu said, 1. The Moral Law 2. Heaven 3. Earth 4. The Commander … |
| `no_speech` | `false` | 0 | Wistia | Wistia Product Page - Looping Header | 15.07 | *null* | *null* | *null* |

The last row is a music-only clip with no voice-over: it comes back with its metadata and **is not charged**.

**[Browse a full live example dataset →](https://api.apify.com/v2/datasets/JyenFaGlKrlpQtUre/items?clean=true\&format=json)** — one real run, three rows: a 3-minute public-domain recording transcribed and charged for 4 audio minutes, a music-only clip returned `no_speech`, and a dead link returned `unreachable`. The run charged 4 audio minutes in total — exactly the one row that carries a transcript.

A delivered row in full:

```json
{
  "url": "https://www.loom.com/share/3690d29d5e3c426eb0f7ea00f9d3ae7b",
  "resolvedMediaUrl": "https://cdn.loom.com/sessions/transcoded/3690d29d5e3c426eb0f7ea00f9d3ae7b.mp4",
  "sourceType": "site",
  "siteName": "Loom",
  "title": "Sell Faster and Smarter with Loom",
  "durationSeconds": 53.45,
  "language": "English",
  "languageConfidence": null,
  "text": "Ready to prospect more efficiently, close more pipeline, and create loyal champions? …",
  "segments": [{ "start": 0.88, "end": 7.12, "text": "Ready to prospect more efficiently…" }],
  "srt": "1\n00:00:00,880 --> 00:00:07,120\nReady to prospect more efficiently…",
  "vtt": "WEBVTT\n\n00:00:00.880 --> 00:00:07.120\nReady to prospect more efficiently…",
  "wordCount": 118,
  "status": "ok",
  "retryable": false,
  "charged": true,
  "chargedMinutes": 1,
  "note": null
}
```

Every field is always present — explicit `null` over silent omission. Non-delivered rows carry a `status` and a `note` instead, plus `retryable`: `true` means the host refused us this time (a block, a throttle, a bot page) and running the same input again is worth it; `false` means the answer will not change.

| status | What happened | Charged |
|---|---|---|
| `ok` | Speech transcribed | ✅ per audio minute |
| `no_speech` | Audio decoded, but it is music, silence or a tone | ❌ |
| `no_audio_stream` | The file is video with no audio track at all | ❌ |
| `not_media` | The link returns an image, a feed or a live stream — not a file. If a host served a web page where the file should be, that is a block: the row comes back `retryable: true` | ❌ |
| `no_media` | The page resolved but exposes no audio or video we can fetch | ❌ |
| `unsupported_site` | A page link on a site that is not on the tested list (the note names the actor that does handle it, when one exists) | ❌ |
| `unreachable` | 404 / 410 / DNS failure — permanent. A 401, 403, 451, throttle or timeout is a block, not a missing file: same status, `retryable: true` | ❌ |
| `drm_or_paywall` | The media needs a signed-in account | ❌ |
| `too_large` | Over the per-item size limit, or over your `maxMinutesPerItem` | ❌ |
| `asr_failed` / `failed_download` / `failed_resolve` | Did not complete after all attempts (also listed in the run's `ERRORS` record) | ❌ |
| `asr_unavailable` | Our speech-recognition service refused this actor's access mid-run — that is on us, not you. Remaining items were answered instantly without being started; try again later | ❌ |
| `budget_stop` | The run hit your charge cap, your minute cap, or the run timeout before reaching this item | ❌ |
| `input_error` | The entry is not a usable link — the note says what to pass | ❌ |

This actor may fail when the platform changes things — failed items are never charged.

### For AI agents & LLMs — copy this block

```
Actor:   steadyfetch/media-transcriber  (Apify Store)
Does:    any audio/video URL -> transcript + SRT + VTT + timestamped segments.
         One JSON row per URL. No login, no cookies, no third-party API key.
Run:     POST https://api.apify.com/v2/acts/steadyfetch~media-transcriber/run-sync-get-dataset-items?token=APIFY_TOKEN
Input:
  {
    "urls": ["https://example.com/episode.mp3",
             "https://archive.org/details/some_item",
             "https://soundcloud.com/user/track"],
    "datasetId": "aBcD1234efGh5678i",                      // optional, chain a scraper run
    "datasetItems": [{"videoUrl": "https://cdn/clip.mp4"}],// optional, paste rows instead
    "outputFormats": ["text", "segments", "srt", "vtt"],   // optional, default ["text"]
    "language": "auto",                                    // optional, or an ISO-639-1 code
    "maxMinutesPerItem": 0,                                // optional, 0 = no limit
    "maxTotalMinutes": 0                                   // optional, 0 = no limit
  }
  Omit a field rather than sending null — the platform rejects an explicit null
  before the run starts.
Output   one row per URL: url, resolvedMediaUrl, sourceType, siteName, title,
         durationSeconds, language, text, segments[], srt, vtt, wordCount,
         status, retryable, charged, chargedMinutes, note
         retryable=true on a non-ok row means the host blocked/throttled us —
         re-run the same input. retryable=false means the answer will not change.
Chain    datasetId takes the default dataset of ANY finished actor run. Each row is
         deep-scanned for one audio/video link (videoUrl, audioUrl, mediaUrl,
         playAddr, downloadAddr, a bare file link — at any depth); thumbnails,
         covers and profile pictures are ignored. One media link per row, and a
         link already in `urls` is never transcribed twice. Rows with no media
         link are counted on the run's status line, never charged.
Pricing  pay per event, platform usage INCLUDED, no start fee:
  audio-minute-transcribed   $0.012 -> $0.003 per audio minute (falls with your Apify plan tier)
  Billed per started minute, on decoded audio. Unreachable, silent, oversized and
  unsupported items charge nothing at all.
  Cap any run with maxTotalChargeUsd (minimum $0.05) or with maxTotalMinutes.
MCP:     reachable through Apify's MCP server; ask for "speech to text from a URL".
```

### What this does not do

- **No speaker labels.** This returns one transcript per file with timestamps, not a who-said-what breakdown. If you need speaker separation, this is not the right actor.
- **No translation.** Speech is transcribed in the language it was spoken. About 90 languages are detected automatically.
- **No downloading.** You get text and subtitles, not a media file.

### Which links work

**Any direct media file link, from any host.** `.mp3 .m4a .wav .flac .ogg .opus .aac .aiff` · `.mp4 .m4v .mov .webm .mkv .avi` — signed CDN links and links with no file extension work too; the file is identified by its actual bytes, not by its name.

**Page links, on these 11 tested sites only:**

| | |
|---|---|
| Podcast hosts | Libsyn · Megaphone · Buzzsprout · Acast · Spotify for Podcasters (Anchor) |
| Storage & archives | S3 / CloudFront links · Archive.org |
| Audio & video | SoundCloud · Loom · Twitch VODs · Wistia |

Everything else comes back as an uncharged `unsupported_site` row that says so. We do not claim "1,000+ sites" — this list is what was actually measured.

**Sites we deliberately send elsewhere.** YouTube, Instagram, TikTok, Facebook, LinkedIn and the Google Ads Transparency Center each have a dedicated steadyfetch actor that does the job better and cheaper than a general transcriber can. Paste one of those page links here and the row tells you which actor to use — and if you already hold the direct media file link, paste that instead: it works.

**Known limits, stated plainly.** Vimeo only serves video to a signed-in account, so it cannot be transcribed from a public link. Live streams have no end and are refused before anything is spent — both a live page and a raw ICY/SHOUTcast radio URL are caught on the first few kilobytes, not after a download. Files over 256 MB come back as `too_large` with their measured size — point us at an audio-only version instead.

### How much does it cost to transcribe audio?

**One result = one audio minute actually transcribed.** Billed per started minute, measured from the decoded audio, so a 90-second clip bills 2 minutes. The per-minute price falls with your Apify plan tier — see the Pricing tab.

| Job | Approx. cost at the headline price |
|---|---|
| A 45-minute podcast episode | ≈ $0.14 |
| 20 podcast episodes (~15 hours) | ≈ $2.70 |
| A 3-hour recorded session | ≈ $0.54 |
| Apify's $5 starter credit | ≈ **1,600 audio minutes** |

Platform usage (compute + transfer) is **included in the event price** — the per-minute price you see is the whole price. Unreachable links, music-only audio, oversized files and unsupported pages carry no result fee.

### How to transcribe an mp3 URL (no code)

1. Open the actor and paste your links into **Media URLs**, one per line.
2. Optional: tick **SRT subtitles** and **VTT subtitles** under *Formats to return* — they cost nothing extra.
3. Optional: set **Max total minutes** if you want a hard ceiling on the run.
4. Click **Start**. Each link comes back as one JSON row. Export as JSON/CSV, or read it over the API.

Leave the list empty and the actor returns a single uncharged demo row showing the output shape.

### Chain it after any scraper

You do not have to paste links by hand. Run whatever scraper you already use, then put its **Dataset ID** into this actor — with Apify's *Connect actor* integration the field fills itself, so the two runs become one workflow.

Every row is deep-scanned for a single audio or video link, whatever the scraper calls it: `videoUrl`, `audioUrl`, `mediaUrl`, `playAddr`, `downloadAddr`, a bare file link, nested or top-level. Thumbnails, covers and profile pictures are never mistaken for media, a row's video and its audio track count as **one** item (never two charges), and a link you also listed in *Media URLs* is transcribed once. Rows carrying no media link are counted on the run's status line and cost nothing. If you would rather not chain by ID, paste the rows straight into **Dataset items**.

Page links belonging to YouTube, Instagram, TikTok, Facebook, LinkedIn or the Google Ads Transparency Center behave the same way here as anywhere else in this actor — an uncharged row naming the dedicated actor for that platform.

### FAQ

**How do I transcribe an mp3 URL?** Paste the link into *Media URLs* and start the run. Any host works — the file is identified by its bytes, so signed CDN links and extension-less links are fine.

**Speech to text API without a subscription?** That is this actor. You pay per audio minute transcribed, with no monthly fee and no start fee, through your existing Apify account.

**How do I transcribe a podcast episode to SRT?** Paste the episode's audio URL (or its page on Libsyn, Megaphone, Buzzsprout, Acast or Spotify for Podcasters), tick *SRT subtitles*, and the `srt` field comes back ready to save as a `.srt` file.

**What happens if the link is wrong?** The actor reads about 4 KB of the response and checks the actual bytes before downloading anything. A web page, an image or a dead link comes back as an uncharged row telling you what it found.

**Music-only audio, or a video with no voice-over?** Speech models hallucinate filler like "Thank you." over music. This actor detects that in any language and returns an honest, uncharged `no_speech` row instead of selling you a fake transcript.

**How long can a file be?** There is no duration limit. Long recordings are transcribed in overlapping windows and stitched back together with continuous timestamps; the row says when that happened. The only size limit is 256 MB per file.

**Which languages?** Whisper-class multilingual recognition — English, Spanish, German, Japanese, Arabic, Portuguese, Indonesian and about 90 more, detected automatically. The detected `language` ships on every row.

**Do I need cookies, a login, or my own API key?** No. The actor fetches public media only.

**Can I feed it the output of another actor?** Yes — put that run's **Dataset ID** in, or paste its rows into *Dataset items*. The actor finds the media link in each row itself, so you do not have to know which column your scraper used. See *Chain it after any scraper* above.

**Why did some chained rows come back with nothing?** Rows that carry no audio or video link are skipped, counted on the run's status line, and never charged — an image post or a comment row has nothing to transcribe.

**Can I cap what a run spends?** Two ways: `maxTotalMinutes` stops the run after that many charged audio minutes, and Apify's own *Maximum cost per run* caps the charge in dollars. Either way the run stops cleanly and the items it did not reach come back as uncharged `budget_stop` rows.

### Also from steadyfetch

**Speech to Text · Any Link or File** is the general-purpose transcriber: any direct audio or video
link, or a file you host. For the platforms below, use the platform's own actor — it reads the platform's
listing, carries its metadata, and costs less per row.

**The rest of the steadyfetch shelf** — same contract everywhere: all-inclusive pay per event,
no start fee, charged only on delivery.

| Family | Actors |
|---|---|
| Ad creative intelligence | [Facebook](https://apify.com/steadyfetch/facebook-ads-transcript-scraper) · [Google Ads video](https://apify.com/steadyfetch/google-ads-video-transcript-scraper) · [TikTok](https://apify.com/steadyfetch/tiktok-ads-transcript-scraper) · [LinkedIn](https://apify.com/steadyfetch/linkedin-ads-transcript-scraper) · [Google Ads text & OCR](https://apify.com/steadyfetch/google-ads-creative-text-scraper) |
| Trends & keywords | [Google Trends](https://apify.com/steadyfetch/google-trends-scraper) · [Trends Now](https://apify.com/steadyfetch/google-trends-now-scraper) · [Breakout keywords](https://apify.com/steadyfetch/breakout-keywords-scraper) · [Autocomplete keywords](https://apify.com/steadyfetch/google-keyword-suggest-scraper) · [Keyword volume & CPC](https://apify.com/steadyfetch/keyword-search-volume-scraper) · [Social trends](https://apify.com/steadyfetch/social-trends-scraper) |
| YouTube transcripts | [YouTube videos](https://apify.com/steadyfetch/youtube-transcript-scraper) · [YouTube channels](https://apify.com/steadyfetch/youtube-channel-transcripts) |
| Instagram | [Reel transcripts](https://apify.com/steadyfetch/instagram-reel-transcript-scraper) · [Profile posts](https://apify.com/steadyfetch/instagram-profile-posts) |
| Jobs | [Indeed](https://apify.com/steadyfetch/indeed-jobs-scraper) · [Career sites by domain](https://apify.com/steadyfetch/company-jobs-by-domain) · [Glassdoor](https://apify.com/steadyfetch/glassdoor-jobs-scraper) · [Multi-board](https://apify.com/steadyfetch/multi-job-board-scraper) · Google Jobs |
| Amazon | [Products](https://apify.com/steadyfetch/amazon-product-scraper) · [Search](https://apify.com/steadyfetch/amazon-search-scraper) · Bestsellers · Sellers |
| **Any media file** | [Speech to Text · any link or file](https://apify.com/steadyfetch/media-transcriber) |

Unlinked names are publishing shortly on the same account — search **steadyfetch** on Apify Store.

Ready-made n8n templates for the suite: **[github.com/steadyfetch/n8n-templates](https://github.com/steadyfetch/n8n-templates)** — no community nodes needed.

### Feedback & support

Found an issue? Open it on the **Issues tab** — issues are answered in about 3 hours, and always within one business day. Feature requests welcome, especially a site you would like added to the tested list.

# Actor input Schema

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

Direct audio/video file links, or page links on a supported site. Leave empty to see one uncharged demo row. Omit the field rather than sending null — the platform rejects an explicit null before the run starts.

## `datasetId` (type: `string`):

The default dataset ID of a finished scraper run. With Apify's 'Connect actor' integration this is filled automatically — the cleanest way to chain. One media link per row is transcribed; a link that is already in "Media URLs" is never transcribed twice.

## `datasetItems` (type: `array`):

Paste rows from a scraper run instead of chaining by ID. The actor deep-scans each row for an audio or video link — works with any scraper's output shape, including nested posts. Thumbnails, covers and profile pictures are never mistaken for media.

## `outputFormats` (type: `array`):

Plain text is always returned. Add timestamped segments and ready-made SRT / VTT subtitle files at no extra cost.

## `language` (type: `string`):

Leave as "auto" to detect the spoken language automatically (about 90 languages are supported). Otherwise give a two-letter ISO 639-1 code such as en, es, de, ja, ar.

## `maxMinutesPerItem` (type: `integer`):

Skip any single item longer than this many audio minutes. 0 means no per-item limit. Skipped items come back as uncharged rows saying how long they were.

## `maxTotalMinutes` (type: `integer`):

Stop the run once this many audio minutes have been charged in total. 0 means no limit. The run stops cleanly and the items it did not reach come back as uncharged rows.

## Actor input object example

```json
{
  "urls": [],
  "outputFormats": [
    "text"
  ],
  "language": "auto",
  "maxMinutesPerItem": 0,
  "maxTotalMinutes": 0
}
```

# Actor output Schema

## `transcripts` (type: `string`):

One row per URL: transcript text, word count, language, duration, and optional timestamped segments plus SRT and VTT. Every row carries a status (ok / no\_speech / unsupported\_site / unreachable / not\_media / too\_large / …) and a charged flag. Only 'ok' rows are charged, and only for the audio minutes they actually delivered.

## `summary` (type: `string`):

Delivered items, audio minutes charged, uncharged misses, failures, any clean stop, and which sources and formats this run used.

## `errors` (type: `string`):

Present when a fetch or the speech step failed after all attempts: the URL, the status, and the reason.

# 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": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/media-transcriber").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": [] }

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/media-transcriber").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": []
}' |
apify call steadyfetch/media-transcriber --silent --output-dataset

```

## MCP server setup

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

```

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/JHpv1LcFz3wD942Bo/builds/DKf3TA24ETsdpDE7K/openapi.json
