# Instagram Transcript Scraper (`dami_studio/instagram-transcript-scraper`) Actor

Turn Instagram reel and video links into the spoken transcript: full text, timestamps, SRT and WebVTT, detected language, plus the caption, author, duration and engagement counts. Flat per-transcript price with no per-minute fee. No account, no cookies, no browser.

- **URL**: https://apify.com/dami\_studio/instagram-transcript-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Videos, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 transcript scrapeds

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

## Instagram Transcript Scraper

Paste Instagram reel, video or post links and get back what was actually said in them: the full spoken transcript as text, the detected language, and — depending on which speech-to-text endpoint you point it at — per-cue timestamps with ready-made SRT and WebVTT subtitle files. Every row also carries the post's own caption, hashtags, author, duration, publication date and engagement counts, so the transcript arrives with its context attached. No browser, and no Instagram set-up on your side.

- Takes reel links, /p/ and /tv/ links, instagram.com/share/ links, or bare shortcodes — mixed together in one list.
- Reads the audio track on its own rather than the whole video, so a long reel costs a few hundred kilobytes instead of several megabytes.
- Speech-to-text runs on YOUR provider account, at your rate: OpenAI, Groq, Deepgram, an audio-capable model behind any OpenAI-compatible gateway, or your own self-hosted Whisper server.
- Reels with no speech in them are reported for free, not billed as empty transcripts.
- Runs with empty input return a labelled sample row, free, so you can see the shape before you spend anything.
- Nothing to configure on the Instagram side. Posts are looked up without an account, and a built-in session covers the occasional post Instagram will not hand to a logged-out reader.

### Price

**$2.50 per 1,000 transcripts**, plus a **$0.0005 start fee per run** (billed per gigabyte of run memory, so exactly that on the default 1 GB).

This is a flat rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.

| Transcripts | Total cost |
|---|---|
| 100 | $0.2505 |
| 1,000 | $2.5005 |
| 10,000 | $25.0005 |
| 100,000 | $250.0005 |

#### What is actually charged

- **One `transcript-scraped` event per transcript row written to the dataset.** Nothing else is metered per row.
- **Free:** the sample row an empty run returns, and every diagnostic row — a blocked target, a dead URL, a search that matched nothing. Those rows all carry `"charged": false`.
- Posts that turn out to contain no speech — music-only, ambient or silent clips — are reported as free notes rather than charged as blank rows.
- Photo posts, carousels, dead links and private accounts come back as free notes naming the post.
- Videos longer than your `maxDurationSeconds` limit are skipped for free, before any audio is downloaded.
- The same reel pasted twice in one run is fetched once and charged once.
- A run that finds nothing costs the start fee and nothing more.
- Rows never leave the dataset without a charge, and are never charged without a row. The billed event is a named one, so there is no price quietly attached to `apify-default-dataset-item` — the trick that makes some scrapers bill you for their own error messages.

### Input

```json
{
  "postUrls": [
    "https://www.instagram.com/reel/DcBsBkPOtmB/",
    "https://www.instagram.com/reel/DcD_ysfuq9B/"
  ],
  "transcriptionApiKey": "sk-your-own-speech-to-text-key",
  "transcriptionBaseUrl": "https://api.openai.com/v1",
  "transcriptionModel": "whisper-1",
  "maxItems": 10
}
```

