# Video Transcript API — YouTube + TikTok for AI Agents (`architekture/unified-transcript-api`) Actor

Fetch video transcripts from YouTube and TikTok in one JSON schema for AI pipelines and agents. $1 per 1,000 transcripts, misses free, no start fee, batch up to 1,000 URLs.

- **URL**: https://apify.com/architekture/unified-transcript-api.md
- **Developed by:** [architek.earth](https://apify.com/architekture) (community)
- **Categories:** AI, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 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.

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

## 🎬 Video Transcript API — YouTube + TikTok, One Schema for AI Agents

**One API for every video platform your agents watch.** Paste YouTube and TikTok URLs in one list and get back one JSON shape for all of them: `platform`, `videoId`, `url`, `title`, `transcript`, `language`, `durationSec`. Recorded 18 Oct 2026 on Apify infrastructure: 7 of 7 YouTube and 12 of 12 TikTok test URLs were answered on the first attempt, with 0 retries — 15 transcripts, and the captionless and non-existent videos correctly returned as free status rows.

From **Architekture**, makers of the Threads, Reddit, Substack and retail scrapers.

🤖 **Built for AI workflows.** One deterministic, schema-stable row per URL — drop it into LLM context, RAG pipelines or agent tool calls via the Apify API, the Apify MCP server (`mcp.apify.com`), LangChain, LlamaIndex, or AI coding agents like Claude Code and Cursor. Your agent never has to branch on which platform a link came from.

| 🌐 Platforms | 📍 Access | 📦 Recorded run | 💰 From | 🔑 Standout fields |
|---|---|---|---|---|
| YouTube + TikTok in one list (Instagram Reels: free status row) | Public videos, no login 🌍 | 5 YouTube + 10 TikTok transcripts, 0 retries | $1.00 / 1,000 transcripts | same `transcript` / `language` / `durationSec` on every platform, optional timestamps |

Give it video URLs and get one row per distinct URL:

- the full transcript as one plain-text string, ready for a prompt
- the language you got (`en`, `de`, `en-US` …) and whether it was auto-generated
- the video title (TikTok: the caption text), author/channel and duration in seconds
- optional `segments` with start time, duration and text for each cue

### 🚀 Why choose this actor

- 🔗 **One schema across platforms.** YouTube and TikTok rows have exactly the same fields, in the same types. Mix links in one run; no per-platform parsing on your side.
- 💸 **$1 per 1,000 transcripts, no start fee, no per-minute fee.** A 20-minute TED talk costs the same $0.001 as a 30-second TikTok.
- 🆓 **Never pay for misses.** No captions, private, removed, wrong language, blocked or not a video URL: you get a free row with a machine-readable `status` and a plain-English `message`.
- ✅ **Never charged for a language you did not ask for.** Ask for `de` and the video has only English: the row is `language_unavailable` (free) and lists what exists.
- 🔎 **Batch-friendly.** Up to 1,000 URLs per run, 4 in parallel; the same video given twice (or as a short link) is delivered and charged once, and the repeat gets a free `duplicate` row.
- 📈 **Resumable.** After a platform migration or a resurrect, the run continues where it stopped, without duplicate or re-charged rows.

### ⚠️ Worth knowing up front

| Question | Answer |
|---|---|
| Does it transcribe audio itself? | ❌ No. It returns the captions the platform already has (uploaded or auto-generated). No speech-to-text, so no hallucinated words. |
| Instagram Reels? | ❌ Not in this version. Instagram shows logged-out visitors no caption tracks (checked 18 Oct 2026). Reel URLs get a free `not_supported` row. |
| How many TikToks have subtitles? | In our 18 Oct 2026 sample of 15 news and brand videos, 12 had TikTok's auto-captions. Videos without speech usually have none (free row). |
| Do I need an account or cookies? | ❌ No. Public videos only. |
| Translation? | ❌ Not in this version. You get the tracks the platform has; `languages` picks among them. |
| Which TikTok links work? | `tiktok.com/@user/video/…`, embeds and `m.tiktok.com/v/….html` (tested live). `vm.`/`vt.tiktok.com` and `/t/` short links are supported (live check pending). Photo posts get a free row. |

### ⚡ Try the example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://www.tiktok.com/@nasa/video/7697300954667994381"
  ]
}
```

Add `"includeTimestamps": true` for per-cue timing, or `"languages": ["de", "en"]` to prefer German, then English.

### ⚙️ Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `videoUrls` | array | — | YouTube (watch, youtu.be, Shorts, embed, live, or a bare 11-character ID) and TikTok URLs, mixed. Instagram Reel URLs get a free `not_supported` row. Up to 1,000. |
| `languages` | array | `[]` | Language codes in order of preference (`en`, `de`, `pt-BR`). Empty = the video's own language (the track the platform shows by default). No match = free `language_unavailable` row. |
| `includeTimestamps` | boolean | `false` | Adds `segments` (start, duration in seconds, text). Off keeps rows small for LLM context. |
| `proxyConfiguration` | proxy | Apify Residential | Used for YouTube only. YouTube refuses Apify datacenter IPs (7 player clients tested 18 Oct 2026), so Residential is the default and the only tested option. TikTok always uses the Apify datacenter proxy. |
| `feedback` | string | — | A note to the maintainers. Read by a human. |

### 🧱 Stable fields for automations

Every row has every field below (`null` when not applicable), whatever the platform or status. New fields are only ever added.

| Field | Meaning |
|---|---|
| `platform` | `youtube`, `tiktok` or `instagram` (`null` for an unrecognised URL). |
| `videoId`, `url` | The platform's video ID and the canonical video URL. |
| `title` | YouTube title; TikTok caption text (TikTok has no separate title). |
| `transcript` | The whole transcript as one string (`null` unless `status` is `ok`). |
| `language` | Language of the transcript you got, e.g. `en`, `de`, `en-US`. |
| `durationSec` | Video length in seconds. |
| `status` | `ok` (charged) or a free status: `no_captions`, `language_unavailable`, `unavailable`, `private`, `age_restricted`, `blocked`, `error`, `invalid_url`, `not_supported`, `duplicate`. |
| `message` | Why, in plain English, when `status` is not `ok`. |
| `isAutoGenerated`, `isTranslated` | Auto captions (YouTube ASR, TikTok's auto-captions); `isTranslated` is `true` only for a TikTok machine-translated track you asked for in `languages` (never chosen unasked). |
| `author` | YouTube channel name or TikTok handle. |
| `segments`, `segmentCount` | Cues (`start`, `duration`, `text`) when `includeTimestamps` is on; `segmentCount` always. |
| `availableLanguages` | The caption tracks the video has, so you can re-ask in another language. |
| `input`, `scrapedAt` | The URL exactly as you gave it; ISO 8601 UTC time. |

### 📦 Output example

Assembled from values recorded on 18 Oct 2026 (transcripts shortened here).

```json
{
  "platform": "tiktok",
  "videoId": "7697300954667994381",
  "url": "https://www.tiktok.com/@nasa/video/7697300954667994381",
  "title": "There was plenty to see across NASA this week 🚀 …",
  "transcript": "From breaking records on Mars to lighting up the skies over Earth, NASA put on quite a show this week. …",
  "language": "en-US",
  "durationSec": 121,
  "status": "ok",
  "message": null,
  "isAutoGenerated": true,
  "isTranslated": false,
  "author": "nasa",
  "segments": null,
  "segmentCount": 43,
  "availableLanguages": [{ "language": "en-US", "isAutoGenerated": true, "isTranslated": false }],
  "input": "https://www.tiktok.com/@nasa/video/7697300954667994381",
  "scrapedAt": "2026-10-18T22:02:20Z"
}
```

The same shape from YouTube:

```json
{ "platform": "youtube", "videoId": "iG9CE55wbtY", "title": "Do schools kill creativity? | Sir Ken Robinson | TED", "transcript": "Good morning. How are you? …", "language": "en", "durationSec": 1203, "status": "ok", "isAutoGenerated": false, "segmentCount": 427 }
```

A video without captions (free):

```json
{ "platform": "youtube", "videoId": "TtIrFvWoY8E", "title": "NASA Climate Spiral", "transcript": null, "durationSec": 30, "status": "no_captions", "message": "This video has no captions (the uploader disabled them or none were generated). Not charged." }
```

### 🧠 Advanced usage

- 🗂️ **Agent tool call:** expose the actor as one tool, "get transcript for these URLs"; branch only on `status`.
- 🔎 **RAG over a channel:** feed `transcript` to your chunker; keep `url` and `videoId` as citation metadata.
- 📈 **Clip search:** turn on `includeTimestamps` and search `segments[].text` to jump to the second a phrase is said.
- ✅ **Completion check:** read `RUN_SUMMARY`. It has `status` (`COMPLETE`, `PARTIAL`, `NO_RESULTS`, `BUDGET_LIMIT`, `TIME_LIMIT`, `ABORTED`, `STATE_SAVE_FAILED`, `PUSH_FAILED`, `INVALID_INPUT`, `NOT_CONFIGURED`), `transcripts`, `byPlatform`, `byStatus`, `charged` and `pending`.

### 🔌 Integrations

Export as JSON, CSV or Excel; push to Google Sheets; trigger from Make, Zapier or n8n; get a webhook when a run finishes; schedule with the Apify Scheduler.

### 💸 How much does it cost?

1,000 transcripts ≈ **$1.00**. A 50-video research batch ≈ **$0.05**. Videos without captions, private or removed videos and Instagram links cost nothing. Platform usage (including the residential proxy YouTube needs) is included, and there is no start fee.

### 💰 Pricing

| Event | Price | Covers |
|---|---|---|
| `transcript` | $1.00 / 1,000 | One row with `status: "ok"` — one video's transcript, any length, any platform |

Every other row is free. A run stops cleanly at your spending limit, with charges equal to transcripts delivered.

### ❓ FAQ

**Why is a TikTok transcript slightly off?** TikTok's auto-captions are speech recognition. We return them verbatim and mark them `isAutoGenerated: true`; we never edit or "fix" words.

**Which YouTube track do I get with no `languages`?** The one YouTube itself shows by default; if YouTube marks none, the spoken language (a manual track in it wins over auto-captions).

**Why `blocked`?** The platform refused the request after 3 attempts on fresh IPs. It is free; re-run just those URLs later.

### 🤖 For AI agents & LLM apps

`architekture/unified-transcript-api` turns YouTube and TikTok video URLs into transcripts in one deterministic, schema-stable JSON shape: `platform`, `videoId`, `url`, `title`, `transcript`, `language`, `durationSec`, plus a machine-readable `status`. Invoke it from any agentic workflow via the Apify API or the Apify MCP server (`mcp.apify.com`) as a tool call. Cost is bounded before it starts: $1.00 per 1,000 transcripts, no start fee, and misses are free. Honest limits: existing captions only (no speech-to-text, no translation), public videos only, no Instagram in this version.

Works out of the box with LangChain, LlamaIndex and AI coding agents — call it as a tool from Claude Code, Cursor, or any MCP client via `mcp.apify.com`.

### 🧰 More Architekture scrapers

- 🎬 **Social & video:** Video Transcript API (you are here) · [Threads Replies](https://apify.com/architekture) · [Reddit](https://apify.com/architekture) · [Substack](https://apify.com/architekture)
- 🇪🇺 **Retail:** [Mercadona Spain](https://apify.com/architekture/mercadona-es-scraper) · [ALDI Ireland](https://apify.com/architekture/aldi-ie-scraper) · [Dunnes Stores Ireland](https://apify.com/architekture/dunnes-ie-scraper)

### 🙋 Support

Open an Issue on this actor's page; a human replies within a day.

### ⚖️ Legal compliance

This actor reads only captions that YouTube and TikTok show logged-out visitors on public videos, without an account. Transcripts can be copyrighted and can contain personal data. You are responsible for having the right to use them (copyright, GDPR/CCPA) and for the platforms' terms. Not affiliated with YouTube, Google, TikTok, ByteDance, Instagram or Meta.

### 📝 Changelog

- **2026-10-18**: 1.0. YouTube and TikTok transcripts in one schema; language preference with free `language_unavailable`; optional timestamps; short-link resolution and de-duplication; free status rows for every miss (Instagram Reels included); clean stops at the spending limit and timeout; resumable after platform migration.

# Actor input Schema

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

YouTube (watch, youtu.be, Shorts, embed, live, or a bare 11-character ID) and TikTok (tiktok.com/@user/video/…, vm./vt. short links, /t/ links, embeds) URLs, mixed freely. Instagram Reel URLs get a free not_supported row (see README). Every distinct URL gets exactly one row (a repeat of the same video gets a free duplicate row). Up to 1,000.

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

Language codes in order of preference, e.g. en, de, pt-BR. Empty = the video's own language (the track the platform shows by default). When none of these exists, the row is language_unavailable (free) and lists the languages that do exist; you are never charged for a transcript in a language you did not ask for.

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

Add a segments list (start, duration in seconds, text) to each transcript. Off = plain transcript text only, the cheapest payload for LLM context.

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

Used for YouTube only. YouTube refuses every Apify datacenter IP (tested 2026-10-18), so Residential is the default and the only tested option. TikTok always uses the Apify datacenter proxy.

## `feedback` (type: `string`):

Tell the maintainers what you needed and did not get. Read by a human; does not change the output.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://www.tiktok.com/@nasa/video/7697300954667994381"
  ],
  "includeTimestamps": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One row per input URL: status ok rows carry the transcript (charged); every other status is free.

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

RUN_SUMMARY: transcripts per platform, free rows by status, charges.

# 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.youtube.com/watch?v=iG9CE55wbtY",
        "https://www.tiktok.com/@nasa/video/7697300954667994381"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("architekture/unified-transcript-api").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.youtube.com/watch?v=iG9CE55wbtY",
        "https://www.tiktok.com/@nasa/video/7697300954667994381",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("architekture/unified-transcript-api").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.youtube.com/watch?v=iG9CE55wbtY",
    "https://www.tiktok.com/@nasa/video/7697300954667994381"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call architekture/unified-transcript-api --silent --output-dataset

```

## MCP server setup

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

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/V3NpoZ3GodYzpaL8P/builds/RbOy1eASTzLalvQOY/openapi.json
