# Instagram Reels Transcript - Reel to Text & SRT (`sauliusautomatesit/instagram-reels-transcript`) Actor

Transcribe public Instagram Reels to text, timestamped segments, SRT and VTT. Instagram publishes no caption track, so every Reel is transcribed with Whisper speech-to-text — which is exactly why Reels without captions still come back as text. Public content only: no login, no cookies.

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

## Pricing

from $34.00 / 1,000 transcript from whisper speech-to-texts

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?

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

## Instagram Reels Transcript — Reel to text & SRT

**Turn public Instagram Reels into transcripts, timestamps, SRT and VTT.**

Paste Reel links, get the words back. Every Reel returns plain text, timestamped segments, a
ready-to-use `.srt` subtitle file, a `.vtt` file, and the metadata Instagram makes public:
account, caption, hashtags, duration, detected language.

Instagram does not publish a caption track to anyone who is not signed in — not through the
embed player, not through its web API, not on the Reel page itself. Tools that read caption
tracks therefore return nothing for Reels. **This Actor transcribes the audio instead**, with
Whisper speech-to-text, which is why it returns a transcript for Reels that have no captions
at all.

- **No API key, no login, no cookies.** Public Reels only.
- **90+ languages,** detected automatically. Measured across a 50-Reel test set: English,
  Spanish, Arabic and French all came back correctly labelled.
- **Bulk-safe.** One dead link never breaks the rest of the run.
- **You only pay for delivered transcripts.** Failures, private Reels, music-only Reels and
  over-length Reels are free.

***

### What it handles

| Input | Supported |
|---|---|
| `instagram.com/reel/SHORTCODE/` | ✅ |
| `instagram.com/reels/SHORTCODE/` | ✅ |
| `instagram.com/USERNAME/reel/SHORTCODE/` | ✅ |
| `instagram.com/p/SHORTCODE/` and `/tv/SHORTCODE/` | ✅ when the post is a video |
| Profile, explore or hashtag URLs | ❌ — not single Reels; returned as `INVALID_URL` |
| Private accounts, deleted Reels, login-gated content | ❌ — returned as an error row, uncharged |
| Photo posts and photo carousels | ❌ — `MEDIA_UNAVAILABLE`, uncharged |