| Field | What it does |
|---|---|
| `postUrls` | The posts you want transcribed. Full reel links, /p/ or /tv/ links, instagram.com/share/ links, or bare shortcodes — mix them freely. Up to 1,000 per run. |
| `transcriptionApiKey` | Your own speech-to-text key. Instagram does not publish a transcript of its own, so the audio has to be listened to by something; this Actor uses your account for that instead of marking up a per-minute fee. The key is used for the requests and nothing else — never stored, never logged, never written to the dataset. |
| `transcriptionBaseUrl` | The OpenAI-compatible base URL your key belongs to. Defaults to OpenAI. Groq, Deepgram's compatibility endpoint, an OpenAI-compatible gateway in front of an audio-capable model, and self-hosted Whisper servers all work. |
| `transcriptionModel` | The model name your provider expects — `whisper-1` on OpenAI, `whisper-large-v3-turbo` on Groq, or whatever your gateway exposes. |
| `maxItems` | How many transcripts to return at most. The list is read in order and the run stops as soon as it has this many, so links past that point cost you nothing. Default 10. |
| `language` | Optional ISO language code passed to your provider as a hint. Leave empty to let it detect the language, which it usually gets right. |
| `maxDurationSeconds` | Videos longer than this are skipped with a free note instead of transcribed. Long videos cost you real money at your own provider, so this makes that a decision rather than a surprise. Default 900. |
| `transcriptionMode` | Leave on auto. It tries the `/audio/transcriptions` endpoint first — the one that returns per-cue timestamps — and falls back to `/chat/completions` with an audio part when the gateway does not implement it. |
| `transcriptionChatModel` | Only needed when the chat fallback uses a differently-named model from the transcription one. |
| `concurrency` | How many posts to work through in parallel. Default 5, maximum 15. Lower it if your speech-to-text provider rate-limits you. |
| `sessionCookies` | Leave empty — and note this is an Instagram cookie, not the speech-to-text key. Posts are looked up without an account, with a built-in session covering the ones that need it. That session is shared, so on a long list under heavy load some posts can come back blocked; putting your own Instagram cookie here gives the run a rate limit nobody else shares. Paste it as `sessionid=...; csrftoken=...` — Chrome: F12 → Application → Cookies → instagram.com. It is used for this run's requests and nothing else. |
| `proxyUrls` | Leave empty. Fill it in only if you want the Instagram traffic to leave through proxy servers you already pay for, as `http://user:pass@host:port`. |

Run it with **empty input** and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.

### Output

