# Instagram Reel Transcript Scraper — Speech to Text, No Login (`scrapersdelight/instagram-transcript-scraper`) Actor

Get the transcript of Instagram Reels by URL or username: spoken words (faster-whisper, on-actor), caption, hashtags, views, likes, comments, duration, owner, music, mp4 URL. No login, no cookies, no crawler spoofing. Music-only reels are never billed for transcription.

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

## Pricing

from $2.00 / 1,000 per reel returneds

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?

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 Reel Transcript Scraper — Speech to Text, No Login

Turn public [Instagram](https://www.instagram.com) Reels into text. One row per reel: the
**transcript** of what is actually said (faster-whisper, run on this Actor's own compute — no
API key), the spoken **hook** (first 3 seconds), **transcriptLanguage, transcriptWordCount,
speechSeconds**, plus **caption, hashtags, mentions, viewCount, likeCount, commentCount,
durationSeconds, takenAt, ownerUsername, ownerVerified, musicTitle, musicArtist,
usesOriginalAudio, videoUrl, audioUrl** and **thumbnailUrl**. Feed it reel URLs or usernames.
**No login. No cookies. No CAPTCHA solver, no impersonated crawler.** On the embed page the
Actor sends its own identifying User-Agent (`scrapersdelight-instagram-transcript/1.0`); the
profile-feed call is the logged-out web client's own request and sends the ordinary
desktop-browser User-Agent and public `x-ig-app-id` that call requires.

**Every reel is transcribed on the Actor, and a reel with no speech is never billed for a
transcript.** Measured on the shipped sample: of 5 @nasa videos, 2 carried narration and were
transcribed (166 and 86 words), 3 were music/ambient and came back `transcriptStatus:
"no-speech"` at the row price only. A 60-second narrated reel costs **$0.008 all-in**
($0.002 row + $0.006 transcript-minute) — about half of the next cookieless on-actor transcript
Actor ($0.015, makework36) and a sixth of the house Instagram reel scraper's $0.048 transcript
(store prices, checked 2026-08-22).

```json
{
  "usernames": ["nasa"],
  "maxReelsPerUser": 5,
  "maxItems": 5
}
```

Click **Try for free** and hit **Start** — that block is the input the Actor ships with. A
bare `{}` runs the same sample. See *Field fill* and *Sample row* below for what the shipped
default returned on the platform.

***

### The wedge: transcripts priced like rows, not like an API

Most Instagram transcript Actors on the Store are one of three things: a browser plus an
optional `sessionid` cookie (your account is the thing at risk), a reseller of a third-party
transcription API (you pay their margin on every minute), or a metadata scraper that hands you
the caption and calls it a transcript. This Actor is none of those:

- **Logged-out public surfaces only.** Two Instagram surfaces, both public: the oEmbed page
  every embedded reel loads, and the profile-feed JSON the logged-out web client fetches.
  Measured 2026-08-22 through Apify datacenter IPs: **16/16 live reel pages, 15/15 feed pages
  with contiguous pagination and 0 duplicate shortcodes**. No CAPTCHA anywhere, no login wall
  on either surface.
- **Speech recognition runs inside the Actor** (faster-whisper, int8, model baked into the
  image). The model loads **once per run** (under 4 s; 0.8–3.1 s measured) and is fed every
  reel; audio comes from Instagram's audio-only DASH rendition where the feed publishes one
  (~0.5 MB for a 60 s reel instead of a 3–25 MB mp4), so the per-reel cost is a few
  CPU-seconds.
- **`no-speech` is a first-class result, and it is free.** Whisper's own per-segment signals
  (`no_speech_prob`, `avg_logprob`, `compression_ratio`) plus voice-activity detection decide
  whether a reel had words. A music-only reel returns `transcript: null`,
  `transcriptStatus: "no-speech"`, and the `transcript-minute` event is not charged. You do
  not pay for hallucinated lyrics.
- **`hook`** — what is said in the first 3 seconds — is its own column, because that is the
  field content researchers copy out of every transcript by hand.

***

### Read this before you buy rows

1. **`transcriptStatus` tells you what you got.** Five values: `transcribed` (speech found,
   billed), `no-speech` (music/ambient, row price only), `low-confidence` (words came back,
   but the confidence signals — language probability, words per spoken second — say it is
   probably a music bed or crowd noise rather than speech; the text is still delivered so you
   can judge it, and it is **not** billed a transcript-minute), `failed` (media download or
   ASR error — `transcriptError` says why, row price only), `skipped` (you ran with
   `transcribe: false`). Filter on `transcribed` **or** `low-confidence` if you want every row
   that carries text.
2. **Username mode returns VIDEOS, not every post.** The profile feed mixes photos,
   carousels and videos; photo posts and carousel posts are skipped because there is nothing
   to transcribe. On @nasa, 5 of the first 23 posts were videos. `maxReelsPerUser` counts
   videos, and the Actor pages until it has that many, the feed ends, or it has read about
   three pages per video asked for (a cap that stops a photo-only feed from paging forever).
3. **Collaborations show the real owner.** A reel co-authored with another account appears
   in the feed of both; `ownerUsername` is the account that posted it, which can differ from
   the username you asked for (the sample's @nasa feed returned a @nasajohnson and a
   @spherevegas reel).
4. **URL mode's `takenAt` is approximate.** Instagram's public embed page carries no
   timestamp, so the per-URL route derives one from the media id, which is minted when the
   upload starts — measured 1 to 7 minutes before the real publish time. `takenAtSource`
   says `feed (exact)` or `media-id (approximate, minutes early)` on every row.
5. **`viewCount` is play count.** For reels Instagram publishes plays, not unique viewers.
   `ownerFollowers` is only available on the per-URL route (the feed does not carry it).
6. **Transcript quality is the model's.** `base` (default) is accurate on clear narration;
   use `small` for accents, noisy audio or non-English speech (≈3× the compute time), or set
   `language` explicitly on short clips. Speech buried under a loud music bed can be missed.

***

### What you get

One row per unique reel/video, deduplicated run-wide on `shortcode` before billing.
Timestamps are ISO-8601 UTC; counts are integers; `durationSeconds` is the video length.

| Group | Fields | Example |
|---|---|---|
| **Identity** | `shortcode`, `mediaId`, `url`, `type`, `productType` | `Dbn-XJhk0_-` · `reel` · `clips` |
| **Owner** | `ownerUsername`, `ownerId`, `ownerFullName`, `ownerVerified`, `ownerFollowers` | `nasa` · `NASA` · `true` |
| **Transcript** | `transcript`, `hook`, `transcriptLanguage`, `transcriptLanguageProbability`, `transcriptWordCount`, `speechSeconds`, `transcriptStatus`, `transcriptError`, `audioMinutesBilled` | `"NASA is building a moon base…"` · `en` · `86` · `transcribed` |
| **Optional transcript formats** | `transcriptSegments[]`, `transcriptSrt`, `transcriptVtt` | `{start: 0.3, end: 1.8, text: "Go for launch."}` |
| **Text** | `caption`, `hashtags[]`, `mentions[]`, `accessibilityCaption` | `#Artemis` · `@astro_anil` |
| **Engagement** | `viewCount`, `likeCount`, `commentCount` | `2655226` · `73853` · `586` |
| **Video** | `durationSeconds`, `width`, `height`, `videoUrl`, `audioUrl`, `thumbnailUrl`, `hasAudio` | `50.2` · `1076×1912` |
| **Audio attribution** | `musicTitle`, `musicArtist`, `usesOriginalAudio` | `Original audio` · `nasajohnson` · `true` |
| **Dates & provenance** | `takenAt`, `takenAtSource`, `isPaidPartnership`, `source`, `scrapedAt` | `2026-08-18T19:37:40.000Z` · `feed (exact)` · `username` |

The dataset ships with a **Reel transcripts** table view (account, shortcode, posted, seconds,
status, language, hook, spoken words, word count, caption, views, likes, comments, audio,
link).

***

### Field fill — measured on the shipped sample

The shipped default (`usernames: ["nasa"]`, 5 videos), run on the platform 2026-08-22.
Sorted by fill, so the sparse fields are impossible to miss.

| Field | Fill | Notes |
|---|---|---|
| `shortcode` / `mediaId` / `url` / `type` | 100% | |
| `ownerUsername` / `ownerId` / `ownerVerified` | 100% | |
| `ownerFullName` / `isPaidPartnership` | 100% on the username route · **0%** on the URL route | the embed page payload is not parsed for either |
| `caption` | 100% | |
| `takenAt` / `takenAtSource` | 100% | exact from the feed; approximate on the URL route |
| `durationSeconds` / `width` / `height` | 100% | |
| `viewCount` / `likeCount` / `commentCount` | 100% | |
| `videoUrl` / `audioUrl` / `thumbnailUrl` | 100% | `audioUrl` is the DASH audio-only rendition (feed route only) |
| `musicTitle` / `musicArtist` / `usesOriginalAudio` / `hasAudio` | 100% | |
| `transcriptStatus` / `audioMinutesBilled` | 100% | |
| `transcriptLanguage` / `speechSeconds` | 100% | present on `no-speech` rows too (`speechSeconds: 0`) |
| `hashtags` / `mentions` | 40–60% | only when the caption has any — the shipped sample re-samples @nasa's newest videos, so this moves |
| **`transcript` / `hook` / `transcriptWordCount`** | **40%** | **2 of 5 sample reels had speech — this is the reality of brand feeds, not a gap** |
| **`ownerFollowers`** | **0%** on the username route · 100% on the URL route | the feed JSON does not carry it; the embed page does |
| **`accessibilityCaption`** | **0%** | Instagram rarely publishes it for video |
| `transcriptError` | 0% | only on `failed` rows |

**Every run prints its own counts in the log** — fetches by rung, residential fallbacks used,
megabytes pulled, ASR seconds, transcribed / no-speech / failed.

***

### How to run it

#### 1. Reel URLs — transcribe specific reels

```json
{
  "reelUrls": [
    "https://www.instagram.com/reel/DcCH2ZygIiP/",
    "https://www.instagram.com/p/Dbn-XJhk0_-/",
    "DbDZxP6Cjea"
  ]
}
```

Accepts `/reel/`, `/reels/`, `/p/` and `/tv/` URLs (with or without tracking parameters) or
bare shortcodes. A photo or carousel URL is skipped with a log line, never billed. A private,
removed or copyright-blocked post is reported as unavailable and skipped.

#### 2. Usernames — the newest reels of an account

```json
{
  "usernames": ["natgeo", "redbull"],
  "maxReelsPerUser": 20,
  "postedAfter": "2026-08-01"
}
```

Pages the public profile feed newest-first, 12 posts per page, keeps videos, stops when it has
`maxReelsPerUser` of them, reaches `postedAfter`, the feed ends, or it has read about three
pages per video asked for. Pinned posts are read but
do not stop the date filter. One pinned proxy session per profile keeps pagination on one IP.

#### 3. Metadata only — no transcription

```json
{ "usernames": ["gopro"], "maxReelsPerUser": 50, "transcribe": false }
```

Rows at the row price alone: caption, counts, duration, owner, music, mp4/audio/thumbnail
URLs. `transcriptStatus` is `skipped` and the schema is unchanged.

#### 4. Subtitles and timestamps

```json
{
  "reelUrls": ["https://www.instagram.com/reel/DcCH2ZygIiP/"],
  "transcriptFormats": ["segments", "srt", "vtt"],
  "model": "small",
  "language": "en"
}
```

Adds `transcriptSegments` (start/end/text), `transcriptSrt` and `transcriptVtt`. Plain text
and the hook are always included.

#### Limits and cost controls

`maxItems` is the hard cap on rows (your cost ceiling). `maxAudioMinutesPerReel` (default 5)
stops a long feed video from running up transcript minutes — reels are at most 3 minutes, but
profile feeds can carry hour-long videos. The Actor also pre-checks the run's charge limit
before transcribing each reel, so a `maxTotalChargeUsd` cap ships whole rows and stops
cleanly with a status message.

#### Scheduling and integrations

Save the input as a **Task**, attach an Apify **Schedule** (`0 7 * * *` for a daily pull of
your competitors' new reels with `postedAfter` set to yesterday), and read the dataset over
the API, Zapier, Make, n8n, webhooks or MCP. Starting a run from the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~instagram-transcript-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"usernames":["nasa"],"maxReelsPerUser":10}'
```

***

### Sample row

A real row from the shipped sample, captured on the platform 2026-08-22 (caption and
transcript truncated here; full length in the dataset).

```jsonc
{
  "shortcode": "Dbn-XJhk0_-",
  "mediaId": "3956405067326902270",
  "url": "https://www.instagram.com/reel/Dbn-XJhk0_-/",
  "type": "reel",
  "productType": "clips",
  "ownerUsername": "nasa",
  "ownerId": "528817151",
  "ownerFullName": "NASA",
  "ownerVerified": true,
  "ownerFollowers": null,
  "caption": "Lunar landers, assemble.\n \nIn clean rooms across the country, the next generation of commercial lunar landers is coming together. …",
  "hashtags": ["nasa", "moon", "exploration", "science"],
  "mentions": ["blueorigin", "fireflyaerospace", "intuitivemachines", "voyagertech_"],
  "takenAt": "2026-08-04T16:18:15.000Z",
  "takenAtSource": "feed (exact)",
  "durationSeconds": 32.45,
  "viewCount": 8742862,
  "likeCount": 257578,
  "commentCount": 2654,
  "width": 1080,
  "height": 1920,
  "videoUrl": "https://scontent-….cdninstagram.com/o1/v/t2/f2/m86/….mp4?…",
  "audioUrl": "https://scontent-….cdninstagram.com/o1/v/t2/f2/m78/….mp4?…",
  "thumbnailUrl": "https://scontent-….cdninstagram.com/v/t51.82787-15/….jpg?…",
  "musicTitle": "Original audio",
  "musicArtist": "nasa",
  "usesOriginalAudio": true,
  "hasAudio": true,
  "isPaidPartnership": false,
  "accessibilityCaption": null,
  "source": "username",

  "transcript": "NASA is building a moon base, a place where astronauts will live, work, and conduct science …",
  "hook": "NASA is building a moon base, a place where astronauts will live, work, and conduct science",
  "transcriptLanguage": "en",
  "transcriptLanguageProbability": 0.997,
  "transcriptWordCount": 86,
  "speechSeconds": 31.24,
  "transcriptStatus": "transcribed",
  "transcriptError": null,
  "audioMinutesBilled": 1,
  "scrapedAt": "2026-08-22T17:38:03.225Z"
}
```

Fields people misread:

- `audioMinutesBilled` is the number of `transcript-minute` events this row cost — `0` on
  `no-speech`, `failed` and `skipped` rows. It is `ceil(audio seconds / 60)`, so a 61-second
  reel is 2.
- `speechSeconds` is how much of the audio Whisper judged to be speech, which is usually less
  than `durationSeconds`.
- `hook` is everything said before the 3-second mark, not the first sentence.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `reelUrls` | string list | `[]` | Reel/post URLs or shortcodes. Photo/carousel posts skipped. |
| `usernames` | string list | `[]` | Public usernames or profile URLs; newest videos first. |
| `maxReelsPerUser` | integer | `10` | Videos per username (pages until it has that many). |
| `postedAfter` | date | — | Stop at older posts (`YYYY-MM-DD`). |
| `maxItems` | integer | `50` | **Hard row cap for the run.** |
| `transcribe` | boolean | `true` | Off = metadata-only rows at the row price. |
| `model` | select | `base` | `tiny` (fastest) · `base` (recommended) · `small` (best accuracy, ~3× slower). |
| `language` | string | `auto` | ISO-639-1 code to force, or auto-detect per reel. |
| `transcriptFormats` | select list | `[]` | Add `segments`, `srt`, `vtt`. Text + hook always included. |
| `maxAudioMinutesPerReel` | integer | `5` | Audio past this is not transcribed or billed. |
| `proxyConfiguration` | proxy | Apify datacenter | For Instagram page/API calls. Media bytes never go through a proxy. |
| `requestDelayMs` | integer | `500` | Pause between Instagram requests. |

***

### Pricing

Two pay-per-event prices, no subscription, no platform fee from this Actor.

| Event | Price | When |
|---|---|---|
| `reel-scraped` | **$0.002 per reel row** | every unique reel returned (deduplicated first) |
| `transcript-minute` | **$0.006 per started audio-minute** | **only when spoken words were found** |

| Run | Cost |
|---|---|
| The shipped sample (5 @nasa videos, 2 with speech — one of them 77 s, so 3 audio-minutes) | **$0.028** |
| 100 narrated reels ≤ 60 s | **$0.80** |
| 100 reels, metadata only | **$0.20** |
| 1,000 narrated reels ≤ 60 s | **$8.00** |

- A reel that has no speech, fails to download, or fails in ASR bills the **row only**.
- Rows are pushed and charged one at a time (`Actor.pushData(row, 'reel-scraped')`, then the
  transcript minutes), and the row event is checked against the run's charge limit before a
  reel is touched, the transcript-minute event before anything is pushed — a capped run stops
  on a whole row and never half-bills one.
- Duplicates (the same reel reached through two usernames or a URL and a username) are
  dropped before billing.

***

### Honest limits

- **Public accounts only.** A private account returns nothing (reported in the log, not
  billed). No login or `sessionid` input exists, on purpose.
- **No native caption track exists on any public Instagram surface**, so every transcript
  here is speech recognition, and its accuracy is the model's (see *Read this* §6). Reels
  whose speech sits under a loud music bed can come back `no-speech`.
- **`ownerFollowers` is `null` on the username route** and `accessibilityCaption` is almost
  always `null` — both are Instagram data gaps, not scraping failures. The mirror image:
  **`ownerFullName` and `isPaidPartnership` are `null` on the URL route** — the embed page
  payload is not parsed for either.
- **A fifth `transcriptStatus`, `low-confidence`, exists.** Words came back but the
  confidence signals say it is probably music or crowd noise. The row still carries
  `transcript`, `hook` and `transcriptWordCount`, and is **not** billed a transcript-minute —
  so a filter on `transcriptStatus == "transcribed"` alone will silently drop those rows.
- **URL-route `takenAt` is approximate** (media-id derived, minutes early). Use username mode
  when you need the exact posting time.
- **Carousel videos are out of scope.** A carousel post is one post containing several media
  items; only standalone videos/reels are returned. Stories and live replays are not reachable
  logged out and are not attempted.
- **Instagram changes its surfaces.** Both surfaces used here have been stable public
  endpoints for years, but a change on Instagram's side would show up as rows dropping to zero
  — the run then exits with a status message naming the failure counts, never a silent empty
  dataset.
- **Media URLs expire.** `videoUrl`/`audioUrl`/`thumbnailUrl` are signed CDN links valid for
  hours, not days. Download what you need promptly.

***

### How it works, and what it cost to make reliable

**Per-URL route:** `GET https://www.instagram.com/p/{shortcode}/embed/captioned/` — the
page Instagram serves to every embedded reel on the web. To clients without JavaScript it is
server-rendered with a `contextJSON` blob carrying `video_url`, the caption, view/like/comment
counts, duration, dimensions, owner and music attribution. This Actor sends its own honest
User-Agent (`scrapersdelight-instagram-transcript/1.0`) and gets that render; it does not
pretend to be Googlebot or any other crawler.

**Username route:** `GET https://www.instagram.com/api/v1/feed/user/{name}/username/?count=12`
with the web client's public `x-ig-app-id` header — the same request the logged-out profile
page makes in your browser — paginated with `max_id`. Each video item carries
`video_versions`, a DASH manifest with an audio-only rendition, caption, counts, duration,
`taken_at`, owner and clip/music metadata.

**Audio → text:** the smallest audio rendition is downloaded without a proxy, ffmpeg converts
it to 16 kHz mono PCM (capped at `maxAudioMinutesPerReel`), and a single faster-whisper
process (int8, model pre-baked in the image) transcribes every reel of the run. Segments
Whisper flags as non-speech, low-confidence or repetitive are dropped; a reel with nothing left
is `no-speech`.

**Transport ladder, measured 2026-08-22 through Apify** (17 shortcodes, 5 profiles × 3
contiguous pages, fresh sessions per call):

| Rung | Embed page (per-URL) | Profile feed (username) | CDN media |
|---|---|---|---|
| Direct, no proxy (reported, then ignored — no container has a home IP) | 16/17 | 12/13 (the direct walk was cut short after 13 pages) | 3/3 |
| **Apify datacenter (default)** | **16/17** | **15/15** | **3/3** |
| Apify RESIDENTIAL + US | 16/17 | 15/15 | 3/3 |

The one embed miss is the same shortcode on every rung — a deleted post, not a block; on the
16 live posts every rung is 16/16. The one direct feed miss was an empty page from the home
IP that both proxy rungs served in full. Pagination returned **0 duplicate shortcodes across
the 15 pages**. No anti-bot vendor sits in front of either surface (no Cloudflare, no
DataDome, no CAPTCHA) — Instagram rate-limits by IP, which is why the Actor paces itself
(`requestDelayMs`), pins one session per profile, and falls back to **RESIDENTIAL for a capped
number of retries** (20% of the input size, minimum 3 per run) only when a datacenter call
comes back blocked. A clean run never pays residential prices, and media bytes never do.

**What is deliberately not used:** `web_profile_info` (429-rate-limited on every proxy rung),
`/graphql/query` (needs page-minted tokens), `/api/v1/media/{id}/info` (login redirect),
`?__a=1` (gone). Instagram's `robots.txt` ends with `User-agent: *` / `Disallow: /`, which is
the rule that applies to this Actor; it reads only the two public surfaces above, at a gentle
pace, and leaves every logged-in surface alone.

***

### Duplicates

Measured on the 15-page ladder walk: **0 duplicate shortcodes within a profile across
contiguous pages**. Across inputs, the same reel can arrive twice — through two co-author
feeds, or as a URL and again through its owner's feed — and the Actor keeps a run-wide set
keyed on `shortcode`, drops the repeat **before** any media is downloaded or billed, and
**asserts uniqueness across everything it pushed** before the run may succeed. The log prints
the exact duplicate count dropped.

***

### When a run fails

- **0 rows** → the run exits with a status message naming the cause (blocked requests,
  unavailable posts, non-video URLs, private account) and the counts, never a silent success
  with nothing in it.
- **A reel fails to download or transcribe** → the row is still delivered with
  `transcriptStatus: "failed"` and `transcriptError`, billed at the row price only.
- **The transcriber cannot start** → rows are delivered metadata-only with
  `transcriptStatus: "failed"`; nothing is billed for transcription.
- **Run time limit** → the Actor stops before starting a reel it cannot finish, delivers
  everything complete, and says how many reels were not processed.
- **Charge limit** → same, on a whole-row boundary.
- **Duplicate leak** → the uniqueness assertion fails the run rather than shipping a dirty
  dataset.

***

### Who buys this

- **Content and social teams** mining competitor and creator reels for hooks, scripts and
  talking points — `hook` and `transcript` are the columns, `viewCount` ranks them.
- **Agencies and creator tools** repurposing short-form video into blogs, newsletters, threads
  and carousels.
- **Brand and influencer monitoring** — what sponsored creators actually *say*, not just what
  the caption claims; `isPaidPartnership` and `mentions` flag the disclosures.
- **Accessibility and localisation** teams generating captions (`srt`/`vtt`) for reels that
  ship without them.
- **AI / RAG pipelines** that need spoken-word text from Instagram at per-row prices.

***

### FAQ

**Do I need an Instagram account, login or cookies?**
No. There is no `sessionid` field and the Actor never sends one. Everything read is a
logged-out public surface.

**Does it pretend to be Googlebot to get the data?**
No. On the embed page it sends its own identifying User-Agent
(`scrapersdelight-instagram-transcript/1.0`) — Instagram serves that page server-rendered to
any non-JavaScript client. The profile-feed call is the logged-out web client's own request,
so it sends the ordinary desktop-browser User-Agent and public `x-ig-app-id` that call
requires. No crawler is impersonated on either surface.

**Why is `transcript` null on some rows?**
`transcriptStatus` says why: `no-speech` (music/ambient — not billed for a transcript),
`failed` (`transcriptError` has the reason), or `skipped` (`transcribe: false`). A fifth
status, `low-confidence`, does carry text: words came back but the confidence signals say it
is probably a music bed or crowd noise, so the text ships for you to judge and no
transcript-minute is billed.

**Which model should I pick?**
`base` for narration in a major language. `small` for accents, noise or non-English speech.
`tiny` when you only need a rough gist fast.

**Can I get the whole back-catalogue of an account?**
Yes — raise `maxReelsPerUser` and `maxItems`, and the run timeout to match (budget roughly
25 s per 30-second reel and 45–60 s per 75-second reel on `base` at the default memory;
`small` is ~3× that). The Actor stops cleanly at the time limit and tells you how many were
left.

**Two runs — will I get duplicates?**
Within a run, never (asserted). Across runs, use `postedAfter` to pick up only new reels.

**Is this legal?**
The data is public, but reels contain personal data and copyrighted audio. You are
responsible for complying with Instagram's terms and with how you use the output.

***

### Legal & fair use

This Actor reads public, logged-out Instagram surfaces only — sending its own identifying
User-Agent on the embed page, and on the profile feed the ordinary desktop-browser User-Agent
and public `x-ig-app-id` that the logged-out web client's own request carries — and never uses
a login, session cookie, CAPTCHA solver or impersonated crawler.
Transcripts are machine-generated from audio the account owner published publicly. **You are
responsible for complying with Instagram's Terms of Use and Meta's automated data collection
terms, with copyright in the underlying audio, and with privacy law in your jurisdiction.**

Instagram® is a trademark of Meta Platforms, Inc. This Actor is not affiliated with, endorsed
by, or connected to Meta.

***

### Feedback

Found a field you need or a reel that transcribes badly? Open an issue on the **Issues** tab,
and if the Actor earns it, a review on the **Reviews** tab helps other buyers find it.

# Actor input Schema

## `reelUrls` (type: `array`):

Instagram reel or video post URLs (https://www.instagram.com/reel/CODE/ or /p/CODE/), or bare shortcodes. One row per video. Photo and carousel posts are skipped (nothing to transcribe). Works alongside usernames.

## `usernames` (type: `array`):

Public Instagram usernames or profile URLs (natgeo, @nasa, https://www.instagram.com/nasa/). The newest videos/reels of each profile are taken, up to maxReelsPerUser. Private accounts return nothing.

## `maxReelsPerUser` (type: `integer`):

How many of the newest videos to take from each username. The profile feed mixes photos and videos (about 1 in 3 posts is a video on a typical brand account), so the actor pages until it has this many videos.

## `postedAfter` (type: `string`):

Optional date (YYYY-MM-DD). Username mode stops paging once it reaches older posts; URL mode drops older reels. Pinned posts are handled correctly.

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

Hard cap on rows across all URLs and usernames — your cost ceiling for the run. Each row is one reel.

## `transcribe` (type: `boolean`):

ON: download the audio and transcribe it with faster-whisper on this actor's own compute (no external API, no key). OFF: metadata-only rows — caption, counts, owner, music, media URLs — at the row price alone.

## `model` (type: `string`):

faster-whisper model (int8, baked into the image so nothing downloads at run time). base is the accuracy/cost meta for clear narration; small is noticeably better on accents, noisy audio and non-English speech at roughly 3x the compute time; tiny is the fastest and rougher.

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

ISO-639-1 code (en, es, pt, de, fr, hi, ...) to force the language, or auto to detect it per reel. Forcing the language helps on short reels where detection has little to go on.

## `transcriptFormats` (type: `array`):

Plain text (transcript) and the 3-second spoken hook are always included. Add timestamped segments, SRT or VTT subtitles if you need alignment.

## `maxAudioMinutesPerReel` (type: `integer`):

Reels are at most 3 minutes, but a profile's feed can carry long videos. Audio beyond this many minutes is not transcribed (and not billed). 1-60.

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

Proxy for the Instagram page/API requests. Apify datacenter is the default and measured clean (16/16 embed pages, 15/15 feed pages); a blocked datacenter request gets one capped retry through RESIDENTIAL automatically. Media bytes are always fetched without a proxy.

## `requestDelayMs` (type: `integer`):

Pause between consecutive Instagram page/API requests. Default 500 ms keeps a run well inside Instagram's public rate limits.

## Actor input object example

```json
{
  "reelUrls": [],
  "usernames": [
    "nasa"
  ],
  "maxReelsPerUser": 3,
  "maxItems": 3,
  "transcribe": true,
  "model": "tiny",
  "language": "auto",
  "transcriptFormats": [],
  "maxAudioMinutesPerReel": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestDelayMs": 500
}
```

# Actor output Schema

## `items` (type: `string`):

One row per unique public Instagram reel/video: spoken-word transcript (plus language, word count, 3-second hook and optional segments/SRT/VTT), caption, hashtags, mentions, views, likes, comments, duration, posted-at, owner, music attribution, mp4 and thumbnail URLs, and a transcript status that says whether the reel had speech. Deduplicated run-wide on the shortcode before billing.

# 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 = {
    "reelUrls": [],
    "usernames": [
        "nasa"
    ],
    "maxReelsPerUser": 3,
    "maxItems": 3,
    "model": "tiny",
    "maxAudioMinutesPerReel": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/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 = {
    "reelUrls": [],
    "usernames": ["nasa"],
    "maxReelsPerUser": 3,
    "maxItems": 3,
    "model": "tiny",
    "maxAudioMinutesPerReel": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/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 '{
  "reelUrls": [],
  "usernames": [
    "nasa"
  ],
  "maxReelsPerUser": 3,
  "maxItems": 3,
  "model": "tiny",
  "maxAudioMinutesPerReel": 1
}' |
apify call scrapersdelight/instagram-transcript-scraper --silent --output-dataset

```

## MCP server setup

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