# TikTok Transcript Scraper & API: Video & Speech to Text (`quarra/tiktok-transcript-scraper`) Actor

Turn TikTok videos into text, from links, short links or an account's latest videos. TikTok's own captions when they exist, AI speech-to-text otherwise, with timestamps, SRT and VTT and the language spoken. No login. Music-only videos are not charged.

- **URL**: https://apify.com/quarra/tiktok-transcript-scraper.md
- **Developed by:** [Quarra Labs](https://apify.com/quarra) (community)
- **Categories:** Social media, Videos, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.75 / 1,000 transcripts

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

## TikTok Transcript Scraper: Video to Text, Captions and SRT

Turn TikTok videos into text, without an account or API key. Paste video links (short `vm.tiktok.com` links too)
or give an account, and each video comes back with its transcript as plain text and as timestamped segments, its
language, optional SRT and WebVTT subtitle files, and the video's metadata (account, date, views, likes, comments,
shares, saves, caption, sound).

We use TikTok's own captions when the video has them (written by the creator or automatic): they are returned as
they are, instantly, and cost no speech-to-text minutes. Otherwise the audio is transcribed by AI. Music-only videos
come back as a free `no_speech` record instead of a made-up transcript. You pay only for transcripts delivered.

### What we measured

On the Apify platform on 8 October 2026, with the default settings, on 42 inputs: links taken from Reddit posts that
shared TikTok videos (most of them `vm.`, `vt.` and `/t/` short links), three accounts and a video ID:

- **26 transcripts** in 5 languages (English, Japanese, Spanish, Portuguese, Chinese): **22 from TikTok's own
  captions** (no minutes billed), 4 from speech-to-text.
- **15 videos without clear speech or without a video** (music, loops, uncertain language, photo slideshows): free
  `missing` records.
- **5 failures** with their reason, all free: deleted, private and age-restricted videos, an expired short link, an
  account that does not exist.
- 46 records in 93 seconds. The default input (one video) runs in about 7 seconds.

Running the same input again (13 records: damaged links, music videos, an account, unknown accounts) gave the same
verdict on every record.

Speech-to-text runs on third-party AI providers: OpenAI's Whisper models (large-v3 and large-v3 turbo) run by Groq
for most videos, and ElevenLabs Scribe, a stronger model, to settle the language when hints disagree and as a
backup. The audio is sent to them only to be transcribed. ElevenLabs stores each transcript on its side: we delete
it as soon as we have read it and check that it is gone (we read it again and get "not found"); if a deletion cannot
be confirmed before the run ends, the run log says so.

On the videos that have both, TikTok's automatic captions and Whisper differ by about 7 % of words (mostly
punctuation and names; measured on 23 videos). Whisper is known to invent short phrases on music ("Thank you.",
"Music", "Bye."); we measured that on 170 videos and filter it: a phrase the model is not confident about, a known
phantom phrase, or a few words spread over a long stretch of music are not speech. A video whose speech is all
filtered out is `missing` with `missingReason: "no_speech"`, free. We never sell part of a video as its transcript:
when the filter has to leave out more than 20% of the speech (the model looped or was unsure over that part), the
video is `missing` with `missingReason: "incomplete_transcript"`, free. Loops are measured per character, so Hindi,
Urdu, Arabic or Tamil text is judged like English (until 8 October 2026 a byte-based measure dropped correct Hindi
sentences as "loops"). Whisper's output is also capped for each 30 seconds: dense speech in some scripts
(Gujarati, Telugu, fast Hindi) loses the end of each 30 seconds without a trace. We detect it; such a video goes to
a second, stronger model, and is never charged as a fragment.

TikTok's own captions pass the same checks before they are used: captions that are only a sound tag ("\[Music]"), a
lone phantom phrase ("Thank you."), a loop, or run longer than the video (captions of another video) are not sold;
the audio is transcribed instead. A sound tag inside a caption line ("\[Applause]", "♪") is removed from the text. Captions with no language are not used either.

