# TikTok Transcript Scraper - Captions & Subtitles, Paid If Found (`neverempty/tiktok-transcript-scraper`) Actor

Get the transcript of TikTok videos from their URLs: every caption language TikTok publishes (auto captions and machine translations) as plain text, WebVTT, SRT and timed segments, with views and likes. Charged only for videos that return a transcript.

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

## Pricing

from $2.92 / 1,000 transcript returneds

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 - Captions & Subtitles, Paid If Found

Get the transcript of public TikTok videos from their URLs. For each video you get the spoken words as **plain text, WebVTT, SRT and timed segments**, in **every caption language TikTok publishes** for it (the original-language auto captions and TikTok's machine translations), together with the author, description, duration and view, like, comment, share and save counts.

**You pay only for videos that actually return a transcript.** A video without captions, a deleted video, a bot check or a mistyped URL comes back as a free row that says why - never as a blank, charged row.

### What you can use it for

- **Content research and repurposing**: turn TikTok videos into blog posts, newsletters, scripts or summaries without watching them.
- **AI pipelines**: feed `transcriptText` into an LLM for summaries, topic tagging, sentiment or Q\&A. The segments keep timestamps if you need to quote a moment.
- **Subtitles**: download ready-made `.vtt` or `.srt` bodies for re-editing or translation.
- **Multilingual work**: a Japanese video can come back with its Japanese captions and TikTok's English translation in the same row.
- **Performance analysis**: spoken hooks next to views and likes.

### How it works

1. Paste video URLs (`https://www.tiktok.com/@name/video/1234567890123456789`), bare numeric video ids, or `vm.tiktok.com` short links.
2. For each video the Actor opens the public video page, checks that TikTok answered with **the same video id you asked for**, and reads the list of captions TikTok publishes for it.
3. It downloads each caption file, checks that it is a WebVTT file with text whose last caption ends within the video's length (plus 3 seconds), and returns it in four formats.
4. The row is written and charged. Videos without a transcript get a free row with the reason.

This Actor does **not** run speech-to-text. It returns the captions TikTok itself publishes. When TikTok lists no captions for a video, you get a free `no-captions` row.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `videos` | example videos | Video URLs, numeric video ids or short links. A repeated video is read and charged once; the repeat gets a free `duplicate` row. |
| `languages` | all | Caption languages to return: `en`, `en-US`, `eng` or `eng-US` style codes. Matching is by language, not region. |
| `includeMachineTranslations` | `true` | Return captions TikTok marks as machine translation (`MT`). |
| `includeVtt` | `true` | Add a WebVTT body per caption track. |
| `includeSrt` | `true` | Add an SRT body per caption track. |
| `includeSegments` | `true` | Add timed segments (`start`, `end` in seconds, `text`). |
| `maxVideos` | `100` | Stop after this many transcript rows. Up to 1,000 entries per run. |
| `useProxy` | `true` | Retry through a proxy only if TikTok answers with a bot check. |

```json
{
    "videos": [
        "https://www.tiktok.com/@mrbeast/video/7654638524729216287",
        "https://www.tiktok.com/@user6222822550964/video/7371575672298425621"
    ],
    "languages": [],
    "includeMachineTranslations": true
}
```

If you leave `videos` out of the input, the two example videos above are read (their URLs appear in `inputUrl` and the run log says so). An empty list is rejected with a free `invalid-input` row instead of reading the examples.

### Output

One row per video with a transcript (shortened):

```json
{
    "status": "ok",
    "videoId": "7654638524729216287",
    "videoUrl": "https://www.tiktok.com/@mrbeast/video/7654638524729216287",
    "authorUsername": "mrbeast",
    "authorMatchesInput": true,
    "durationSeconds": 24,
    "transcriptLanguage": "eng-US",
    "transcriptSource": "ASR",
    "transcriptText": "Who's faster, me or the fastest man to ever live? I hope this slows him down. ...",
    "transcriptCount": 1,
    "transcripts": [
        {
            "languageCode": "eng-US",
            "source": "ASR",
            "sourceName": "automatic speech recognition",
            "isAutoGenerated": true,
            "isOriginalLanguage": true,
            "text": "Who's faster, me or the fastest man to ever live? ...",
            "segmentCount": 13,
            "lastCueEndSeconds": 24.041,
            "vtt": "WEBVTT\n\n00:00:00.000 --> 00:00:03.180\nWho's faster, me or the fastest man to ever live?\n...",
            "srt": "1\n00:00:00,000 --> 00:00:03,180\nWho's faster, me or the fastest man to ever live?\n...",
            "segments": [{ "start": 0, "end": 3.18, "text": "Who's faster, me or the fastest man to ever live?" }]
        }
    ],
    "availableLanguages": ["eng-US (ASR)"],
    "languagesNotReturned": [],
    "playCount": 32300000,
    "likeCount": 2900000,
    "commentCount": 48100,
    "shareCount": 35500,
    "saveCount": 118851,
    "countsMayBeRounded": true
}
```

| Field | Meaning |
| --- | --- |
| `transcriptText` | Plain text of the caption track in the video's original language (or the first returned track if TikTok does not mark one as original). Japanese and Chinese captions are joined without inserted spaces; other languages, Korean included, are joined with a space. |
| `transcripts[]` | Every returned caption track: `languageCode` as TikTok writes it (`eng-US`, `jpn-JP`, `spa-ES`), `source` as TikTok writes it (`ASR` or `MT`), `isAutoGenerated` and `isOriginalLanguage` as TikTok marks them, and the text in the formats you asked for. |
| `availableLanguages` | Every caption track TikTok lists for the video, as `language (source)`. |
| `languagesNotReturned` | Tracks that were listed but not returned, with the reason (`language-not-requested`, `machine-translation-not-requested`, `longer-than-video`, `no-text`, `not-webvtt`, `caption-file-blocked`, ...). |
| `authorMatchesInput` | `false` when the URL you gave has a different username than the video's author. TikTok serves the video by id whatever name is in the URL, so the id decides which video you get and this field tells you the name did not match. |
| `resolvedFromShortLink` | The short link you gave, when the video was reached through one (also on free rows for such videos). |
| `playCount`, `likeCount`, `commentCount`, `shareCount`, `saveCount` | The counts TikTok puts on the video page. |
| `countsMayBeRounded` | `true` when a view, like, comment or share count is 10,000 or more. |

#### About the counts

TikTok's video page carries the counts the way the app displays them. On the videos measured on 2026-09-14, every view, like, comment and share count of 10,000 or more was rounded to the displayed figure (32.3M came back as `32300000`, 981.4K as `981400`), counts below 10,000 were not rounded (`7955`), and the save count was not rounded even above 10,000 (`118851`). Treat large counts as approximate.

#### Rows that are not charged

| `status` | When |
| --- | --- |
| `no-captions` | TikTok lists no captions for the video. TikTok's own `noCaptionReasonCode` (for example `3`) is passed through untranslated, with `hasOriginalAudio` as TikTok marks it. |
| `no-such-video` | TikTok says the video does not exist (code `10204`, "item doesn't exist"). |
| `different-video-returned` | TikTok answered with a different video id than the one asked for; its captions are not returned. |
| `no-matching-language` | The video has captions, but not in the languages or caption types you asked for. The row lists what is available. |
| `captions-unreadable` | Captions are listed but none could be read as this video's transcript (the row lists the reason per language, such as a caption address outside TikTok's own domains). |
| `short-link-not-a-video` | The short link does not redirect to a video page. |
| `blocked` | TikTok answered with a bot check or an empty page on every try. |
| `unreadable` | The page could not be read, or TikTok answered with a code whose meaning has not been measured (passed through as `tiktokStatusCode`). |
| `invalid-input` | The entry is not a TikTok video URL, id or short link, or `videos` is an empty list. |
| `duplicate` | The same video was already handled by an earlier entry in this run. |
| `not-checked` | `maxVideos` transcripts were already returned, or the list is longer than 1,000 entries. The row says how many entries were not read. |
| `budget-reached` | The run's maximum total charge has no room for another transcript row. The row says how many entries were not read. |