One row per transcript. A real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "transcript",
  "shortcode": "DcBsBkPOtmB",
  "url": "https://www.instagram.com/reel/DcBsBkPOtmB/",
  "inputUrl": "https://www.instagram.com/reel/DcBsBkPOtmB/",
  "authorUsername": "veereceipts",
  "authorName": "GaryVee Receipts",
  "authorUrl": "https://www.instagram.com/veereceipts/",
  "authorIsVerified": false,
  "caption": "Big picture always wins. And I guarantee you, when you make legacy decisions instead of quick cash decisions, you’re gonna win every time.",
  "hashtags": [],
  "durationSeconds": 78.13,
  "createdAt": "2026-08-14T15:59:58.000Z",
  "productType": "clips",
  "language": "en",
  "text": "Every one of them. You can't skip. Has to be about your long-term legacy. If you're a very hard worker and you're putting everything on the line, put it into your own personal brand. Just make rent, pay your bills, and keep building your personal brand and always think of the big picture. The legacy. Who are you going to be viewed at when it's all said and done?",
  "wordCount": 250,
  "characterCount": 1361,
  "segmentCount": 0,
  "segments": [],
  "srt": null,
  "vtt": null,
  "transcriptSource": "speech-to-text",
  "audioSource": "audio track",
  "audioBytes": 472064,
  "mediaSource": "graphql",
  "viewCount": null,
  "likeCount": 1893,
  "commentCount": 47,
  "musicTitle": "Original audio",
  "coverUrl": "https://scontent-ord5-1.cdninstagram.com/v/t51.82787-15/773662926_18627161911033297.jpg",
  "scrapedAt": "2026-08-16T23:12:11.018Z"
}
```

#### Field notes

- `text` — the whole transcript as one string. This is the field most people want.
- `segments` — per-cue timing — `start`, `end`, `startTime`, `endTime`, `text`. Present when your endpoint is a real transcription endpoint that returns verbose JSON; an audio-capable chat model returns the words without timings, and then this is an empty array.
- `srt` — a ready-to-use SubRip subtitle file, built from `segments`. Null when there are no timings.
- `vtt` — the same cues as WebVTT. Null when there are no timings.
- `language` — the language your speech-to-text provider detected, as an ISO code. Null if it did not report one.
- `caption` — the text the creator typed under the post. This is NOT the transcript — it is what Instagram calls the caption.
- `durationSeconds` — read from the media manifest, accurate to hundredths of a second.
- `viewCount` — Instagram withholds play counts from logged-out callers, so in practice this is null on reels. It is kept in the row for the posts where Instagram does expose it. `likeCount` and `commentCount` are reliably present.
- `audioBytes` — how much audio was actually downloaded for the row — useful if you are estimating your own speech-to-text spend.
- `transcriptSource` — "speech-to-text (timestamped)" when cues came back, "speech-to-text" when only the text did.
- `charged` — true on real transcript rows, false on sample and diagnostic rows. Filter on it if you want only what you paid for.

Every real row carries `"charged": true`. Sample rows carry `"_sample": true` and diagnostic rows carry `"_diagnostic": true` with an `errorCode` you can filter on, and neither is ever billed.

### How it works

- The post link is turned into a shortcode, and the public logged-out post payload is read for the media, caption, author and counts.
- That lookup is tried logged-out first for every post, because a post is public and normally answers an anonymous reader straight away. Only when Instagram puts a login wall in front of the payload does the run fall back to a built-in session, so the fallback is reserved for the posts that actually need it.
- From that payload the Actor takes the AUDIO stream on its own — not the video — which is a fraction of the bytes and all that speech-to-text needs.
- The audio goes straight to the speech-to-text endpoint you nominated, on your key. The transcript comes back, gets shaped into a row with the post metadata, and is written to the dataset.
- When Instagram changes the shape of its public post payload, the Actor falls back to the public embed page for the same post, which still yields a complete transcript.
- Requests leave through a large rotating pool of addresses, so a rate-limited address is replaced rather than retried into the ground.

### What people use it for

- Content research: pull the actual words out of a creator's reels to see how they open, how they structure a hook and what they repeat.
- Repurposing: turn a reel into a blog post, a newsletter section or a script outline without watching it twice.
- Accessibility and subtitling: use the SRT or WebVTT output to caption reposts of your own videos.
- Search and archiving: keep a searchable text record of everything a set of accounts has posted, so you can grep a year of video.
- Competitive and market research on what is being said in a niche, at a scale nobody can watch manually.

### Why you bring your own speech-to-text key

Instagram does not publish transcripts. It publishes the video, the audio and the caption the creator typed — and nothing that contains the spoken words. This was checked exhaustively before this Actor was written: dozens of reels from a dozen large public accounts, every field of every media payload walked for a subtitle or transcript track, plus the streaming manifests, which carry a video track and an audio track and no text track at all.

So the only honest way to produce a transcript is to listen to the audio, and listening to audio costs money wherever it happens. This Actor gives you that cost at wholesale: it does the part that is hard to find and cheap to run — resolving the post, pulling the audio stream on its own, shaping the result — and lets you pay your own speech-to-text provider directly for the part that is metered. You choose the provider, you see the invoice, and you can switch to a cheaper or more accurate one whenever you like without changing anything here.

Any OpenAI-compatible endpoint works. If your gateway exposes `/audio/transcriptions` you also get per-cue timestamps and subtitle files; if it only exposes an audio-capable chat model, you get the full text without timings.

### What a run costs you end to end

Two separate bills, and both are small. This Actor charges its flat per-transcript rate plus the start fee, and nothing else — no per-minute fee, no charge for length, no charge for posts that turn out to have no speech in them. Your speech-to-text provider charges you their own rate for the audio minutes, billed to you by them.

Because this Actor never charges by duration, a fifteen-minute video costs exactly the same here as a fifteen-second one. That is the opposite of how transcript pricing usually works, and it is worth knowing if your list is long-form.

### Limitations

- A speech-to-text key is required. Without one, a run returns the free sample row and stops — it will not silently produce empty transcripts.
- Instagram publishes no transcript of its own, so every transcript here is machine-generated from the audio and inherits your provider's accuracy, including on names, jargon, heavy accents and overlapping speech.
- Per-cue timestamps, SRT and WebVTT only appear when your endpoint implements `/audio/transcriptions` and returns verbose JSON. An audio-capable chat model returns the words without timings.
- Only public posts. Private accounts, deleted posts and posts behind an age or region restriction come back as free diagnostic notes.
- Photo posts and carousels have no audio and are reported as free notes rather than transcribed.
- Instagram does not give play counts to logged-out callers, so `viewCount` is null on reels in practice. Likes and comments are reliably present, and were on every row of the run this sample came from.
- Videos longer than `maxDurationSeconds` (default 900) are skipped rather than transcribed, because the audio minutes are billed to you by your provider. Raise it deliberately.
- Audio above roughly 24 MB is refused, because the common speech-to-text endpoints refuse it too. That is around two hours of reel audio.
- Instagram rate-limits aggressively. The Actor rotates addresses and retries, and reports a blocked post as a free diagnostic row rather than pretending it was silent.
- The built-in session behind the walled posts is shared, and Instagram rate-limits per account rather than per caller, so it is not unlimited throughput. Under heavy concurrent load supply your own cookie in `sessionCookies` for a rate limit that is yours alone. If it runs out mid-run, the run finishes with the transcripts it produced and says so on its status line rather than failing.

### Questions

**Do I need an Instagram account or cookies?**

No. Runs work out of the box with only your speech-to-text key filled in. Posts are looked up the way a logged-out visitor reads them, and the occasional walled post falls back to a built-in session. Nothing is ever posted, liked, followed or changed.

**Then what is `sessionCookies` for?**

A private rate limit on the Instagram side — it is not the speech-to-text key. The built-in session is shared between runs, and Instagram counts requests per account rather than per caller, so a busy hour is a slower hour for everyone using it. Paste your own Instagram cookie into `sessionCookies` and this run uses only that — its own quota, nobody else's. Like your API key it is used for the run's requests and nothing else: never stored, never logged, never written into the dataset. Optional, and most runs never need it.

**Which speech-to-text providers work?**

Anything speaking the OpenAI shape: OpenAI itself, Groq, Deepgram's OpenAI-compatible endpoint, most OpenAI-compatible gateways in front of an audio-capable model, and self-hosted Whisper or faster-whisper servers. Set the base URL and the model name to match.

**Is my API key safe?**

It is read from the run input, used to make the transcription requests, and never stored, logged, echoed into the dataset or included in any error message — error text is scrubbed of anything key-shaped before it is written.

**What happens to a reel with no talking in it?**

You get a free `NO_SPEECH` note naming the post, and you are not charged for it.

**Does a longer video cost more here?**

Not here — the per-transcript rate is flat regardless of duration. Your own speech-to-text provider will charge you more for more audio.

**Can I get subtitles I can drop straight into a video editor?**

Yes, when your endpoint returns timings: the `srt` and `vtt` fields are complete files, ready to save as `.srt` or `.vtt`.

**Why did a post come back as NOT\_FOUND?**

The link is wrong, the post was deleted, or the account is private. All three are free.

# Actor input Schema

## `postUrls` (type: `array`):

The posts you want transcribed. A full link (https://www.instagram.com/reel/DcBsBkPOtmB/), a /p/ or /tv/ link, an instagram.com/share/ link, or just the bare shortcode - all work, and you can mix them in one list. Up to 1,000 per run.

## `transcriptionApiKey` (type: `string`):

The key for YOUR speech-to-text account - OpenAI, Groq, Deepgram's OpenAI-compatible endpoint, an audio-capable model behind an OpenAI-compatible gateway, or your own self-hosted Whisper server. Instagram does not publish transcripts, so the audio has to be listened to by something; using your key means you pay your provider's rate directly instead of a marked-up per-minute fee here. The key is used for the requests and nothing else - it is never stored, logged or written to the dataset.

## `transcriptionBaseUrl` (type: `string`):

The OpenAI-compatible base URL your key belongs to. Leave as is for OpenAI. Examples: https://api.groq.com/openai/v1, https://api.deepgram.com/v1/openai, or your own server.

## `transcriptionModel` (type: `string`):

The model name your provider expects. whisper-1 for OpenAI, whisper-large-v3-turbo for Groq, or the name of any audio-capable model your gateway exposes.

## `maxItems` (type: `integer`):

How many transcripts to return at most. The list is read in order and the run stops as soon as it has this many, so links past that point cost you nothing. Keep it low while you are testing - you pay per transcript.

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

A language code such as es, fr, de or ja, passed to your speech-to-text provider as a hint. Leave empty to let it detect the language itself, which is usually right.

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

Videos longer than this are skipped with a free note rather than transcribed. Long videos cost you real money at your speech-to-text provider, so the limit is here to make that a decision rather than a surprise. Default 900 seconds (15 minutes).

## `transcriptionMode` (type: `string`):

Leave on auto. auto tries the /audio/transcriptions endpoint first (the one that returns per-cue timestamps) and falls back to /chat/completions with an audio part if your gateway does not implement it. Force one shape with transcriptions or chat.

## `transcriptionChatModel` (type: `string`):

Only used when the run falls back to /chat/completions and your audio-capable chat model has a different name from the transcription model above.

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

How many posts to work through in parallel. Default 5, maximum 15. Lower it if your speech-to-text provider rate-limits you.

## `sessionCookies` (type: `array`):

Leave this empty. This is not the speech-to-text key — it is an Instagram cookie, and it is optional. Posts are looked up without an account; a built-in session is only used for the occasional post Instagram will not hand to a logged-out reader. That built-in session is shared between runs, so if you are transcribing a long list and posts come back blocked, paste your own Instagram cookie here and this run gets a rate limit nobody else shares. In Chrome: F12 → Application → Cookies → instagram.com. The `sessionid` cookie is the one that matters; `csrftoken` alongside it is better. Paste it as `sessionid=...; csrftoken=...`, one entry per account. Cookies are used for this run's requests and nothing else — never stored, never logged, never written to the dataset.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "postUrls": [
    "https://www.instagram.com/reel/DcBsBkPOtmB/",
    "https://www.instagram.com/reel/DcD_ysfuq9B/"
  ],
  "transcriptionBaseUrl": "https://api.openai.com/v1",
  "transcriptionModel": "whisper-1",
  "maxItems": 10,
  "language": "es"
}
```