### What you can transcribe

| Input | Examples | What you get |
|---|---|---|
| Videos | `https://www.tiktok.com/@natgeo/video/7694024577701137677`, `7694024577701137677` | that video |
| Short links | `https://vm.tiktok.com/…`, `https://vt.tiktok.com/…`, `https://www.tiktok.com/t/…` | the video they lead to |
| Other forms | `m.tiktok.com/v/….html`, `/embed/v2/…`, `/player/v1/…` | that video |
| Accounts | `@natgeo`, `https://www.tiktok.com/@natgeo` | the account's latest videos |

Mix them in one run. A video found in two inputs (a link and a short link, or a link and an account) is transcribed and
charged once.

**Accounts:** we read the list TikTok publishes for embedding an account on other websites: about 10 of its latest
videos (pinned ones first), without signing in. TikTok's full video list needs signed requests that we do not make;
for older videos, paste their links. Photo slideshows do not take the place of a video, and when the list holds fewer
videos than `maxVideosPerAccount`, the status says so.

**Damaged links are refused, never guessed.** A video link with a cut or extra character (`…/video/76941.`, an empty
`/video/`) gets a free `invalid_url` record; it is never read as the account in the link. An account is only `@name`
or `tiktok.com/@name` with nothing after it.

### Languages

TikTok's own automatic captions are used when they match the language of the post; when they do not (for example
English speech captioned by TikTok's Japanese recognition), the audio is transcribed instead. For speech-to-text,
we do not trust a single language detection when anything says otherwise. Hints: the caption's script (Devanagari,
Hangul, kana…), Hindi written in Latin letters, and the language TikTok gives the post (English is not a hint:
Whisper only translates into English, so it cannot catch a translation). When a hint and the detection disagree, a
stronger speech-to-text model settles the language and transcribes the video (a Korean song that the fast model
"translated" into English comes back in Korean); Hindi, Urdu and Punjabi go to it too. When the ElevenLabs referee is
not available for a video, Whisper large-v3 settles the language and must agree, or nothing is sold. The referee is
paid per use, so a run calls it only once the transcripts it has delivered cover the cost: in a larger run such a
video waits for the others; sent alone, or in a run that delivers little, it can come back as a free
`uncertain_language` record. If the language
still cannot be settled, the video comes back as a free `uncertain_language` record, never as a charged guess and
never as a translation. Hindi and Urdu are one spoken language in two scripts: the caption's script decides.

`language` is a preference, never an instruction: it changes neither the models used nor the number of passes, and
never forces a translation (`requestedLanguageFound: false` when the video is in another language). With `language`
set to Hindi, Urdu or Punjabi, a video that Whisper hears as English (with no caption or TikTok language saying
otherwise) comes back as a free `uncertain_language` record: on those languages Whisper sometimes writes an English
translation instead of what was said.

### Output

One record per video. A transcript (`status: "ok"`), shortened here:

```json
{
  "status": "ok",
  "platform": "tiktok",
  "videoId": "7694024577701137677",
  "url": "https://www.tiktok.com/@natgeo/video/7694024577701137677",
  "author": {
    "username": "natgeo",
    "fullName": "National Geographic",
    "id": "6780344874811442181",
    "isVerified": true,
    "url": "https://www.tiktok.com/@natgeo"
  },
  "caption": "The shoebill's prehistoric appearance makes it unique among living birds, …",
  "hashtags": null,
  "publishedAt": "2026-10-07T20:25:15Z",
  "viewCount": 6529,
  "likeCount": 529,
  "commentCount": 6,
  "shareCount": 5,
  "saveCount": 18,
  "sound": { "title": "original sound", "author": "National Geographic", "isOriginal": true },
  "durationSeconds": 105,
  "language": "en",
  "transcriptSource": "captions",
  "isAutoGenerated": true,
  "hasSpeech": true,
  "transcript": "Did you know shoebills attack their own siblings? Five surprising facts about the shoebill. …",
  "segments": [
    { "start": 0.06, "end": 2.94, "duration": 2.88, "text": "Did you know shoebills attack their own siblings?" },
    { "start": 2.941, "end": 5.061, "duration": 2.12, "text": "Five surprising facts about the shoebill." }
  ],
  "segmentCount": 37,
  "wordCount": 268,
  "characterCount": 1671,
  "billedMinutes": 0,
  "input": "@natgeo",
  "inputType": "account",
  "position": 3,
  "scrapedAt": "2026-10-08T00:10:12.607Z"
}
```