Other platforms return a clear error pointing you at
[Short Video Transcriber](https://apify.com/sauliusautomatesit/short-video-transcriber) for
TikTok and YouTube Shorts.

***

### Honest limits

This section is here rather than buried at the bottom, because Instagram is stricter than the
other short-form platforms and you should know what you are buying.

- **There is no cheap caption path.** Every successful Reel is charged `asr_transcript`
  ($0.040). Sister Actors for TikTok and YouTube Shorts can often use a platform caption
  track at $0.002; Instagram offers none, so that discount does not exist here.
- **Public, logged-out content only.** The Actor holds no Instagram account and accepts no
  cookies or session tokens. A Reel from a private account cannot be transcribed by it, and
  no setting will change that.
- **Some Reels are music, not speech.** Those return `NO_SPEECH_DETECTED` and are not charged.
  On a 50-Reel test set spanning 14 public accounts, 62 % produced a transcript and most of
  the rest were genuinely music-only — for talking-head, news and explainer accounts the rate
  was far higher (Al Jazeera English: 8 of 8).
- **Instagram refuses some datacenter IPs.** The Actor retries those requests through Apify's
  residential proxy automatically. Leave `useResidentialFallback` on.
- **Instagram does not tell an anonymous viewer how long a Reel is.** The length caps are
  therefore enforced from the downloaded audio's own header — after the download, before
  transcription, and before anything is charged.

***

### What you get

One dataset row per input URL:

```json
{
  "url": "https://www.instagram.com/reel/Dcj6uM5CWIE/",
  "platform": "instagram_reels",
  "videoId": "Dcj6uM5CWIE",
  "author": "aljazeeraenglish",
  "authorName": "Al Jazeera English",
  "title": "The story behind the images that defined the week",
  "hashtags": ["news", "aljazeera"],
  "durationSeconds": 129.9,
  "language": "en",
  "source": "whisper_asr",
  "sourceDetail": null,
  "whisperModel": "base",
  "text": "This week began with a moment few expected…",
  "segments": [
    { "start": 0.0,  "end": 3.28, "text": "This week began with a moment few expected" },
    { "start": 3.28, "end": 6.94, "text": "and ended with one nobody could ignore." }
  ],
  "srt": "1\n00:00:00,000 --> 00:00:03,280\nThis week began with a moment few expected\n\n2\n…",
  "vtt": "WEBVTT\n\n00:00:00.000 --> 00:00:03.280\nThis week began with a moment few expected\n\n…",
  "wordCount": 311,
  "segmentCount": 46,
  "publishedAt": "2026-08-24T09:12:04+00:00",
  "status": "ok",
  "error": null
}
```

`source` is always `whisper_asr` on this Actor — Instagram offers nothing else. The field
exists because the whole Actor family shares one row shape, so a dataset from this Actor drops
straight into a pipeline built for the TikTok or Shorts Actor.

The `srt` and `vtt` fields are complete, valid subtitle files.

***

### Pricing

| Event | Price | When it fires |
|---|---|---|
| `asr_transcript` | **$0.040** | A Reel was transcribed. This is the event that fires on this Actor. |
| `caption_transcript` | **$0.002** | Reserved for a native caption track. Instagram publishes none, so in practice this never fires here. |
| `apify-actor-start` | **$0.00005** | Apify's standard start event, charged once per gigabyte of the run's memory. This Actor runs at 2 GB, so **$0.0001 per run**. |

Those are the Free-plan prices. Apify's paid plans get the standard Store discount off every
event — Bronze 5 %, Silver 10 %, Gold and above 15 % — so `asr_transcript` costs $0.038,
$0.036 or $0.034 on those plans.

| Reels transcribed | Cost |
|---|---|
| 100 | **$4.00** |
| 1,000 | **$40.00** |
| 10,000 | **$400.00** |

(Free-plan prices; a Gold plan pays 15 % less. Music-only and failed Reels are not in these
counts, because they are not charged.)

**Nothing else is charged.** In particular you are not charged for:

- Reels that are deleted, private or login-gated;
- Reels longer than your `maxDurationSeconds` or `maxAsrDurationSeconds` caps;
- Reels where Whisper finds no speech at all (music-only clips);
- photo posts, profile URLs, malformed URLs or links from other platforms;
- any run that fails.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `videoUrls` | array | — | **Required.** Public Instagram Reel URLs, one per line. |
| `language` | string | *(blank)* | Two-letter code (`en`, `es`, `pt`, `ar`…). Blank lets Whisper detect it, which is usually right. Worth setting on short or noisy Reels. |
| `allowWhisperFallback` | boolean | `true` | Leave on. Off makes every Reel return a free `CAPTIONS_UNAVAILABLE` error, since Instagram has no caption track to fall back to. |
| `maxDurationSeconds` | integer | `600` | Reels longer than this are skipped, free. Max `1200`. |
| `maxAsrDurationSeconds` | integer | `180` | Length limit for transcription. Longer Reels return `ASR_DURATION_EXCEEDED`, free. Raise it if you want long Reels anyway. |
| `maxVideos` | integer | `1000` | Safety cap on the number of URLs processed. |
| `whisperModel` | string | `base` | `tiny`, `base` or `small`. `small` is better on accents and noise. |
| `beamSize` | integer | `1` | Whisper decoding beam width. |
| `concurrency` | integer | `5` | Reels resolved in parallel. Whisper always runs one at a time. |
| `includeFailedItems` | boolean | `true` | Keep a row (with an `error` code) for every failure, so inputs reconcile to outputs. |
| `useResidentialFallback` | boolean | `true` | Retries through a residential IP when Instagram refuses the normal proxy. **Leave this on.** |
| `proxyConfiguration` | object | Apify Proxy | Recommended. The run pins one proxy session, which Instagram's media CDN requires. |

#### Minimal input

```json
{
  "videoUrls": [
    "https://www.instagram.com/reel/Dcj6uM5CWIE/",
    "https://www.instagram.com/nasa/reel/DcCH2ZygIiP/"
  ]
}
```

***

### Run it

#### In the Apify Console

Open the Actor, paste your links into **Instagram Reel URLs**, one per line, and click
**Start**. Results appear in the dataset and export as JSON, CSV, XLSX or Excel — or download
the `srt` column straight into subtitle files.

#### API

```bash
curl -X POST "https://api.apify.com/v2/acts/sauliusautomatesit~instagram-reels-transcript/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "videoUrls": [
          "https://www.instagram.com/reel/Dcj6uM5CWIE/"
        ]
      }'
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")

run = client.actor("sauliusautomatesit/instagram-reels-transcript").call(input={
    "videoUrls": [
        "https://www.instagram.com/reel/Dcj6uM5CWIE/",
        "https://www.instagram.com/reel/DcCH2ZygIiP/",
    ],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["status"] != "ok":
        print(f"{item['inputUrl']}: {item['error']} — {item['errorMessage']}")
        continue
    print(item["author"], item["language"], item["wordCount"], "words")
    with open(f"{item['videoId']}.srt", "w", encoding="utf-8") as handle:
        handle.write(item["srt"])
```

#### JavaScript

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });

const run = await client.actor('sauliusautomatesit/instagram-reels-transcript').call({
    videoUrls: ['https://www.instagram.com/reel/Dcj6uM5CWIE/'],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();

for (const item of items) {
    if (item.status !== 'ok') {
        console.warn(`${item.inputUrl}: ${item.error}`);
        continue;
    }
    console.log(`@${item.author}: ${item.text}`);
    // await fs.writeFile(`${item.videoId}.srt`, item.srt);
}
```

#### n8n, Make and Zapier

Works with the standard **Apify → Run Actor** node in n8n and the **Apify → Run an Actor**
module in Make. Point the node at this Actor, pass `videoUrls` as the input JSON, then read the
dataset items in the next step — `text` for the transcript, `srt` for a subtitle file, `error`
to branch on failures.

A common shape: a sheet or webhook supplies Reel links → this Actor returns transcripts → an
LLM node summarises, tags or repurposes them → the result is written back. Because failures
come back as rows with an `error` field rather than as a broken run, a filter on
`status == "ok"` is all the error handling most workflows need.

***

### Errors

Failed Reels get a dataset row with `status: "error"`, an `error` code and a plain-English
`errorMessage`. They are never charged.

| Code | Meaning |
|---|---|
| `VIDEO_UNAVAILABLE` | Deleted, never existed, or not reachable without signing in. |
| `VIDEO_PRIVATE` | The account is private. |
| `LOGIN_REQUIRED` | Instagram demanded a signed-in session for this Reel. |
| `DURATION_EXCEEDED` | Longer than `maxDurationSeconds`. |
| `ASR_DURATION_EXCEEDED` | Longer than `maxAsrDurationSeconds`. Raise that limit to transcribe it anyway. |
| `NO_SPEECH_DETECTED` | Whisper found no speech — a music-only or silent Reel. |
| `CAPTIONS_UNAVAILABLE` | Whisper was turned off, and Instagram has no caption track. |
| `MEDIA_UNAVAILABLE` | Not a video (a photo post), or the audio could not be downloaded. |
| `INVALID_URL` | Not a link to a single Reel — a profile or explore URL, for example. |
| `PLATFORM_NOT_ENABLED` | A TikTok, YouTube or other non-Instagram URL. |
| `BLOCKED` / `RATE_LIMITED` | Instagram refused the request. Try again, and keep the residential retry on. |
| `MEMORY_LIMIT` | The Reel does not fit in the run's memory for the chosen model. Raise the memory or pick a smaller model. |

***

### Good to know

- **Memory.** Run with at least 2 GB. That covers the whole duration range with the `base`
  model; the Actor refuses an impossible combination up front instead of being killed mid-run.
- **Accuracy.** `base` is a good default. For accented speech, background music or poor
  recording, `small` is a clear improvement at roughly double the transcription time.
- **One URL per Reel.** This Actor does not crawl accounts or hashtags to find Reels for you.

### What this Actor does not do

It transcribes Reels. It does not scrape comments, followers or account analytics, does not
monitor accounts, does not touch private content, and does not summarise, translate or score
sentiment — pipe the transcript into an LLM step for that.

***

### Related searches

Instagram Reels transcript · Reel to text · transcribe Instagram Reel · Instagram subtitles ·
Reels SRT · Instagram video to text · Reel captions to text · Instagram speech to text ·
bulk Reels transcripts · Instagram Reels VTT · Reels transcript API

# Actor input Schema

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

Public Instagram Reel URLs to transcribe — one per line, from a single link to thousands. `instagram.com/reel/CODE/`, `instagram.com/reels/CODE/`, `instagram.com/USER/reel/CODE/`, `/p/CODE/` and `/tv/CODE/` all work. Profile and explore URLs are not single Reels and will be reported as errors.

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

Two-letter language code (`en`, `es`, `pt`, `ar`, `fr`…). Leave blank to let Whisper detect the language — that is the default and it is usually right. Setting it helps on short or noisy Reels where detection can pick the wrong language.

## `allowWhisperFallback` (type: `boolean`):

On (default), and it needs to stay on. Instagram publishes no caption track to a logged-out viewer, so Whisper speech-to-text is the only way a Reel becomes text — every successful row is charged `asr_transcript`. Turning this off makes every Reel return a free `CAPTIONS_UNAVAILABLE` error, which is useful only as a way to stop the Actor spending anything.

## `maxDurationSeconds` (type: `integer`):

Reels longer than this are skipped with a `DURATION_EXCEEDED` error and are not charged.

## `maxAsrDurationSeconds` (type: `integer`):

Whisper costs roughly a second of CPU per 1.5 seconds of audio, so a per-video price only works up to a point; past this length a Reel returns `ASR_DURATION_EXCEEDED` and costs nothing. The default of 180 s covers the great majority of Reels. Instagram does not tell an anonymous viewer how long a Reel is, so this is enforced from the downloaded audio's own header — before transcription starts, and before anything is charged.

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

Safety cap on how many URLs from the list are processed.

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

Accuracy vs speed. `base` is the recommended balance; `small` is noticeably better on accented speech and noisy audio, and noticeably slower.

## `beamSize` (type: `integer`):

Decoding beam width. 1 is fastest; higher is marginally more accurate and slower.

## `concurrency` (type: `integer`):

How many Reels to resolve at once. Whisper transcription is always serialised regardless of this setting, to keep the run inside its memory limit.

## `includeFailedItems` (type: `boolean`):

On (default): unavailable, private, too-long and non-Instagram URLs get a dataset row carrying an `error` code, so you can reconcile inputs to outputs. Failed rows are never charged.

## `useResidentialFallback` (type: `boolean`):

On (default), and worth leaving on. Instagram refuses far more datacenter addresses than the other short-form platforms do; when it answers the normal proxy with a bot check or a login wall, that one request is retried through Apify's residential proxy. Only requests the cheap route already refused use it.

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

Proxy used to reach Instagram. Apify Proxy is recommended: the run pins one proxy session so that a Reel's media file is fetched from the same IP that resolved it, which is what Instagram's CDN requires.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.instagram.com/reel/Dcj6uM5CWIE/",
    "https://www.instagram.com/reel/DcCH2ZygIiP/"
  ],
  "allowWhisperFallback": true,
  "maxDurationSeconds": 600,
  "maxAsrDurationSeconds": 180,
  "maxVideos": 1000,
  "whisperModel": "base",
  "beamSize": 1,
  "concurrency": 5,
  "includeFailedItems": true,
  "useResidentialFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every transcribed Reel, including rows for the ones that failed.

# 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.instagram.com/reel/Dcj6uM5CWIE/",
        "https://www.instagram.com/reel/DcCH2ZygIiP/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sauliusautomatesit/instagram-reels-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.instagram.com/reel/Dcj6uM5CWIE/",
        "https://www.instagram.com/reel/DcCH2ZygIiP/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sauliusautomatesit/instagram-reels-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.instagram.com/reel/Dcj6uM5CWIE/",
    "https://www.instagram.com/reel/DcCH2ZygIiP/"
  ]
}' |
apify call sauliusautomatesit/instagram-reels-transcript --silent --output-dataset

```

## MCP server setup

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