# TikTok Transcript Scraper: Subtitles, SRT and Metadata (`fetchrail/tiktok-transcript`) Actor

Scrape TikTok video transcripts and subtitles with timestamps as JSON, text, SRT or WebVTT, in the spoken language or a translation TikTok offers. Video metadata free on request. Pay only for videos that return a transcript.

- **URL**: https://apify.com/fetchrail/tiktok-transcript.md
- **Developed by:** [Fetchrail](https://apify.com/fetchrail) (community)
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 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: Subtitles, SRT and Metadata

Turn TikTok videos into clean, timestamped text. Paste a list of links, choose a
language and a format, and get one result per video, ready for an LLM, a
spreadsheet or a subtitle editor.

| At a glance | |
| --- | --- |
| **Price** | $1.00 per 1,000 transcripts, platform usage included |
| **Charged when** | A video returns a transcript. Everything else is free. |
| **Formats** | Timestamped segments (JSON), plain text, SRT, WebVTT |
| **Language** | The spoken language by default, or a translation TikTok offers |
| **Links** | Full video links and `vm.tiktok.com` / `vt.tiktok.com` short links |
| **Metadata** | Caption text, account, duration, views, likes, comments, sound: free on request |
| **AI agents** | Callable as a tool from Claude, Cursor and VS Code over MCP |

### 🧾 What's in each result

- **The transcript** in the format you chose. As segments, each line carries
  `text`, `start` and `duration` in seconds.
- **Which track you got**, in `language`.
- **What you asked for**, in `preferred`, so a fallback is easy to spot.
- **Every caption track** the video has, in `captionTracks`, including the
  translations TikTok offers for it.
- **The video's metadata**, when you turn it on: caption text and hashtags,
  account, duration, upload date, view, like and comment counts, the sound used
  and cover images.
- **A reason code** for every video that has no transcript, instead of a crashed
  run.

### 📌 Where TikTok transcripts come from

TikTok generates captions from a video's speech and shows them in its player.
This Actor returns those captions; it does not run speech-to-text of its own. Two
things follow from that:

- **Not every video has a transcript.** TikTok makes captions for many videos
  with speech, but not all, and none for videos without speech. Those return
  `NO_CAPTIONS`, free of charge.
- **Every track is automatic.** Creators can edit TikTok's captions but cannot
  upload their own, so `isAsr` is always true, as TikTok's own player labels them.

For some videos TikTok also offers a translation, often English. It is listed in
`captionTracks` and returned when you ask for that language. Without a preferred
language you get the language spoken in the video, even when a translation is
listed first.

### 🧠 Who it's for

| You are building | Set this |
| --- | --- |
| Trend or content research across creators | `includeMetadata: true`, then export to CSV or Excel |
| A summarizer, classifier or RAG index | `format: json`, so every chunk keeps its timestamp |
| A prompt from a batch of videos | `format: text`, one string per video |
| Subtitles for re-editing a clip | `format: srt` or `vtt` |
| An English feed from videos in other languages | `language: en`, which returns TikTok's English translation where one exists |
| An agent that answers questions about videos | The MCP setup below |

### ▶️ Run it in three steps

1. Paste TikTok video links into **TikTok video URLs**.
2. Optionally set a **Preferred language**, such as `en`, `de` or `pt-BR`.
3. Press **Start**, then download the results as JSON, CSV or Excel.

### ⚙️ Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `urls` | list of strings, required | | TikTok video links. A link repeated in the list is dropped, so it is never charged twice in one run. |
| `language` | string | the spoken language | Preferred language code. A preference, not a filter: see below. |
| `format` | `json`, `text`, `srt`, `vtt` | `json` | Segments with timestamps, or the transcript as one string. |
| `includeMetadata` | boolean | `false` | Add the video's metadata to each result, at no extra charge. |

#### How the language is chosen

A video always returns a transcript when it has any caption track at all. Tracks
are tried in this order:

1. Your preferred language
2. The language spoken in the video
3. English
4. The first track available

`preferred` echoes what you asked for and `language` says what you got, so
filtering out an unwanted fallback is one comparison.

#### Recipes

**A batch with short links**

```json
{
  "urls": [
    "https://www.tiktok.com/@nasa/video/7697300954667994381",
    "https://vm.tiktok.com/YOUR_SHORT_LINK/"
  ]
}
```

**The English translation, as SRT**

```json
{
  "urls": ["https://www.tiktok.com/@ibaillanos/video/7697233515926129953"],
  "language": "en",
  "format": "srt"
}
```

Ibai Llanos speaks Spanish here, and TikTok offers an English translation, so
`transcript` holds an English SRT file. A video without an English track returns
its spoken language instead.

**Plain text plus video details, for a research sheet**

```json
{
  "urls": ["https://www.tiktok.com/@nasa/video/7697300954667994381"],
  "format": "text",
  "includeMetadata": true
}
```

### 🗂️ Output

One item per URL. Here is a transcript with metadata on, shortened. With metadata
off, the fields from `description` to `extra` are left out:

```json
{
  "inputUrl": "https://www.tiktok.com/@nasa/video/7697300954667994381",
  "site": "tiktok",
  "id": "7697300954667994381",
  "url": "https://www.tiktok.com/@nasa/video/7697300954667994381",
  "title": "There was plenty to see across NASA this week 🚀   🔴 Perseverance brea...",
  "preferred": { "language": null, "asr": false },
  "language": "en-US",
  "isAsr": true,
  "captionTracks": [
    { "language": "en-US", "asr": true, "name": null }
  ],
  "format": "json",
  "segmentCount": 43,
  "transcript": [
    { "text": "From breaking records on Mars to lighting up the skies over Earth,", "start": 0.34, "duration": 3.08 },
    { "text": "NASA put on quite a show this week.", "start": 3.421, "duration": 1.96 }
  ],
  "description": "There was plenty to see across NASA this week 🚀   🔴 Perseverance breaks a record on Mars 🛰️ Fresh cargo arrives at the ISS ...",
  "uploader": {
    "id": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
    "name": "NASA",
    "url": "https://www.tiktok.com/@MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6"
  },
  "durationSeconds": 121,
  "uploadDate": "2026-10-16",
  "viewCount": 97200,
  "isLive": false,
  "thumbnails": [
    { "url": "https://p19-common-sign.tiktokcdn.com/..." }
  ],
  "chapters": [],
  "tags": [],
  "extra": {
    "like_count": 9569,
    "comment_count": 252,
    "language": "en",
    "track": "original sound - NASA",
    "artist": "NASA"
  },
  "error": null
}
```

A video with a translation lists both tracks. Ibai's video above has Spanish audio
and an English translation:

```json
"captionTracks": [
  { "language": "en-US", "asr": true, "name": null },
  { "language": "es-ES", "asr": true, "name": null }
]
```

Without a preferred language it returns `es-ES`, starting *"es oficial los coches
vuelan"*. With `"language": "en"` it returns `en-US`, starting *"it's official
cars fly"*.

TikTok signs its cover image links and they expire, so download any thumbnails
you need soon after the run.

#### When a video has no transcript

You still get an item, and it costs nothing:

```json
{
  "inputUrl": "https://www.tiktok.com/@tiktok/video/6584647400055377158",
  "error": {
    "code": "NO_CAPTIONS",
    "message": "This media has no caption tracks.",
    "requestId": "..."
  }
}
```

| Code | Meaning |
| --- | --- |
| `NO_CAPTIONS` | TikTok has no captions for this video. |
| `UNAVAILABLE` | Removed, private or region locked. |
| `BLOCKED` | TikTok refused the request. Some videos are restricted by region. |
| `UPSTREAM_RATE_LIMITED` | TikTok is throttling requests. Try again later. |
| `EXTRACTION_FAILED` | A temporary failure. Try again later. |
| `UNSUPPORTED_SITE` | Not a TikTok video URL. |
| `INVALID_URL` | Not a valid URL. |
| `FREE_PLAN_LIMIT` | Skipped: the Apify free plan's daily transcripts are used up. |
| `CHARGE_LIMIT_REACHED` | Skipped: the run reached the maximum charge you set. |

None of these is charged.

### 🏷️ Pricing

**One price, one event: $1.00 per 1,000 transcripts.** You are charged once per
video that returns a transcript, with or without metadata. Videos without
captions cost nothing, and platform usage is included.

| Videos with a transcript | Cost |
| --- | --- |
| 100 | $0.10 |
| 1,000 | $1.00 |
| 10,000 | $10.00 |
| 100,000 | $100.00 |

Set a maximum charge for a run and it stops before going over, listing the URLs
it did not process.

**On the Apify free plan, you get 30 transcripts per day**, across all your runs.
The count resets at 00:00 UTC. Once it is reached, the run stops and returns the
remaining URLs with the `FREE_PLAN_LIMIT` code, free of charge, and the run's
status message says so. Videos without a transcript do not count. Paid Apify
plans have no limit.

### 🔌 Plug it into your AI agent

Through [Apify's MCP server](https://docs.apify.com/platform/integrations/mcp),
this Actor becomes a tool your agent can call. Use this server URL, with your
[Apify API token](https://console.apify.com/settings/integrations):

```
https://mcp.apify.com?tools=fetchrail/tiktok-transcript
```

| Client | How to add it |
| --- | --- |
| Claude Code | `claude mcp add --transport http apify "https://mcp.apify.com?tools=fetchrail/tiktok-transcript" --header "Authorization: Bearer YOUR_TOKEN"` |
| Claude Desktop, claude.ai | Add a custom connector with the URL above and sign in to Apify |
| Cursor | In `.cursor/mcp.json`, under `mcpServers`: the URL, plus an `Authorization: Bearer YOUR_TOKEN` header |
| VS Code | In `.vscode/mcp.json`, under `servers`: `"type": "http"`, the URL and the same header |

Then just ask, for example: *"What does this TikTok say? Translate it to English
and summarize it in two lines."*

### 🧑‍💻 From code and automations

**One HTTP request.** Runs the Actor and returns the results. Replace `YOUR_TOKEN`
with your Apify API token:

```bash
curl -X POST "https://api.apify.com/v2/acts/fetchrail~tiktok-transcript/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.tiktok.com/@nasa/video/7697300954667994381"], "format": "text"}'
```

**Python**, with `apify-client` version 3 or later:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("fetchrail/tiktok-transcript").call(
    run_input={"urls": ["https://www.tiktok.com/@nasa/video/7697300954667994381"], "format": "text"}
)
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item.get("title"), item.get("transcript") or item["error"])
```

**No code.** Apify's Make, n8n and Zapier integrations run the Actor and pass each
result to your next step, such as a spreadsheet row or a Slack message. Schedules
and webhooks are set up from Apify Console.

### 🙋 Questions people ask

**Why did a video return `NO_CAPTIONS` when it has speech?** TikTok has not made
captions for it. This Actor returns the captions TikTok already has and does not
transcribe audio itself. You are not charged.

**Why did I get a different language than I asked for?** TikTok offers no track
or translation in that language for this video, so the fallback order chose
another. Compare `language` with `preferred` in the result.

**Can I pass a profile or a hashtag page?** Not yet. Pass individual video links.
TikTok's short drama series episodes are not supported.

**Do short links work?** Yes. `vm.tiktok.com`, `vt.tiktok.com` and
`tiktok.com/t/` links resolve to the video they point at, and `url` in the result
is the full link.

**Is this affiliated with TikTok?** No. It is an independent tool that reads
captions TikTok shows publicly to any viewer.

**Is it legal?** How you use transcripts is your responsibility: they can be
protected by copyright, and spoken content can contain personal data. If in
doubt, consult a lawyer.

### 🛟 Support

Open an issue on this Actor's **Issues** tab. If a video failed, include the
`requestId` from its result, which lets us trace exactly what happened.

# Actor input Schema

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

TikTok video links, one per entry. Full links and short links (vm.tiktok.com, vt.tiktok.com) both work. On the Apify free plan, you get 30 transcripts per day across all runs, reset at 00:00 UTC.

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

A language code such as en, de or pt-BR. A preference, not a filter: a video without a track in it returns the language spoken in the video instead, then English, then the first track available. Each result says which language it returned. Leave empty for the spoken language.

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

Segments with start times and durations, plain text, or a subtitle file (SRT or WebVTT) as text.

## `includeMetadata` (type: `boolean`):

Add the caption text, account, duration, upload date, view, like and comment counts, sound and thumbnails to each result, at no extra charge. Off by default, which keeps results small and runs faster.

## Actor input object example

```json
{
  "urls": [
    "https://www.tiktok.com/@nasa/video/7697300954667994381"
  ],
  "format": "json",
  "includeMetadata": false
}
```

# Actor output Schema

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

One row per URL: title, language, segment count and any error.

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

One row per URL: title, language and the transcript itself, or the error for a video without one.

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

One item per URL with the transcript, the caption tracks the video has and, when asked for, its metadata, or an error code for a video without a transcript.

# 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/@nasa/video/7697300954667994381"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchrail/tiktok-transcript").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/@nasa/video/7697300954667994381"] }

# Run the Actor and wait for it to finish
run = client.actor("fetchrail/tiktok-transcript").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/@nasa/video/7697300954667994381"
  ]
}' |
apify call fetchrail/tiktok-transcript --silent --output-dataset

```

## MCP server setup

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

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/5GkuYQHGuHdYmhiXy/builds/CaHo5YTADKqldpajo/openapi.json