A video without a transcript comes back as a free record that holds the reason and nothing else (no account, counts
or caption: those come with transcripts):

```json
{
  "status": "missing",
  "platform": "tiktok",
  "videoId": "7693616413134703886",
  "url": "https://vm.tiktok.com/ZN8kbSguY/",
  "missingReason": "photo_post",
  "message": "this is a photo post (slideshow), not a video",
  "input": "https://vm.tiktok.com/ZN8kbSguY",
  "inputType": "video"
}
```

#### Fields that need a word of explanation

- `transcriptSource`: `captions` (TikTok's own captions; `isAutoGenerated: false` when the creator wrote them) or
  `speech_to_text` (AI transcription of the audio). Turn off `useTikTokCaptions` to send every video to
  speech-to-text.
- `status`: `ok` (transcript, charged), `missing` (the video exists but has nothing to transcribe: `no_speech`,
  `incomplete_transcript`, `no_audio`, `photo_post`, or `too_long` past your limit; free) or `error` (the link could
  not be read; free).
- `errorCode` and `retryable`: `not_found` (deleted video or expired short link), `private`, `age_restricted`,
  `region_blocked`, `not_a_video` (a short link that leads to an account), `invalid_url`, `account_not_found`,
  `account_private`, `account_empty` are final; `unavailable`, `blocked`, `network`, `transcription_capacity` and
  `transcription_failed` are worth retrying later (`retryable: true`).
- `language`: ISO 639-1 code of the language spoken and transcribed. We never translate: if you set `language` and a
  video is in another language, you get the language actually spoken, with `requestedLanguageFound: false`.
- `filteredSegments`: segments of the raw speech-to-text output left out of the transcript as non-speech (a
  "Thank you." over silence, a "\[Music]" tag, a model loop); minutes are billed on the video's length as usual.
  A model loop or an unsure passage that held more than 20% of the speech makes the video a free
  `incomplete_transcript` record instead.
- `billedMinutes`: started minutes billed as speech-to-text for this transcript; 0 for TikTok's captions.
- `segments`: `start`, `end` and `duration` in seconds. `includeSrt` and `includeVtt` add ready-to-save `.srt` and `.vtt` files.
- `input` and `position`: which of your inputs produced the record, and the rank of the video in an account's list.
  Records are written as each video finishes: sort by `input` and `position` for the source's order.

The dataset has three views: Overview, Transcripts and Video details. **For a spreadsheet, export the Transcripts
view as Excel (XLSX)**: one row per video, and Excel treats every cell as text. The full record with `segments` does
not fit a spreadsheet (one column per segment), and a CSV opened in Excel may read a caption that starts with `=`,
`+`, `-` or `@` as a formula: open CSV files as text, or prefer XLSX or JSON. Excel also cuts a cell after 32,767
characters (about 35 minutes of speech). Control characters and text-direction marks are removed from captions and
transcripts. The run's summary is the `OUTPUT` record of the default key-value store: per input, videos, transcripts,
missing, errors and duplicates, plus speech-to-text and network statistics.

### Input examples

The latest videos of two accounts and a short link:

```json
{
  "urls": ["@natgeo", "https://www.tiktok.com/@bbcnews", "https://vm.tiktok.com/…"],
  "maxVideosPerAccount": 10,
  "includeSrt": true
}
```

Every video through speech-to-text, in Spanish, text only:

```json
{
  "urls": ["https://www.tiktok.com/@…/video/…"],
  "useTikTokCaptions": false,
  "language": "es",
  "transcriptFormat": "text"
}
```

### Pricing

Pay per result: **$0.001 per transcript**, **plus $0.001 per started minute** only when the video is transcribed by
speech-to-text, plus a **start fee of $0.003 per GB of memory per run** ($0.003 at the default 512 MB). A video with
TikTok captions costs $0.001 whatever its length; a 90-second video without captions costs $0.003. Videos without
speech, incomplete transcripts, photo posts, deleted or private videos, errors and duplicates are free. Platform
usage is included.

| Apify plan | Per transcript | Per started minute of speech-to-text |
|---|---|---|
| Free | $0.001 | $0.001 |
| Bronze | $0.0009 | $0.001 |
| Silver | $0.0008 | $0.001 |
| Gold and above | $0.00075 | $0.001 |

Your maximum charge
per run is never exceeded: the length of each video is read before it is transcribed, and the run stops at the last
transcript it can charge and says so in its status. `maxItems` caps the transcripts of a run: ask for 10, get 10.

Free records are a by-product, not a product: a run stops, and says why, once 100 videos or inputs (an unknown
account counts) plus 2 per transcript have given no transcript, or once 15 minutes of audio plus 2 per billed minute
have been transcribed for nothing (music-only videos); audio we could not send because our capacity was used up does
not count. `maxDurationMinutes` (default 30, at most 60) skips longer
videos before anything is transcribed. A video with TikTok captions reserves only the transcript price, and a single
video that would cost more than what is left of your maximum charge gets a free `over_budget` record while the run goes
on with cheaper ones.

### Use cases

- Trend and content research: what creators in a niche say, searchable and quotable.
- Repurposing: turn your own TikToks into captions, posts, newsletters and subtitle files.
- LLM pipelines: summaries, hooks, scripts and topic analysis across hundreds of videos.
- Brand monitoring: what is said about a product in the videos you collect elsewhere.

### Limits

- **Speech or music.** The speech-to-text service does not return exactly the same text on every pass over the same video;
  on clear speech it does not matter, on music it can. So we keep a margin: a video is charged only when its speech is
  clear (enough words, good confidence, no loops such as "wrestles, wrestles, wrestles"); everything in between comes
  back as a free `no_speech` record. Clearly sung lyrics still count as speech. A song (another account's sound, or a
  track TikTok recognises as a released song even when it is posted as "original sound") is charged only when the
  lyrics are clear and extended: on two Whisper passes that agree, or on the single pass of the ElevenLabs model
  (it returns the same text for the same file, so a second pass would prove nothing).
- **"Original sound" of another video** (duets, stitches, reused sounds): a sound is used as the video's own audio only
  when its author is the video's author, it was created with the video and it has the same length; otherwise we take
  the video's own sound track.
- **Accounts:** about 10 latest videos (what TikTok's embed shows). No hashtags, sounds, search or comments.
- **Private and age-restricted videos** need a signed-in account; we do not use accounts, so they come back as free
  `error` records. Some videos are not shown in the region we read from (`region_blocked`).
- **Challenge pages.** TikTok sometimes answers with a bot check instead of the video (about 2 pages in 100 in our
  tests). We never solve those checks: the page is asked again automatically, and only then the video gets a free
  `blocked` record (`retryable: true`).
- **Speed.** Our speech-to-text capacity is shared fairly between runs and paced: count about 3 seconds per video
  without captions on large runs. If it is temporarily used up, videos that need it get free
  `transcription_capacity` records (`retryable: true`, "temporarily used up, retry later") while videos with
  captions keep coming; the status says when to retry. These records never count against your run's limits and never
  make it fail.
- Some short links (`vm.tiktok.com/ZN9D…`, 13 characters) lead to TikTok's home page even in a browser: they come back
  as `not_found`.

### Memory and speed

512 MB is the default and the best setting: the run waits on speech-to-text, not on memory, and the Actor uses
under 100 MB. More memory does not make it faster (the maximum is 1 GB).

### FAQ

**Is it legal to scrape TikTok transcripts?** This Actor reads only public videos, the way a visitor who is not
signed in sees them, solves no bot check and accepts no TikTok terms on your behalf. Transcripts reproduce what
people say, which may be protected by copyright and may contain personal data: check that your use (research,
analysis, quotation, accessibility) is allowed where you are, and handle personal data under the rules that apply to
you (GDPR in Europe). We keep nothing after the run;
the transcripts ElevenLabs stores are deleted during the run and the deletion is checked (the run log says so
if one could not be confirmed).

**Do I need a TikTok account or any setup?** No. Keep the default connection: its cost is included in the price.
If you set your own connection and it refuses everything, the run stops at once with a free `proxy_failed` record
per input.

**Why "captions" for one video and "speech_to_text" for another?** About 4 TikTok videos in 10 have captions (63 of
150 in our sample); we return those as TikTok made them. The others go through speech-to-text. `billedMinutes` says
what each video cost in minutes.

**What happens when TikTok changes something?** The Actor runs every day on known videos, so we see a change before
you do. A run that fails on our side ends as Failed and charges no transcript.

# Actor input Schema

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

One per line: video URLs (tiktok.com/@name/video/…), short links (vm.tiktok.com/…, vt.tiktok.com/…, tiktok.com/t/…), video IDs, or accounts (@name or profile URL) for their latest videos.

## `maxVideosPerAccount` (type: `integer`):

For accounts: how many of their latest videos to transcribe. Without an account, TikTok shows about 10 of them (pinned ones first).

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

Optional. The language you expect, as a code or a name (en, es, hi, English…). It is a preference, not an instruction: each video comes back in the language actually spoken, never translated (requestedLanguageFound: false when it differs). With Hindi, Urdu or Punjabi, a video heard as English is a free uncertain_language record (the model may have translated it). Empty: detected for each video.

## `useTikTokCaptions` (type: `boolean`):

TikTok has captions (written by the creator or automatic) for about 4 in 10 videos: they are returned as they are, instantly and without speech-to-text minutes. Off: every video goes through speech-to-text.

## `transcriptFormat` (type: `string`):

transcript is the whole text in one string; segments is a list of {start, end, duration, text} in seconds.

## `includeSrt` (type: `boolean`):

Add an srt field: the transcript as a ready-to-save .srt subtitle file.

## `includeVtt` (type: `boolean`):

Add a vtt field: the transcript as a ready-to-save .vtt subtitle file.

## `maxDurationMinutes` (type: `integer`):

Longer videos get a free too_long record instead of a transcript. Their length is read before anything is transcribed.

## `maxItems` (type: `integer`):

Optional cap for the whole run, counted in charged transcripts. Respected exactly. Free records (no speech, deleted, private) do not count; they are bounded separately (100 plus 2 per transcript).

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

Connection. Keep the default (automatic). Costs are included in the price.

## Actor input object example

```json
{
  "urls": [
    "https://www.tiktok.com/@natgeo/video/7694024577701137677"
  ],
  "maxVideosPerAccount": 10,
  "useTikTokCaptions": true,
  "transcriptFormat": "both",
  "includeSrt": false,
  "includeVtt": false,
  "maxDurationMinutes": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Every video: transcript, segments, metadata, and the free missing/error records with their reason.

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

One line per video: status, account, language, words, reason.

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

Per-input counts, videos without speech, errors, speech-to-text and network statistics.

# 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.tiktok.com/@natgeo/video/7694024577701137677"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("quarra/tiktok-transcript-scraper").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.tiktok.com/@natgeo/video/7694024577701137677"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("quarra/tiktok-transcript-scraper").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.tiktok.com/@natgeo/video/7694024577701137677"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call quarra/tiktok-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,quarra/tiktok-transcript-scraper"
        }
    }
}
```

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/C8fDBoVB3M9hrhXAy/builds/WeF7gWLlwrUJwhWrU/openapi.json
