# Audio to Text: Uploaded & Direct Audio Files (`tidytools/audio-to-text-files`) Actor

Convert audio files to text: paste direct MP3, WAV, M4A, OGG or FLAC links or upload recordings, and get each transcript as text plus TXT and Markdown files. Whisper, 90+ languages, $0.006 per audio minute. Failed or silent files are free.

- **URL**: https://apify.com/tidytools/audio-to-text-files.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.80 / 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?

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

### What does Audio to Text: Uploaded & Direct Audio Files do?

**Paste links to your audio files or upload recordings, and get each one back as a text transcript with timestamps plus a TXT and a Markdown file, for $0.006 per audio minute (Whisper, 90+ languages). Failed or silent files are free.**

It is made for audio files you already have: MP3, WAV, M4A, AAC, OGG, OPUS, FLAC and other formats ffmpeg can read, from a direct download link or uploaded in Apify Console. Video files (MP4, MOV, WEBM...) work too; only their sound is used. Files up to 2 GB, any length.

#### How to use: convert an audio file to text

1. Paste direct file links, one per line, into **Audio file URLs** (`urls`). Each link must download in a signed-out or private browser window. Or upload recordings from your computer under **Or upload audio files** (`audioFiles`).
2. Optional: set the **Language** (empty = detected automatically) and **Vocabulary hints** for names and terms.
3. Click **Start**. Each file becomes one row with the transcript (`text`), its language, the billed minutes and a link to its `.txt` file (`txtUrl`; the Markdown file is `mdUrl`).
4. Open the files from those links, download the dataset as JSON, CSV or Excel, or call the Actor from the API.

#### First run: try the prefilled example

The form already holds one short English speech sample in **Audio file URLs**: click **Start** without changing anything. It produces one row with the transcript and `charged: true`, billed one audio minute ($0.006). No podcast feed is prefilled in this version, so nothing else is transcribed. Then replace the sample with your own links or uploads.

Choose a recording with spoken words. A music-only or silent file comes back as a free row with `errorType: "no_text"` and `noSpeech: true`.

#### Output

The default table shows one row per file:

| Column | Meaning |
|---|---|
| `input` | The line you gave (link or uploaded file) |
| `url` | The audio file that was processed |
| `text` | The full transcript (empty on a no-speech row) |
| `language` | Language of the transcript, e.g. `en` |
| `txtUrl` | Link to the plain-text transcript file |
| `billedMinutes` | Audio minutes charged for this row (0 on every free row) |
| `charged` | `true` when this row was billed, `false` on every failure, skipped or no-speech row |
| `errorType` | Why a row failed, e.g. `invalid_input`, `no_text`, `not_found`, `blocked`, `too_large` (absent on success) |

The full JSON row also has timed `segments`, `paragraphs`, `durationSeconds`, `wordCount` and `mdUrl`; the **Transcripts (all columns)** view shows more of them. With two or more transcripts in a run, one combined TXT and Markdown file is saved too (`SUMMARY.combinedTranscript`).

**Subtitles are off by default here.** Turn on **Save SRT and VTT subtitles** (`saveSubtitles: true`) to also get `srtUrl` and `vttUrl` for every file, at no extra charge.

#### Pricing

| Event | Price (Free and Bronze plans) | When |
|---|---|---|
| Audio minute | $0.006 (Silver $0.0054, Gold and above $0.0048) | Each started minute of a transcribed file |
| Audio minute with speaker labels | $0.015 (Silver $0.0135, Gold and above $0.012) | Instead of the regular minute, only with **Speaker labels** on |
| AI summary and chapters | $0.01 per file | Only with **AI summary and chapters** on |
| Translated subtitles | $0.002 per minute and language | Only with **Translated subtitles** set, for languages that succeed |

A 60-minute recording costs $0.36. Rows with `success: false` are never charged: a line that is not a link (`invalid_input`), a missing or refused file, a file with no speech (`no_text`), files skipped at your limits. Set Apify's **Maximum cost per run** in Run options or **Max total minutes per run** (`maxTotalMinutes`) to cap a run.

