# Instagram & TikTok Toolkit (`emttn/instagram-tiktok-transcript`) Actor

Get TikTok and Instagram Reels transcripts with timestamps plus 3 frames per video, from public video URLs or a scraper's dataset: up to 100 videos per run, ready for AI agents, pay per started minute.

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

## Pricing

$20.00 / 1,000 started minute of video delivereds

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

## Instagram & TikTok Toolkit

**Give your agent the words and the frames of an Instagram Reel or a TikTok.** This actor turns public Instagram and TikTok video URLs, or the dataset of a scraper you already ran, into transcripts with timestamps plus 3 frames per video, ready to paste into an LLM and one call away from Claude and other agents through the Apify MCP server.

Use it to:

- Analyze competitors' Reels: what they say, how they open, how long they run.
- Pull scripts and hooks out of TikToks.
- Transcribe the videos a TikTok or Instagram scraper already returned.
- Save the images of Instagram posts before their links expire.
- Feed an LLM the words and the frames of a short video.

### TikTok transcript with timestamps and frames

Send up to 100 single video URLs per run, or pick the dataset of a scraper run. For each video you get one dataset item:

- `url`, `platform` (`tiktok` or `instagram`), `videoId` and `durationSecs`.
- `lang`: the language detected in the audio, or the language of the video's own captions when it has them.
- `segments`: the transcript as `{ start, end, text }` in seconds, and `text`: the whole transcript as one string.
- `frames`: 3 images at 10%, 50% and 90% of the video, each `{ t, url }`, stored in your run's key-value store.
- `frameskimUrl`: a link to [Frameskim](https://apify.com/emttn/frameskim), the sister actor for frames by time range and the moments of YouTube videos.

The same item shape works for TikTok links and Instagram Reel links, and a run can mix both.

### Instagram Reels transcript

Paste public Reel URLs in `urls`, or pass the dataset of [apify/instagram-reel-scraper](https://apify.com/apify/instagram-reel-scraper) to transcribe the Reels it found. Each Reel comes back with its transcript, segments and 3 frames.

A video without speech is delivered with its frames, empty `segments` and `text`, `noSpeech: true` and the `message` "No speech found. Nothing was charged for this video." A video that fails becomes an item with `url` and `error` and is not charged.

### Instagram post images from a scraper's dataset

Pass the dataset of a run of [apify/instagram-scraper](https://apify.com/apify/instagram-scraper) or [apify/instagram-post-scraper](https://apify.com/apify/instagram-post-scraper) in `datasetId` (or paste its items in `datasetItems`) and each image or carousel post becomes one dataset item:

- `url` (the post's permalink), `platform`, `kind: "post"`, `postId`, `type` (`image` or `carousel`), `caption`, `timestamp` and `location` (`{ name, id }`, or `null` when the scraper does not give it).
- `images`: each image as `{ index, url, width, height }`, downloaded in the run and stored in your run's key-value store. The `url` is the stored record, never the Instagram CDN link, which expires soon after the scrape: pass the dataset right after the scraper finishes. A carousel gives up to 20 images in order.
- With `readFlyers` set to `first` (the first image of each post) or `all` (every image), each image read also carries `flyer`: `{ isEvent, title, dates: [{ text, start }], venue, artists, rawText }`. The model returns `null` or an empty list for what is not in the image; `isEvent: false` means the image is not an event flyer. `readFlyers` is `none` by default.
- `imagesMissing` and `imagesMissingReason` when some images could not be saved, and `flyerError` on an image that could not be read.

The post's owner, tagged accounts, mentions, comments, likes and Instagram's alt text are never in the output. Profiles, hashtags, stories and searches are not read: run the scraper on them and pass its dataset.

When `readFlyers` is on, each image read is sent to a vision model (Google Gemini 2.5 Flash Lite) through OpenRouter, with data collection denied, to read the event. Nothing else leaves the run.

### Input from a scraper's dataset

Instead of `urls`, or together with them, choose in `datasetId` the dataset of a run of [clockworks/tiktok-scraper](https://apify.com/clockworks/tiktok-scraper), [apify/instagram-reel-scraper](https://apify.com/apify/instagram-reel-scraper) or [apify/instagram-scraper](https://apify.com/apify/instagram-scraper), or paste their items in `datasetItems`. The actor reads that dataset only with the read permission you give it in the input field, takes each video's public URL (never the expiring CDN link) and its duration, and copies only the URL and the ID into its output. Image and carousel posts are delivered as posts (next section); items that are neither (a scraper's error rows, profiles) become items with `source` and "Not a video item. Nothing was charged for it." The limit of 100 counts the videos and posts left after that.

### Pricing

Pay per event, no subscription. Apify bills each event and never more than the spending limit you set for the run.

| Event | Price (USD) | When |
|---|---|---|
| `minute` | $0.02 | Per started minute of each delivered video: 30 s is 1, 61 s is 2, 10 minutes is 10 |
| `post` | $0.002 | Per Instagram image or carousel post delivered |
| `image` | $0.001 | Per post image saved in your run's storage (a carousel of 20 is 20) |
| `flyer` | $0.005 | Per post image read when `readFlyers` is on, even when the image turns out not to be an event |

A post of one image costs $0.003 ($0.008 with its flyer read); a carousel of 10 images with the first one read costs $0.017.

What is not delivered is not charged: a video without speech, a private, removed or blocked video, a video that takes too long or needs more processing than its price covers, a post whose images could not be downloaded, an image that could not be read, and every video or post left after a stop (see Limits). If every item fails, the run ends with the reason and "Nothing was charged."

### Examples

Input:

```json
{ "urls": ["https://www.tiktok.com/@bbcnews/video/7694767678363340054", "https://www.instagram.com/reel/DWjcPlnjQIx/"] }
```

Output (one item per video, values shortened):

```json
{ "url": "https://www.tiktok.com/@bbcnews/video/7694767678363340054", "platform": "tiktok", "videoId": "7694767678363340054", "durationSecs": 26.7, "lang": "en", "segments": [{ "start": 0.0, "end": 4.2, "text": "..." }], "text": "...", "frames": [{ "t": 2.67, "url": "https://api.apify.com/v2/key-value-stores/.../records/tiktok-7694767678363340054-0.jpg" }], "frameskimUrl": "https://apify.com/emttn/frameskim" }
```

A post with one flyer read (`readFlyers: "first"`, values shortened):

```json
{ "url": "https://www.instagram.com/p/DZxvMgyH8yR/", "platform": "instagram", "kind": "post", "postId": "DZxvMgyH8yR", "type": "image", "caption": "Live at the club, Friday 24 October", "timestamp": "2026-06-20T10:00:04.000Z", "location": { "name": "Example Venue", "id": "1234567890" }, "images": [{ "index": 1, "url": "https://api.apify.com/v2/key-value-stores/.../records/post-DZxvMgyH8yR-1", "width": 1080, "height": 718, "flyer": { "isEvent": true, "title": "Live at the club", "dates": [{ "text": "Friday 24 October, 21 h", "start": "2026-10-24T21:00:00" }], "venue": "Example Venue", "artists": ["Example Band"], "rawText": "..." } }] }
```

A video that failed:

```json
{ "url": "https://www.tiktok.com/@someone/video/7000000000000000000", "error": "This video was removed or does not exist. Nothing was charged." }
```

### Use it by API

```bash
curl -X POST "https://api.apify.com/v2/acts/emttn~instagram-tiktok-transcript/runs" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.tiktok.com/@bbcnews/video/7694767678363340054"]}'
```

Then read the run's dataset items when it finishes.

### Use it from Claude (MCP)

Add the Apify MCP server with this actor as a tool:

```json
{ "mcpServers": { "apify": { "url": "https://mcp.apify.com/?actors=emttn/instagram-tiktok-transcript" } } }
```

Then ask, for example: "Use Instagram & TikTok Toolkit on this Reel, list the spoken steps and check the frames for the ingredients."

### Integrations

- **Run it after your scraper.** In the Integrations tab of [clockworks/tiktok-scraper](https://apify.com/clockworks/tiktok-scraper) or [apify/instagram-scraper](https://apify.com/apify/instagram-scraper), add this actor with an Actor-to-Actor integration: it starts when the scraper finishes and reads its dataset: [Actor-to-Actor integrations](https://www.youtube.com/watch?v=zExnYbvFoBM).
- **Schedule it.** Put the scraper on a schedule and the chain runs again each time, so an account is transcribed every day or every week as it posts: [how to schedule tasks](https://www.youtube.com/watch?v=GRFW_Loo2dk).
- **Make, Zapier, n8n and webhooks** work through Apify's own integrations, and Claude through the MCP server above.

https://www.youtube.com/watch?v=GRFW_Loo2dk

https://www.youtube.com/watch?v=zExnYbvFoBM

Ready-made tasks (TikTok transcript, Instagram Reels transcript, save Instagram post images) are in the Tasks tab of this page.

### Related actors

- [TikTok Scraper](https://apify.com/clockworks/tiktok-scraper): finds the videos of a profile, hashtag or search to pass here.
- [Instagram Scraper](https://apify.com/apify/instagram-scraper): finds posts and Reels of a profile or hashtag.
- [Instagram Reel Scraper](https://apify.com/apify/instagram-reel-scraper): lists the Reels of an account.
- [YouTube Transcript](https://apify.com/emttn/youtube-transcript): the same for YouTube, up to 100 videos per run.
- [Frameskim](https://apify.com/emttn/frameskim): frames and moments of YouTube videos.

### Limits

- Videos: single public videos only: no profiles, hashtags, searches, stories or private accounts. More than 100 videos, or any URL that is not a single video, is refused before anything is processed: "Accepts single video URLs only, up to 100 per run. Nothing was charged."
- Videos are processed one after the other. Each video may take 120 s plus 45 s per started minute of its duration; one that takes longer is stopped and not charged. The batch stops after 55 minutes.
- Each video may cost us at most 60% of what it is charged; one that needs more is stopped with "Stopped: this video needs more processing than its price covers. Nothing was charged for it."
- The batch stops after 5 failures in a row, or when what the failed and no-speech videos cost us exceeds 30% of what was charged plus 0.02 USD. The videos left become `error` items ("Stopped: ..."), nothing is charged for them, and the run still ends as succeeded with what was delivered.
- Before each video the actor checks that your spending limit covers all of its minutes; when it does not, the batch stops and the rest are `error` items, not charged.
- Duplicate URLs (the same video twice, in any URL form) are processed and charged once.
- Posts come only from a scraper's dataset, never from a profile, hashtag, story, search or a loose `/p/` URL: those end without processing anything with "Profiles, hashtags and stories are not accepted. Run apify/instagram-scraper or apify/instagram-post-scraper on the profile and pass its dataset in `datasetId`. Nothing was charged." A `/p/` URL that is not a video says "This post has no video. To get image posts, pass the dataset of apify/instagram-scraper in `datasetId`. Nothing was charged for it."
- A post has up to 20 images of at most 8 MB each. A post whose images cannot all be downloaded is delivered with `imagesMissing` if at least one was saved; with none saved it becomes an item with `error` "Could not download this post's images: their links expire soon after scraping. Run the scraper again and pass its dataset right away. Nothing was charged for this post."
- Each post may cost us at most 30% of what it is charged, with the same stop rules as videos; before each post the actor checks that your spending limit covers it in full (one `post`, its `image` events and the `flyer` reads you asked for) and otherwise stops with "Stopped: your maximum charge for this run was reached. Nothing was charged for this post."
- Instagram image links usually expire within hours of the scrape. If the scraper's dataset is old, run it again.

### FAQ

**Which languages?** The language is detected from the audio and returned in `lang`. Tested on English so far.

**What if a run fails?** The status message says why. A run that cannot start says "Nothing was charged."; a run that stops unexpectedly says "Only delivered videos were charged." Retry later if the platform blocked the request.

**Do you keep my videos or transcripts?** No. Each video file is deleted as soon as its item is done and is never delivered. Frames and transcripts live only in your run's own Apify storage, with your plan's retention.

**Do you keep post images?** No. Images are saved only in your run's default key-value store, with your plan's retention. With `readFlyers` on, each image read goes to a vision model through OpenRouter with data collection denied; nothing is kept by us.

**Can it read a flyer into a calendar event?** It returns the title, dates (as written and as ISO 8601 when the year can be inferred from the post's date), venue, artists and the raw text. It returns `null` for what is not in the image, so check `isEvent` and the dates before you publish them.

**Is there an official Instagram or TikTok transcript API?** Partly, and with conditions. Meta's [IG Media reference](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-media) has no transcript field, and it only returns media owned by Instagram professional accounts. TikTok's [Research API video query](https://developers.tiktok.com/doc/research-api-specs-query-videos) exposes `voice_to_text`, but it is the Research API, for [qualified researchers](https://developers.tiktok.com/products/research-api/), and needs a client access token with the `research.data.basic` scope. This actor takes public video URLs or a scraper's dataset and transcribes the audio.

**TikTok and Instagram transcript alternatives?** You can open each video and type what it says, or run a scraper and a speech-to-text tool of your own and join the two. This actor does both in one run and bills per started minute; the scrapers it works with are in Related actors.

**YouTube?** Use [YouTube Transcript for AI Agents](https://apify.com/emttn/youtube-transcript) for transcripts and [Frameskim](https://apify.com/emttn/frameskim) for frames and moments.

### Legal

Not affiliated with TikTok, Instagram or Meta. By running this actor you confirm you have the right to process each video and each post you submit.

Takedown requests: open an issue in the Issues tab of this page or write to support@frameskim.dev. Blocked videos, posts and accounts are refused on the next run with "This video is not available in this actor. Nothing was charged." (for a post, "This post is not available in this actor. Nothing was charged.")

[Terms](https://frameskim.dev/terms) · [Privacy](https://frameskim.dev/privacy)

# Actor input Schema

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

Public TikTok videos (tiktok.com/@user/video/...) or Instagram Reels and video posts (instagram.com/reel/... or /p/...), one URL per video, up to 100 per run. Profiles, hashtags and searches are not accepted. Leave empty to read a dataset instead.

## `datasetId` (type: `string`):

The dataset of a run of clockworks/tiktok-scraper, apify/instagram-reel-scraper, apify/instagram-scraper or apify/instagram-post-scraper in your account. The actor reads it only with the permission you give here: videos get a transcript and 3 frames, and Instagram image and carousel posts get their images saved (pass the dataset right after the scrape: the image links expire). Up to 100 videos and posts in total; other items are reported and not charged.

## `datasetItems` (type: `array`):

Items of one of those scrapers pasted as JSON, instead of choosing their dataset.

## `readFlyers` (type: `string`):

For Instagram image and carousel posts: read the event in the image (title, dates, venue, artists and all the text). none: do not read; first: the first image of each post; all: every image. $0.005 per image read; unreadable images are not charged.

## Actor input object example

```json
{
  "urls": [
    "https://www.tiktok.com/@bbcnews/video/7694767678363340054",
    "https://www.instagram.com/reel/DWjcPlnjQIx/"
  ],
  "readFlyers": "first"
}
```

# Actor output Schema

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

Dataset items: for a video, its URL, platform, ID, duration, language, timestamped segments, full text, 3 frames and a Frameskim link; for a post, its permalink, ID, type, caption, date, location, saved images and each flyer read; failed items carry an error and are not charged.

# 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/@bbcnews/video/7694767678363340054",
        "https://www.instagram.com/reel/DWjcPlnjQIx/"
    ],
    "readFlyers": "first"
};

// Run the Actor and wait for it to finish
const run = await client.actor("emttn/instagram-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/@bbcnews/video/7694767678363340054",
        "https://www.instagram.com/reel/DWjcPlnjQIx/",
    ],
    "readFlyers": "first",
}

# Run the Actor and wait for it to finish
run = client.actor("emttn/instagram-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/@bbcnews/video/7694767678363340054",
    "https://www.instagram.com/reel/DWjcPlnjQIx/"
  ],
  "readFlyers": "first"
}' |
apify call emttn/instagram-tiktok-transcript --silent --output-dataset

```

## MCP server setup

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