# TikTok Transcript — Video to Text in Bulk, URLs or Profiles (`ntriqpro/tiktok-transcript`) Actor

Turn TikTok videos into text in bulk: paste video URLs, whole @profiles (latest N videos each) or another TikTok scraper's dataset. Timecoded sentences, full transcript, the first-3-seconds hook typed and scored, and a per-profile hook and phrase summary. Captions first, Whisper when none.

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

## Pricing

from $5.20 / 1,000 video transcribeds

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 — Video to Text in Bulk, URLs or Profiles

Turn TikTok videos into text in bulk. Paste **video URLs**, whole **@profiles** (the latest N videos
of each are expanded and transcribed one by one), or the **dataset of another TikTok scraper** — and
get every video back as **the full transcript and timecoded sentences**, plus the video's metadata
(views, likes, comments, shares, publish date, author, hashtags). Optional: the **opening hook typed and
scored** for every video, and a **per-profile summary** of hooks and phrases.

Captions are used when the video has them (fast, exact, no extra fee); otherwise the audio is
transcribed with Whisper and billed **per started audio minute**.

| | Free plan (preview) | Paid Apify plan (Starter and up) |
|---|---|---|
| Videos per run | 1 preview video (the first URL, else the first profile's latest video) | up to 500 (default 200) |
| What the video row holds | the verdict only: speech or not, language, hook types, and the **first words** (at most 8, never more than half the transcript) | full transcript, metadata, every field |
| Full transcript, timecoded sentence rows | locked — counted (words, sentences) | delivered |
| Video metadata (views, likes, hashtags, description, date…) | locked | delivered |
| Hook rows (`hooks` on) | locked — the hook types are shown on the video row | one hook row per video with speech |
| Speech-to-text minute rows | not delivered, not charged | one row per started audio minute (videos without captions) |
| Profile check rows | delivered | delivered |
| Profile summary (+ AI style note) | counted, not made | one row per profile |
| Dataset rows read | the first 1,000 of a run | all (up to 5,000 per dataset), 1,000 more per `rows-read` |
| Closing row | `free-plan-preview`: what this run charged, the real counts of what is locked, and what the same input costs as a new paid run | — |
| Price | the events below at the FREE tier, from your free platform credit | the same events with plan discounts: **BRONZE 20% off, SILVER 35% off, GOLD and up 48% off** |

A free run charges `run-started`, `profile-checked` for each profile TikTok answered for, and
`video-transcribed` for its one preview row — about **$0.02** for a URL and a profile. It ends as
SUCCEEDED with a status message that says what was left out. Run the same input on a paid plan for
every row.

> This Actor is an **independent, unofficial tool**. It is **not affiliated with, endorsed by, or
> sponsored by** TikTok or ByteDance. It reads public videos as a logged-out visitor: no login, no
> cookies. It extracts **text only** — no video or audio file is stored in your dataset or
> redistributed (the audio used for transcription is deleted right after). **You are responsible** for
> having the right to process the videos you submit, for complying with TikTok's terms of service and
> with copyright and privacy law, and for how you use the output. Every result row carries this notice
> in its `responsibility` field.

### Who this is for

- **Researchers and AI builders** — clean, timecoded TikTok transcripts ready for search, RAG or
  summarising, straight from the TikTok scraper you already use. Turn `hooks` off and pay for
  transcripts only.
- **Creators and social media managers** — read the first 3 seconds of a competitor's last 20 videos
  in one run: what was said, which hook device was used, how fast the first word comes.
- **Agencies and brands** — turn a creator's whole feed into searchable scripts before a partnership.

### What one run returns

Each result is its own row (`rowType`), so you can filter, export or join them:

| rowType | One row per | Main fields |
|---|---|---|
| `video` | video | `videoUrl`, `author`, `durationSec`, `viewCount`, `likeCount`, `commentCount`, `shareCount`, `publishedAt`, `hashtags`, `transcript`, `transcriptComplete`, `wordCount`, `sentenceCount`, `speechRateWpm`, `hasSpeech`, `language`, `engine`, `asrMinutes`, `source` (`url` / `profile` / `dataset`) |
| `sentence` | spoken sentence | `timecode`, `startSec`, `endSec`, `text`, `wordCount` |
| `asr-minute` | started audio minute transcribed by Whisper | `minuteIndex`, `timecode`, `startSec`, `endSec`, `text`, `wordCount` |
| `hook` | video with speech (`hooks` on) | `hookText` (first N seconds), `hookTypes` (question, number, contrast, promise), `hookScore` 0-100, `firstWordSec` |
| `profile-checked` | profile TikTok answered for | `outcome` (`listed` / `empty` / `not_found`), `exists`, `videosListed` |
| `profile-summary` | profile (paid plans) | `hookTypeDistribution`, `hookTypeSharePct`, `avgHookScore`, `topHooks`, `commonOpeners`, `commonPhrases`, `topKeywords`, `topHashtags`, `avgSpeechRateWpm`, `styleSummary` (AI), `aiStatus`; after a platform restart that cut a video's sentence rows, `hookStatsUnverifiedVideos` names the videos left out of the hook statistics |
| `dataset-read` | 1,000 dataset rows read past the first 1,000 | `block`, `rowsFrom`, `rowsTo`, `videoUrlsFound` |
| `video-checked` | video TikTok answered is not available | `available: false`, `errorCode`, `message` |
| `notice` | message | `noticeCode`, `message` — never charged |

`engine` tells you where the words came from: `tiktok-captions` or `faster-whisper-base` /
`faster-whisper-tiny`. A video with no speech (music only) still gets its video row with
`hasSpeech: false` — that verdict is part of the result.

**Complete or stopped.** `transcriptComplete: true` means the whole caption track or the whole audio is
in the transcript. When Whisper runs out of `timeoutPerVideo`, or your maximum charge has no room for
the remaining minutes, the video row says `transcriptComplete: false`, `transcriptStoppedBy` (`timeout` /
`maximum-charge`) and `transcribedUntilSec`. Such a row is **not charged as `video-transcribed`** — only
the speech-to-text minutes actually transcribed are charged.

**How the hook is scored.** The hook is every sentence that starts in the first `hookSeconds` (default 3).
It is tagged with the devices it uses — a **question**, a **number**, a **contrast** ("but", "stop",
"most people…") or a **promise** ("here's how", "you'll", "in 5 days") — and scored 0-100: 20 points per
device, 10 if the first word comes within one second, 10 if the hook is 20 words or fewer.

### Input

```json
{
  "videoUrls": ["https://www.tiktok.com/@tiktok/video/7693184538704416031"],
  "profiles": ["@tiktok"],
  "maxVideosPerProfile": 20,
  "datasetIds": [],
  "hooks": true,
  "language": "auto"
}
```

- **`profiles`** — `@name`, `name` or `https://www.tiktok.com/@name`; up to 30 per run. The latest
  `maxVideosPerProfile` public videos of each are transcribed. Each profile TikTok answers for is one
  `profile-checked` row, also when it has no public videos or does not exist.
- **`datasetIds`** — datasets of other TikTok scrapers, read with your token. The video link is found in
  `webVideoUrl` / `submittedVideoUrl` (clockworks TikTok scrapers), `postPage` (apidojo), `videoUrl`,
  `url`, or built from `id` + the author name. The first 1,000 rows of a run are covered by the start
  fee; each further 1,000 is one `rows-read`.
- **`hooks`** — on by default: one hook row per video with speech, **+$0.024 per video on BRONZE**
  ($0.03 FREE tier). Turn it off to pay for transcripts only.
- **`startAt`** — skip the first N videos of this input's list (URLs, then profiles, then datasets). When a
  run stops at `maxVideos` or at your maximum charge, the notice names the exact `startAt` for the next run.
- The same video from several inputs is processed and charged **once**.

### Pricing (pay per event)

| Event | When | FREE tier | BRONZE | SILVER | GOLD+ |
|---|---|---:|---:|---:|---:|
| `run-started` | once per run with a valid input, before the first lookup (covers the first 1,000 dataset rows) | $0.005 | $0.004 | $0.00325 | $0.0026 |
| `profile-checked` | per profile TikTok answered for (listed, empty or does not exist) | $0.005 | $0.004 | $0.00325 | $0.0026 |
| `rows-read` | per started 1,000 dataset rows after the first 1,000 of a run | $0.0025 | $0.002 | $0.001625 | $0.0013 |
| `video-transcribed` | per video row with a complete transcript (metadata + transcript + its sentence rows) — **the main event** | $0.01 | $0.008 | $0.0065 | $0.0052 |
| `asr-minute` | per started audio minute transcribed by Whisper (videos without captions only) | $0.02 | $0.016 | $0.013 | $0.0104 |
| `hook-scored` | per hook row (optional, `hooks` on by default) | $0.03 | $0.024 | $0.0195 | $0.0156 |
| `profile-summary` | per profile-summary row (paid plans; same price with or without the AI note) | $0.05 | $0.04 | $0.0325 | $0.026 |
| `video-checked` | per video TikTok answered is not available | $0.005 | $0.004 | $0.00325 | $0.0026 |

Examples on BRONZE:

- 100 video URLs with captions, `hooks` off = 0.004 + 100 × 0.008 = **$0.804**.
- A profile with 20 talking videos with captions, `hooks` on = 0.004 + 0.004 + 20 × (0.008 + 0.024) + 0.04 = **$0.688**
  (`hooks` off: **$0.208**).
- A 90-second video without captions = 0.008 + 2 × 0.016 = **$0.040** (+ $0.024 with its hook row).

Notice rows are free. Your **maximum charge per run** is checked **before** work starts: before each
profile lookup, before each further 1,000 dataset rows, and before Whisper starts on a video (room for
its row and its minutes). A hook row is bought only after the row and minutes of the videos being
transcribed at that moment are covered, so a hook never takes a transcript already under way. It does
spend budget that a later video could have used: with `hooks` on, the same maximum charge may cover
fewer videos than with `hooks` off. To spend the whole maximum charge on transcripts, turn `hooks` off.
When hook rows or summaries no longer fit, that layer stops
and video rows go on; when video rows no longer fit, the run ends as SUCCEEDED with a notice of what was left and the
`startAt` to continue from.

### AI style note (paid plans)

When `aiStyleSummary` is on (default), each profile-summary row gets a three-sentence note written by
Anthropic's Claude from that profile's hooks and common phrases. **The hooks and phrases of that profile
are sent to Anthropic's API (United States).** A note that cites a number or a quote not found in the
videos is dropped (`aiStatus: discarded-ungrounded`); the rest of the summary row is still delivered and
charged as `profile-summary` — the note is part of that row, not a separate fee, so turning it off with
`"aiStyleSummary": false` does not lower the summary price.

### Limits

- Public videos only; private or removed videos are reported, not transcribed.
- TikTok's public profile page lists about 10 videos; more are read through the profile's video list
  when TikTok serves it. When it does not, a `profile-partial` notice says how many were found.
- Profiles are read one request at a time with pauses, to keep the load on TikTok low.
- `startAt` follows the order of this input's list; a profile that posted new videos since the last run
  shifts its list.

# Actor input Schema

## `videoUrls` (type: `array`):

One per line: public TikTok video pages (https://www.tiktok.com/@name/video/123...) or vm./vt.tiktok.com short links. The same video from several inputs is processed and charged once. Only submit videos you have the right to process; you are responsible for how you use the output.

## `profiles` (type: `array`):

Each profile is expanded into its latest public videos (up to 'Videos per profile') and every video is transcribed. Each profile TikTok answers for (with videos, empty, or 'does not exist') is one profile-checked row ($0.005, BRONZE $0.004); a lookup refused or timed out on our side is free. Paid plans also get one profile-summary row per profile. Up to 30 profiles per run.

## `datasetIds` (type: `array`):

Transcribe the videos another Actor already found. Fields read automatically: webVideoUrl, submittedVideoUrl (clockworks TikTok scrapers), postPage (apidojo), videoUrl, url, or id + author name. Read with your own Apify token. The first 1,000 rows of a run are covered by the start fee; paid plans read on at $0.0025 (BRONZE $0.002) per further 1,000 rows (rows-read), up to 5,000 rows per dataset. The free plan reads the first 1,000.

## `maxVideosPerProfile` (type: `integer`):

Latest videos taken from each profile (1-100).

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

Cap on videos transcribed in one run, across all inputs (paid plans, up to 500). The free plan gives 1 preview video (verdict and first words) and counts the rest; its closing row prices this cap as a paid run.

## `hooks` (type: `boolean`):

On: one hook row per video with speech — the words of the first N seconds, hook types and a 0-100 score — charged as hook-scored ($0.03 FREE tier, $0.024 BRONZE, $0.0156 GOLD). Off: transcripts only, no hook charge. Free plans see only the hook types.

## `startAt` (type: `integer`):

Skip the first N videos of this input's list (URLs, then profiles, then datasets; 0 = from the start). When a run stops at Max videos or at your maximum charge, its notice names the exact value for the next run.

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

auto uses the video's own caption track, or detects the spoken language. Or give a two-letter code (en, es, ko, ...) to pick that caption track or force Whisper to that language.

## `hookSeconds` (type: `integer`):

Sentences that start inside the first N seconds form the hook (1-15).

## `aiStyleSummary` (type: `boolean`):

Adds a three-sentence note on how the creator opens and phrases videos to each profile-summary row. When on, the hooks and common phrases of that profile are sent to Anthropic's API (United States). Notes that cite numbers or quotes not found in the videos are dropped. The note is part of the profile-summary row: turning it off does not change the summary price.

## `whisperModel` (type: `string`):

Used only for videos without captions, billed per started audio minute (asr-minute, $0.02, BRONZE $0.016). base is more accurate, tiny is faster. Both are built into the image (no download wait).

## `timeoutPerVideo` (type: `integer`):

Longest time spent transcribing one video without captions (60-1800). When it runs out, the video row says transcriptComplete false and is not charged as video-transcribed; only the audio minutes transcribed are charged.

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

Each video is first fetched directly. The proxy is used only when TikTok refuses the direct request.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.tiktok.com/@tiktok/video/7693184538704416031"
  ],
  "profiles": [],
  "datasetIds": [],
  "maxVideosPerProfile": 20,
  "maxVideos": 200,
  "hooks": true,
  "startAt": 0,
  "language": "auto",
  "hookSeconds": 3,
  "aiStyleSummary": true,
  "whisperModel": "base",
  "timeoutPerVideo": 240,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "videoUrls": [
        "https://www.tiktok.com/@tiktok/video/7693184538704416031"
    ],
    "profiles": [],
    "datasetIds": [],
    "maxVideosPerProfile": 20,
    "maxVideos": 200,
    "hooks": true,
    "language": "auto",
    "hookSeconds": 3,
    "aiStyleSummary": true,
    "whisperModel": "base",
    "timeoutPerVideo": 240,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ntriqpro/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 = {
    "videoUrls": ["https://www.tiktok.com/@tiktok/video/7693184538704416031"],
    "profiles": [],
    "datasetIds": [],
    "maxVideosPerProfile": 20,
    "maxVideos": 200,
    "hooks": True,
    "language": "auto",
    "hookSeconds": 3,
    "aiStyleSummary": True,
    "whisperModel": "base",
    "timeoutPerVideo": 240,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ntriqpro/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 '{
  "videoUrls": [
    "https://www.tiktok.com/@tiktok/video/7693184538704416031"
  ],
  "profiles": [],
  "datasetIds": [],
  "maxVideosPerProfile": 20,
  "maxVideos": 200,
  "hooks": true,
  "language": "auto",
  "hookSeconds": 3,
  "aiStyleSummary": true,
  "whisperModel": "base",
  "timeoutPerVideo": 240,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call ntriqpro/tiktok-transcript --silent --output-dataset

```

## MCP server setup

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