#### Other sources still work

This Actor runs the same transcription engine, prices and rules as [Audio Transcription](https://apify.com/tidytools/audio-transcriber) (`tidytools/audio-transcriber`). Only the form is set up for audio files: the file fields come first, the example is a single speech sample, no podcast feed is prefilled and subtitles are off by default. Nothing else is turned off. Everything the engine accepts is still processed and charged the same way when you add it, in the form under **Other sources (optional)** or in the JSON input:

- Google Drive, Dropbox, OneDrive and other share links ("anyone with the link"), presigned storage links and web pages with one audio or video file, pasted in `urls`.
- YouTube, TikTok, Instagram Reel, Loom, Wistia, Streamable and v.redd.it video links, pasted in `urls`.
- Podcast RSS feeds or Apple Podcasts links (`podcastFeeds`, or pasted in `urls`), with episode count, date and title filters.
- Another Actor's dataset (`datasetId`) or its rows pasted into `datasetItems`, with `datasetField` naming the URL field (dot paths such as `media.url` work). Only the media link is used; other columns of those rows are not copied to the results.
- The request-list field `audioUrls` of earlier integrations.

See the [Audio Transcription README](https://apify.com/tidytools/audio-transcriber) for the details of each source, speaker labels, keyword search, meeting notes, schedules and the n8n / Make integration.

#### How files are downloaded

- Each file is requested with our own User-Agent (`TidyToolsTranscriber`), without cookies or logins. With **Download User-Agent** on Auto (default), a file whose host refuses it is requested once more with a standard desktop browser User-Agent string (a plain header; no CAPTCHA solving, no browser automation). Choose "TidyTools only" to never switch.
- With **Download files from** on Auto (default), a file that Apify's network cannot get is tried through our own servers. When a host sends Retry-After, the Actor waits that long.
- robots.txt is not read for the links you give: each link is fetched as one user-supplied file or page, nothing is crawled. Reddit post and profile pages are never read (Reddit's robots.txt does not allow it); pass the v.redd.it video link instead.
- A file that is private, missing, expired or refused comes back as a free row that says why (`not_found`, `link_expired`, `blocked`, ...), and `retryable: true` marks the ones worth trying again later.

#### Input (copy and paste)

```json
{
    "urls": ["https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"],
    "language": "en"
}
```

#### Limitations

- Each link must be a public file that downloads without signing in; files up to 2 GB. The Actor never uses cookies or logins and never solves CAPTCHAs, so private or refused files come back as free error rows.
- Only spoken words are transcribed: music-only or silent files are free `no_text` rows.
- Uploads (`audioFiles`) work in Apify Console only; in API calls pass file URLs in `urls`.
- Speaker labels are not available for some languages (e.g. Chinese) or for YouTube links; those rows fail as free `unsupported` rows.
- Reddit post and profile pages are not read; use the v.redd.it video link.

#### FAQ

**Why is a row `no_text`?** No speech was found (silence or music only). The row is free; check that the recording has spoken words.

**A link fails with `invalid_input`.** The line is not a URL. Put one link per line; each bad line is its own free row and the other files still run.

**Can I upload files through the API?** In API calls, pass file URLs in `urls`. `audioFiles` is the upload field of Apify Console.

**Where are the subtitle files?** Turn on `saveSubtitles` to get `srtUrl` and `vttUrl`; they are off by default in this version.

### Optional content pack

Set `"contentPack":true` for chapter, show notes, X and LinkedIn drafts in `contentPack`. Add `"contentPackSubtitleLanguages":["Spanish"]` for one translated SRT/VTT language in `contentPack.translatedSubtitles` (the existing $0.002/started-minute subtitle event applies on success). Drafts require review before publishing. Off by default; existing output and charges stay the same. A failed pack has `contentPackError` and no pack charge. **Charge starts 2026-11-06 or later:** one $0.10 `content-pack` event per successful pack, in addition to existing audio-minute charges. Until the operator sets `CONTENT_PACK_CHARGE_FROM` to an announced date on or after 2026-11-06 and that date arrives, the text pack is free.

# Actor input Schema

## `contentPack` (type: `boolean`):

Off by default. Draft chapters, show notes, X and LinkedIn posts. One content-pack event at $0.10 per successful episode from 2026-11-06; free before then. Translated subtitles use the existing minute event.

## `contentPackSubtitleLanguages` (type: `array`):

One optional translated SRT/VTT language billed by the existing subtitle-language-minute event.

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

Direct links to audio files (MP3, WAV, M4A, OGG, FLAC, OPUS, AAC; video files such as MP4 work too), one per line. Each file must download without signing in. The example is a short English speech sample (one billed minute, $0.006). Choose recordings with spoken words: music-only or silent files return a free no_text row. Files up to 2 GB.

## `audioFiles` (type: `array`):

Upload recordings from your computer in Apify Console (they are stored in a key-value store of your account and read from there). Same formats and 2 GB limit as the links above. In API calls, pass file URLs in "urls" instead.

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

ISO 639-1 code such as en, es, de, ja or zh (names such as English also work). Leave empty to detect automatically. YouTube links are always transcribed in the spoken language; when it differs from this, the row says so in languageNote.

## `vocabulary` (type: `string`):

Names, brands or terms that appear in the audio, e.g. "Apify, Cloudflare, Kubernetes". Helps spell them correctly.

## `transcriptionEngine` (type: `string`):

Choose "fast" (default, Cloudflare Workers AI Whisper) or "accurate" (self-hosted Whisper with more thorough decoding), e.g. {"transcriptionEngine":"accurate"}. Accurate is slower; busy pieces fall back to Fast and each result reports asrBackend. Both cost the same. Speaker labels use Deepgram; YouTube links use the YouTube service regardless of this setting.

## `saveTranscriptFiles` (type: `boolean`):

Save a plain-text (.txt) and a Markdown (.md) transcript for every file, linked as txtUrl and mdUrl, plus one combined file for the run when there are 2 or more transcripts (no extra charge). On by default.

## `saveSubtitles` (type: `boolean`):

Also save SRT and VTT subtitle files for every transcript, linked as srtUrl and vttUrl (no extra charge). Off by default in this version; turn it on if you need subtitles.

## `summarize` (type: `boolean`):

Add a short summary and timestamped chapters to each transcript. Charged as a separate event per file (see Pricing). Recordings shorter than 2 minutes get a summary only.

## `meetingNotes` (type: `boolean`):

Add structured meeting notes to each transcript: a short summary, decisions, action items (task, owner, due date) and, with speaker labels on, talk time per speaker and each speaker's main points. Saved in the row (meetingNotes) and as a Markdown file (meetingNotesUrl). No extra charge.

## `speakerLabels` (type: `boolean`):

Label each segment and paragraph with Speaker 1, Speaker 2… and count the speakers. Uses Deepgram nova-3 instead of Whisper and is billed as "audio minute with speaker labels" ($0.015/min) instead of the regular audio minute. Some languages (e.g. Chinese) are not available; those files fail with errorType "unsupported" and are not charged. Not available for YouTube links (free "unsupported" row).

## `wordTimestamps` (type: `boolean`):

Add a words array \[{word, start, end}] to every segment (for video editing and precise subtitle alignment). Included in the regular $0.006/min price. With speaker labels on, the words come from nova-3 and the speaker-label price applies (not charged twice).

## `subtitleLanguages` (type: `array`):

Also make SRT + VTT subtitles in these languages (up to 5), e.g. Spanish, German, Traditional Chinese, ja. Timings and speaker labels are kept. $0.002 per audio minute per language, charged only for languages that succeed.

## `searchTerms` (type: `array`):

JSON array of up to 50 brands, names or topics to find, e.g. {"searchTerms":\["Apify","customer churn"]}; in the form, enter one per line. Matches include timestamp, sentence, speaker and a link to that moment; the run also saves KEYWORD_MATCHES.csv. Matching ignores case and accents; a trailing \* matches word endings, e.g. "transcri\*". No extra charge.

## `audioUrls` (type: `array`):

Same as "Audio or video file URLs", in Apify's request list format (also accepts a link to a text file with URLs). Kept for existing integrations; new integrations should use "urls".

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

A dataset from another Actor run, e.g. a podcast or video scraper. Its media URLs are added to the list above. Have the rows themselves instead? Paste them into "datasetItems" below.

## `datasetField` (type: `string`):

Field that holds the media URL, e.g. audioUrl or media.url (a dot path, for nested fields). Leave empty to detect it (audioUrl, mediaUrl, videoUrl, url...). Used for the dataset above and for pasted dataset rows.

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

JSON array of rows from another Actor dataset, e.g. {"datasetItems":\[{"audioUrl":"https://example.com/episode.mp3"}]}. Each row needs a media URL field (audioUrl, mediaUrl, videoUrl, url, etc.), or set datasetField to its field name; dot paths work. A row may itself be a URL. Rows without usable links get free error rows with their index; duplicate media is transcribed once. Other source columns are not copied to results.

## `podcastFeeds` (type: `array`):

Podcast RSS feed URLs or Apple Podcasts show links. The newest episodes are transcribed (see the next field) and charged per audio minute like files. Empty by default in this version.

## `maxEpisodesPerFeed` (type: `integer`):

How many of the newest episodes to transcribe from each feed.

## `usePublisherTranscripts` (type: `boolean`):

Many podcasts publish their own transcript in the feed (Podcasting 2.0 <podcast:transcript>: VTT, SRT, JSON, HTML or text). When an episode has one, it is downloaded and converted to the same output (text, segments, SRT/VTT for timed formats) with transcriptSource "publisher", and no audio minutes are charged. A transcript that is empty or does not match the episode length is ignored and the audio is transcribed as usual.

## `episodesSince` (type: `string`):

Only feed episodes published on or after this date (YYYY-MM-DD), or within e.g. "30 days". Applied before "Episodes per podcast feed". Filtered episodes get no row; SUMMARY.episodesFiltered counts them.

## `episodesUntil` (type: `string`):

Only feed episodes published on or before this date (YYYY-MM-DD, the whole day counts).

## `titleIncludes` (type: `array`):

Only feed episodes whose title contains one of these texts (case-insensitive). \* matches anything, e.g. "interview\*ceo".

## `titleExcludes` (type: `array`):

Skip feed episodes whose title contains one of these texts (case-insensitive), e.g. "trailer", "rerun", "best of".

## `maxMinutesPerFile` (type: `integer`):

Skip files longer than this. Skipped files get a result row with errorType "too_large" and are not charged. Empty = no limit (0 also means no limit). YouTube videos: empty = 240 minutes (the maximum).

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

Stop charging for new files once this many audio minutes were transcribed. Files that would go over it get a row with errorType "limit_reached" and are not charged. Empty or 0 = no limit (your Apify spending limit still applies).

## `maxConcurrency` (type: `integer`):

Long files are split into 90-second pieces that are transcribed in parallel. More is faster.

## `fileConcurrency` (type: `integer`):

How many files (podcast episodes, URLs) are downloaded and transcribed at the same time. More is faster for batches; rows then arrive in the order files finish (inputIndex gives the input position). Your max charge per run and maxTotalMinutes are still respected: the cost of every file in progress is reserved before it is transcribed. With little run memory fewer files run at once (about 3 at 1024 MB).

## `fileTimeoutSecs` (type: `integer`):

A file whose download, conversion and transcription take longer than this is reported with errorType "timeout" and not charged; the run goes on with the next file. Raise it for recordings of several hours. YouTube videos get at least their length x 15 s + 20 min (our YouTube queue decides how long they take). 0 = no limit.

## `proxyConfiguration` (type: `object`):

Only used for requests sent from Apify's network (file downloads and podcast feeds). Apify proxy usage is billed to your Apify account.

## `downloadVia` (type: `string`):

Some file hosts refuse downloads from data centers or after many files in a row (HTTP 401/403/429 or a bot-check page). Auto then tries our backend (any size), our Oracle server (up to 15 MB) and our home line in Taiwan (up to 10 MB) before giving up. Each failed file is a free row that lists what was tried. No extra charge. We never use cookies or logins and never solve CAPTCHAs; when a host sends Retry-After we wait that long.

## `downloadUserAgent` (type: `string`):

Every file is first requested with our own User-Agent (TidyToolsTranscriber). Some hosts serve media files only to browsers: with Auto, a file that was refused is requested once more with a standard desktop browser User-Agent string (a plain header; no cookies, logins, CAPTCHA solving or browser automation). Choose TidyTools only to never switch. Only used for the audio/video file links you give.

## `newEpisodesOnly` (type: `boolean`):

Remember what was transcribed (podcast episode GUID or file URL) and skip it in later runs with the same monitor name. From podcast feeds, only episodes published after the newest one already transcribed are taken, so a run on a day without a new episode transcribes (and charges) nothing. Failed files are retried next time.

## `backfillOlderEpisodes` (type: `boolean`):

With "Only new episodes": also take older episodes that were never transcribed (up to Max episodes per feed per run), to work through a back catalogue over several runs. Off by default, so scheduled runs only pick up new releases.

## `monitorName` (type: `string`):

Runs with the same name share the list of transcribed files, e.g. one name per podcast schedule. Letters, digits and dashes.

## `sourceFileId` (type: `string`):

Your own id of the file (Drive file id, Dropbox file id `id:...`, storage object key...). Used for runs with one file; it is copied to the row so your workflow can match results, and it is the key of `resumeStore`. Without it, an id is derived from the link (Drive file id, Dropbox shared link id, the bucket and object of a presigned URL). Every row also has this run's `runId`.

## `resumeStore` (type: `string`):

Optional. A name such as `n8n-recordings`: finished transcripts are remembered by source file id in a named key-value store of your Apify account. When the same file is sent again with the same settings (a workflow retry), its saved transcript is returned again with `reused: true` and is not charged again; if another run is still transcribing that file, this run waits for it. Leave empty to turn off.

## Actor input object example

```json
{
  "contentPack": false,
  "contentPackSubtitleLanguages": [],
  "urls": [
    "https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"
  ],
  "transcriptionEngine": "fast",
  "saveTranscriptFiles": true,
  "saveSubtitles": false,
  "summarize": false,
  "meetingNotes": false,
  "speakerLabels": false,
  "wordTimestamps": false,
  "subtitleLanguages": [],
  "podcastFeeds": [],
  "maxEpisodesPerFeed": 1,
  "usePublisherTranscripts": true,
  "maxConcurrency": 3,
  "fileConcurrency": 3,
  "fileTimeoutSecs": 1800,
  "downloadVia": "auto",
  "downloadUserAgent": "auto",
  "newEpisodesOnly": false,
  "backfillOlderEpisodes": false
}
```

# Actor output Schema

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

No description

## `full` (type: `string`):

No description

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

No description

## `details` (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 = {
    "contentPackSubtitleLanguages": [],
    "urls": [
        "https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"
    ],
    "subtitleLanguages": [],
    "podcastFeeds": [],
    "maxEpisodesPerFeed": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/audio-to-text-files").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 = {
    "contentPackSubtitleLanguages": [],
    "urls": ["https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"],
    "subtitleLanguages": [],
    "podcastFeeds": [],
    "maxEpisodesPerFeed": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("tidytools/audio-to-text-files").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 '{
  "contentPackSubtitleLanguages": [],
  "urls": [
    "https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"
  ],
  "subtitleLanguages": [],
  "podcastFeeds": [],
  "maxEpisodesPerFeed": 1
}' |
apify call tidytools/audio-to-text-files --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/audio-to-text-files"
        }
    }
}
```

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/40YN2yrWrfachO2vE/builds/U32GYnMZGoZvoyk99/openapi.json
