# TikTok Transcripts - Captions, SRT, Speech-to-Text (`kaz_kakyo/tiktok-transcripts`) Actor

Extract TikTok video transcripts via API, MCP, or schedule — captions with timestamps, SRT subtitles, multi-language, optional Deepgram speech-to-text for videos without captions. $0.005 per captioned video. One JSON row per URL. No browser, no login.

- **URL**: https://apify.com/kaz\_kakyo/tiktok-transcripts.md
- **Developed by:** [Heim AI](https://apify.com/kaz_kakyo) (community)
- **Categories:** Videos, Social media, Agents
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 captioned video transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## TikTok Transcripts — Captions & Speech-to-Text from Video URLs

**URL in → transcript out.** Pass TikTok video links; get one JSON dataset row per video with the caption track (ASR / creator / MT) or optional Deepgram speech-to-text when captions are absent. No browser, no login. Built for **MCP agents, API clients, and scheduled pipelines**.

| | |
|---|---|
| **Actor id** | `kaz_kakyo/tiktok-transcripts` |
| **Minimal input** | `{ "tiktokUrls": ["https://www.tiktok.com/@user/video/…"] }` |
| **Cost** | **$0.005 per captioned video** · $0.012/audio-minute STT fallback · $0.005/run start |
| **Output** | Dataset rows with `type: "transcript"` or `type: "error"` |

### Call it (MCP / API / schedule)

#### MCP (agents)

```json
{
  "actor": "kaz_kakyo/tiktok-transcripts",
  "input": {
    "tiktokUrls": ["https://www.tiktok.com/@aespa_official/video/7401509513653734664"]
  }
}
```

Optional extras agents usually want:

```json
{
  "tiktokUrls": ["https://www.tiktok.com/@aespa_official/video/7401509513653734664"],
  "languages": ["en"],
  "includeTimestamps": true,
  "includeSrt": true,
  "transcribeMissingCaptions": true
}
```

After the run, read the default dataset. Every row has a `type` discriminator — filter on `"transcript"`; treat `"error"` as per-video failure. Missing captions (when STT is off/unavailable), photo posts, private/region-blocked videos, bad URLs, duplicate aliases of the same video, and misconfigured PPE pricing become error rows and the run still **SUCCEEDS** (including all-failed batches) so agent mistakes do not look like platform outages. The run fails only on a post-charge delivery failure (charged but could not write the dataset).

#### API / `apify-client`

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('kaz_kakyo/tiktok-transcripts').call(
  {
    tiktokUrls: ['https://www.tiktok.com/@aespa_official/video/7401509513653734664'],
    includeTimestamps: true,
  },
  { maxTotalChargeUsd: 1.0 }, // hard budget for this run
);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const transcripts = items.filter((i) => i.type === 'transcript');
```

Same shape via REST: `POST /v2/acts/kaz_kakyo~tiktok-transcripts/runs` with your token, then poll or attach a webhook.

#### Make it recurring (what sticky callers do)

1. **Save a Task** in Console with your fixed options (`languages`, `includeTimestamps`, `includeSrt`, `transcribeMissingCaptions`). Agents and cron jobs call the **task id**, not ad-hoc input.
2. **Schedule the Task** (hourly/daily) when the URL list is stable — e.g. a creator watchlist you refresh elsewhere.
3. **Webhook on `SUCCEEDED`** to your endpoint / Zapier / Make — pull `defaultDatasetId` and process only `type === "transcript"` rows.
4. **Cap spend** on every automated run with `maxTotalChargeUsd`. When the cap hits, remaining videos become `type: "error"` skipped rows with `errorCode: "charge_limit_reached"` — no surprise bill, no silent free transcripts.
5. **Chain**: any TikTok search/profile scraper that outputs video URLs → this actor. For YouTube captions use [`kaz_kakyo/youtube-transcripts`](https://apify.com/kaz_kakyo/youtube-transcripts). For direct media file URLs use [`kaz_kakyo/audio-transcriber`](https://apify.com/kaz_kakyo/audio-transcriber).

Long runs checkpoint a per-video billing ledger (`RESERVED` → `CHARGED` → `DELIVERED`) — a platform migration resumes without re-billing. Incomplete ledger entries surface as `resumed_incomplete` error rows (re-run those videos). Same video via two input URLs (canonical + bare id) yields one transcript and one `duplicate_video` error.

### Output contract

One dataset item per input URL (plus skipped/invalid rows). Success shape:

```json
{
  "type": "transcript",
  "url": "https://www.tiktok.com/@aespa_official/video/7401509513653734664",
  "videoId": "7401509513653734664",
  "author": "aespa_official",
  "title": "…",
  "durationSeconds": 32,
  "language": "eng-US",
  "source": "captions",
  "subtitleSource": "ASR",
  "machineTranslated": false,
  "languageFallback": false,
  "transcript": "Full caption text…",
  "availableLanguages": [{ "language": "eng-US", "source": "ASR" }],
  "segments": [{ "start": 0.0, "duration": 2.1, "text": "…" }],
  "srt": "1\n00:00:00,000 --> …"
}
```

| Field | When present |
|---|---|
| `transcript`, `videoId`, `author`, `title`, `durationSeconds`, `language`, `source` | always on success |
| `subtitleSource`, `machineTranslated` | caption-derived rows (`source: "captions"`) |
| `languageFallback` | true when preferred `languages` did not match and another track was used |
| `availableLanguages` | caption-derived rows |
| `segments` | `includeTimestamps: true` |
| `srt` | `includeSrt: true` |
| `sttModel`, `minutesBilled`, `confidence` | STT fallback rows (`source: "stt"`) |
| `segmentsUrl` / `srtUrl` | rare — oversized payloads spilled to the key-value store |

Failure / skip row (never charged):

```json
{ "type": "error", "url": "https://…", "videoId": "…", "error": "…", "errorCode": "no_captions" }
```

Common `errorCode` values: `invalid_url`, `photo_post`, `duplicate_video`, `video_unavailable`, `region_blocked`, `bot_gated`, `no_captions`, `stt_duration_unknown`, `pricing_unavailable`, `resumed_incomplete`, `input_cap_exceeded`, `charge_limit_reached`, `run_aborted`. Overflow / budget-skip rows always carry an `errorCode` — do not branch on message text.

Download the dataset as JSON, CSV, Excel, or HTML from Console or the dataset API.

### Why this one

- **Captions-first, cheap.** $0.005 per captioned video via TikTok's own ASR WebVTT tracks — no Whisper burn when captions exist.
- **Honest STT fallback.** Videos without captions (music/dance clips, many older posts) can optionally go through Deepgram at $0.012/audio-minute.
- **Agent-safe.** Bad/missing-caption/photo URLs become `type: "error"` rows; the run still succeeds.
- **Transcript only.** No likes, views, comments, or author profiles — sell the speech, not the engagement graph.
- **Timestamps + SRT** when you need them; plain `transcript` by default for RAG / MCP context packing.

### Pricing

| Event | Price | When |
|---|---|---|
| Captioned video | **$0.005** | Successful caption-derived transcript (one charge per video) |
| STT minute | **$0.012** | Per started audio-minute of Deepgram fallback (`ceil(duration/60)`, min 1) |
| Actor start | $0.005 | Per run |

Error / skipped rows are **never** billed. Cap spend with `maxTotalChargeUsd` on the run or task.

### Input rules agents must follow

- **`tiktokUrls` (required)** — `/@user/video/{id}`, `m.tiktok.com/v/{id}.html`, shortlinks (`vm.tiktok.com`, `vt.tiktok.com`, `/t/`), or bare numeric ids (≥15 digits). Max 500 per run (`maxItems: 500`). Duplicates by video ID become one transcript + `duplicate_video` error rows. Empty/whitespace entries become `invalid_url`. Photo posts (`/@user/photo/{id}`) become error rows without fetching.
- **`languages`** — optional preferred codes in order (default prefers `en`). Accepts `en`, `eng`, or `eng-US`. Within a language: creator > ASR > MT. Unmatched prefs fall back with `languageFallback: true`.
- **`includeTimestamps`** — adds `segments[]` with `{ start, duration, text }`.
- **`includeSrt`** — adds an `.srt`-format string.
- **`transcribeMissingCaptions`** — default true; when false, no-caption videos become `no_captions` error rows.
- **`deepgramApiKey`** — optional BYOK for the STT fallback (otherwise the zero-setup pool is used when available).

### Limits (honest)

- **Caption availability varies.** Music/dance clips and many older videos often have no subtitle track — those need the STT fallback (or return `no_captions`).
- **Photo posts unsupported** — no speech track; returned as `photo_post` error rows.
- **No engagement metadata by design** — transcript only (no likes/views/comments/profiles).
- **STT needs a known duration** — if page metadata has no usable duration, STT is refused (`stt_duration_unknown`) so cost stays bounded.
- **STT media cap** — videos longer than 60 minutes or with media over 120 MB are refused uncharged (`stt_duration_unknown` / `media_too_large`).
- **Pricing must be configured** — on PPE, if an event price is missing/zero the actor refuses premium delivery (`pricing_unavailable`) rather than leaking free data.
- **Source drift risk** — TikTok page JSON / CDN URLs can change; the captions path is HTTP-only and will need code updates if TikTok breaks the rehydration payload.
- **Errors never fail the run** — check `error` / `errorCode` on dataset rows.

See the **Input** tab for the full schema. See the **API** tab for run/dataset endpoints.

### FAQ

**Do I need a TikTok login?** No. The actor fetches the public video page + subtitle CDN.

**Captions vs STT?** Captions are preferred when present (`source: "captions"`). STT runs only when captions are absent and `transcribeMissingCaptions` is true with a Deepgram key available (`source: "stt"`).

**Why did my URL fail?** No captions (and STT off/unavailable), photo post, private/deleted, region-blocked, or bot gate. Check `error` / `errorCode` — the run status will still be SUCCEEDED.

**How do I keep costs predictable on a schedule?** Set `maxTotalChargeUsd` on the run/task. Prefer captioned videos when possible — STT minutes cost more.

**Related actors?** YouTube captions → [`kaz_kakyo/youtube-transcripts`](https://apify.com/kaz_kakyo/youtube-transcripts). Direct media files → [`kaz_kakyo/audio-transcriber`](https://apify.com/kaz_kakyo/audio-transcriber).

***

*If this saved you time, a Store review on the [actor page](https://apify.com/kaz_kakyo/tiktok-transcripts) helps a solo dev. Hit a problem? [Open an issue](https://apify.com/kaz_kakyo/tiktok-transcripts/issues).*

# Actor input Schema

## `tiktokUrls` (type: `array`):

Required. TikTok video URLs (`/@user/video/{id}`, `m.tiktok.com/v/{id}.html`, shortlinks `vm.tiktok.com` / `vt.tiktok.com` / `/t/`, or bare numeric ids ≥15 digits). One dataset row per URL; max 500 per run. Photo posts (`/@user/photo/{id}`) become type:error rows without fetching. Minimal call: only this field.

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

Optional language codes tried in order (e.g. `en`, `es`, `eng-US`). Accepts 2-letter, 3-letter, or full TikTok codes. Within a language, creator-provided captions beat ASR, which beat machine-translated. If none match, falls back to the best available original track and sets `languageFallback: true`. Empty = prefer English, then any available.

## `includeTimestamps` (type: `boolean`):

Default: false. When true, adds `segments[]`: `{ start, duration, text }` per caption cue (or STT utterance). Useful for programmatic indexing and RAG chunking.

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

Default: false. When true, adds `srt` (ready-to-save `.srt` string from the caption track or STT utterances).

## `transcribeMissingCaptions` (type: `boolean`):

Default: true. When a video has no caption track, download its audio and transcribe with Deepgram ($0.012 per started audio-minute). Requires the zero-setup pool key or `deepgramApiKey`. Without a key, videos without captions become `no_captions` error rows; the run still succeeds.

## `deepgramApiKey` (type: `string`):

Optional. Bring your own free Deepgram key (console.deepgram.com — $200 credit, no card) for the speech-to-text fallback when captions are absent. Leave empty to use the zero-setup pool when available. Stored encrypted; sent only to api.deepgram.com.

## Actor input object example

```json
{
  "tiktokUrls": [
    "https://www.tiktok.com/@aespa_official/video/7401509513653734664"
  ],
  "languages": [
    "en"
  ],
  "includeTimestamps": false,
  "includeSrt": false,
  "transcribeMissingCaptions": true
}
```

# Actor output Schema

## `overview` (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 = {
    "tiktokUrls": [
        "https://www.tiktok.com/@aespa_official/video/7401509513653734664"
    ],
    "languages": [
        "en"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kaz_kakyo/tiktok-transcripts").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 = {
    "tiktokUrls": ["https://www.tiktok.com/@aespa_official/video/7401509513653734664"],
    "languages": ["en"],
}

# Run the Actor and wait for it to finish
run = client.actor("kaz_kakyo/tiktok-transcripts").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "tiktokUrls": [
    "https://www.tiktok.com/@aespa_official/video/7401509513653734664"
  ],
  "languages": [
    "en"
  ]
}' |
apify call kaz_kakyo/tiktok-transcripts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kaz_kakyo/tiktok-transcripts",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ecr9I92zHp92fzzQZ/builds/yNI1WVYzoz1liVYws/openapi.json