### Pricing

**$4.00 per 1,000 transcript rows**, charged only for rows with `status: "ok"`. One row per video, whatever the number of caption languages in it. Free rows are never charged. If you set a maximum total charge for the run, the Actor stops reading videos when no further transcript row fits, and says so in a `budget-reached` row.

### Limits and notes

- Only captions TikTok publishes are returned. There is no speech-to-text, so videos with music only, no speech, or captions TikTok has not generated come back as `no-captions`.
- Private videos and videos TikTok restricts have not been measured; they come back as a free row with TikTok's own code rather than a guessed reason.
- Photo posts (`/photo/` URLs) are accepted and read by id; whether TikTok lists captions for them has not been measured.
- Short links are followed one redirect. The Actor only uses the target when it is a TikTok video address it accepts as input (`/@name/video/<id>`, `/@name/photo/<id>` or `m.tiktok.com/v/<id>.html`).
- Requests go out one after another with a pause between video pages. In a run on 2026-09-14, 16 entries took 20 seconds.

### FAQ

**Do I need a TikTok account or API key?** No. The Actor reads public video pages.

**Which languages are supported?** Whatever TikTok publishes for the video. Codes are passed through as TikTok writes them.

**Can I get the transcript of a video without captions?** Not with this Actor; it does not transcribe audio.