# Actor output Schema

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

Every row in the default dataset: shortcode, authorUsername, caption, durationSeconds, language, segmentCount, wordCount, text, url, createdAt, likeCount, inputUrl, authorName, authorUrl, authorIsVerified, hashtags, characterCount, segments, srt, vtt, transcriptSource, audioSource, audioBytes, mediaSource, productType, commentCount, viewCount, musicTitle, coverUrl, scrapedAt. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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 = {
    "postUrls": [
        "https://www.instagram.com/reel/DcBsBkPOtmB/",
        "https://www.instagram.com/reel/DcD_ysfuq9B/"
    ],
    "transcriptionBaseUrl": "https://api.openai.com/v1",
    "transcriptionModel": "whisper-1",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/instagram-transcript-scraper").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 = {
    "postUrls": [
        "https://www.instagram.com/reel/DcBsBkPOtmB/",
        "https://www.instagram.com/reel/DcD_ysfuq9B/",
    ],
    "transcriptionBaseUrl": "https://api.openai.com/v1",
    "transcriptionModel": "whisper-1",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/instagram-transcript-scraper").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 '{
  "postUrls": [
    "https://www.instagram.com/reel/DcBsBkPOtmB/",
    "https://www.instagram.com/reel/DcD_ysfuq9B/"
  ],
  "transcriptionBaseUrl": "https://api.openai.com/v1",
  "transcriptionModel": "whisper-1",
  "maxItems": 10
}' |
apify call dami_studio/instagram-transcript-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/u3o1Chneoh6vxYqj2/builds/hgaAODocoYS2h4BpI/openapi.json