**Is it legal?** The Actor reads publicly available pages. Transcripts may contain personal data; use them for legitimate purposes and in line with the laws that apply to you.

# Actor input Schema

## `videos` (type: `array`):

Videos to get transcripts for: https://www.tiktok.com/@name/video/1234567890123456789 (with or without the scheme), the numeric video id, or a vm.tiktok.com / vt.tiktok.com / tiktok.com/t/ short link. A repeated video is read and charged once and the repeat gets a free 'duplicate' row. If you leave this field out, two example videos are read; an empty list is rejected.

## `languages` (type: `array`):

Leave empty to return every caption language TikTok publishes for the video. Otherwise list language codes: a two-letter code (en, ja, es), a code with a region (en-US, pt-BR; matched by language, not region), TikTok's three-letter code (eng, jpn, spa) or the full code (eng-US, jpn-JP). A video that has captions, but none in these languages, comes back as a free row listing the languages it has.

## `includeMachineTranslations` (type: `boolean`):

On = captions TikTok marks as machine translation (source MT) are returned alongside the original-language captions. Off = only the other captions (in every video measured on 2026-09-14 that was automatic speech recognition, source ASR).

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

Add each caption track as a WebVTT file body (vtt) to the row.

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

Add each caption track as an SRT file body (srt) to the row.

## `includeSegments` (type: `boolean`):

Add each caption track as a list of segments with start and end in seconds and the text (segments). The plain text is always included.

## `maxVideos` (type: `integer`):

Reading stops once this many charged transcript rows have been returned, and a free row says how many entries at the end of the list were not read. Videos without a transcript do not count. Up to 1,000 entries are read per run.

## `useProxy` (type: `boolean`):

Every request goes out directly first, which worked from Apify on every request measured on 2026-09-14. If TikTok answers with a bot check or an empty response, this switches to a proxy session and tries again. With it off, that answer is returned as a free 'blocked' row and no proxy is paid for.

## Actor input object example

```json
{
  "videos": [
    "https://www.tiktok.com/@mrbeast/video/7654638524729216287",
    "https://www.tiktok.com/@user6222822550964/video/7371575672298425621"
  ],
  "includeMachineTranslations": true,
  "includeVtt": true,
  "includeSrt": true,
  "includeSegments": true,
  "maxVideos": 100,
  "useProxy": true
}
```

# Actor output Schema

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

One row per TikTok video that returned a transcript: the video id and URL, the author, the description, creation time and duration, the transcript text in the video's original caption language, every returned caption language with its source (automatic speech recognition or machine translation) as plain text, WebVTT, SRT and timed segments, the caption languages that were not returned and why, and the view, like, comment, share and save counts as TikTok shows them (counts of 10,000 and above may be rounded). Videos without captions, videos that do not exist, bot checks and invalid input come back as free rows that say why.

# 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 = {
    "videos": [
        "https://www.tiktok.com/@mrbeast/video/7654638524729216287",
        "https://www.tiktok.com/@user6222822550964/video/7371575672298425621"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/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 = { "videos": [
        "https://www.tiktok.com/@mrbeast/video/7654638524729216287",
        "https://www.tiktok.com/@user6222822550964/video/7371575672298425621",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/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 '{
  "videos": [
    "https://www.tiktok.com/@mrbeast/video/7654638524729216287",
    "https://www.tiktok.com/@user6222822550964/video/7371575672298425621"
  ]
}' |
apify call neverempty/tiktok-transcript-scraper --silent --output-dataset

```

## MCP server setup

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