# Facebook Transcript Scraper & API: Reels & Video to Text (`quarra/facebook-transcript-scraper`) Actor

Turn Facebook reels and videos into text, from links or a Page's latest reels. Plain text with timestamps, SRT and VTT, the language spoken, and the video's details. Facebook's own captions when they exist. No login. Videos without speech are not charged.

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

## Pricing

from $1.00 / 1,000 transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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?

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

## Facebook Transcript Scraper: Reels and Videos to Text

Turn public Facebook reels and videos into text, without an account or API key. Paste reel or video links, or a public
Page, and each video comes back with its transcript as plain text and as timestamped segments, its language, optional
SRT and WebVTT subtitle files, and the video's metadata (Page, date, caption, views, reactions, comments).

Two things you would otherwise check by hand: when the creator added captions to a video, we return them as they are
(no speech-to-text, only the transcript fee), but Facebook's automatic captions are not used, because on 10 reels
shared on Reddit (12 October 2026) they misheard 5 that our speech-to-text got right; and videos that are only music
come back as a free `no_speech` record instead of a made-up transcript. Every video that cannot be transcribed (deleted, private, not a video) gets a free
record that says why. You pay only for transcripts delivered.

### What we measured

On the Apify platform on 11 October 2026, with the default settings of that day (Facebook's automatic captions were
still used then), on 13 public reels and videos (English,
Spanish and Hindi; 32 to 1,052 seconds) taken from Pages and from videos shared at random, in 4 runs:

- **9 transcripts.** 6 came from Facebook's own captions (English, Spanish, Hindi; no audio minute billed; a
  17-minute video returned 2,703 words), 3 from speech-to-text (a 60-second reel: 57 words; a 2 minute 25 video: 3
  minutes billed). In this sample about two videos in three had captions; on an earlier local sample of 10, about one
  in four. Expect both.
- **4 reels without clear speech** (music, ambient sound) came back as free `missing` records. We checked two of them
  by hand with a separate transcription: nothing but music.
- A Page without public reels, a Page that does not exist, a deleted video and a post link each came back as a free
  `error` record with the reason.
- The default input (one reel) runs in about 6 seconds. A Page with 4 videos took 14 seconds.

Speech-to-text runs on third-party AI providers: OpenAI's Whisper models (large-v3 and large-v3 turbo) run by Groq
for most videos, and ElevenLabs Scribe, a stronger model, to settle the language when hints disagree and as a
backup. The audio is sent to them only to be transcribed. ElevenLabs stores each transcript on its side: we delete
it as soon as we have read it and check that it is gone (we read it again and get "not found"); if a deletion cannot
be confirmed before the run ends, the run log says so.

Whisper is known to invent short phrases on music ("Thank you.", "Music", "Bye."). We filter them: a phrase the model
is not confident about, a known phantom phrase, or a few words spread over a long stretch of music are not speech. A
video whose speech is all filtered out is `missing` with `missingReason: "no_speech"`, free. We never sell part of a
video as its transcript: when the filter has to leave out more than 20% of the speech, the video is `missing` with
`missingReason: "incomplete_transcript"`, free.

### What you can transcribe

| Input | Examples | What you get |
|---|---|---|
| Reels | `https://www.facebook.com/reel/636216875539019`, `/{page}/reel/…` | that reel |
| Videos | `https://www.facebook.com/watch/?v=…`, `/{page}/videos/…`, `/video.php?v=…`, `/watch/live/?v=…` | that video |
| Short links | `https://www.facebook.com/share/r/…`, `/share/v/…`, `https://fb.watch/…` | the video behind the link |
| Pages | `@SteveBartlettShow`, `https://www.facebook.com/SteveBartlettShow`, `…/reels/` | the Page's latest reels |

Mix them in one run. A video found in two inputs (a link and a Page) is transcribed and charged once.

**Pages:** without signing in, Facebook shows at most the 10 latest reels of a Page, and some Pages show none (those
get a free `account_empty` record that says so). We read those and transcribe up to `maxVideosPerAccount` (10 at most).
For older videos, paste their links. Personal profiles are not read. Facebook shows only public videos to a visitor
who is not signed in: private videos, groups, stories, posts, photos and Marketplace are not supported and come back
as free `error` records.

### Languages

Creator captions are returned in the language spoken in the video; the automatic translations Facebook adds to them
are ignored. For speech-to-text we use the same language checks as our other transcript Actors: a caption written in
another script than the detected language, or a detection that contradicts a strong hint, sends the video to a
stronger model that settles the language; if it still cannot be settled, the video comes back as a free
`uncertain_language` record, never as a charged guess, and never as a translation.

`language` is a preference, never an instruction: it changes neither the models used nor the number of passes, and
never forces a translation. A video in English with `language: "fr"` comes back in English with
`requestedLanguageFound: false`.

### Output

One record per video. A transcript (`status: "ok"`), shortened here:

```json
{
  "status": "ok",
  "platform": "facebook",
  "videoId": "636216875539019",
  "url": "https://www.facebook.com/reel/636216875539019/",
  "author": {
    "username": "SteveBartlettShow",
    "fullName": "Steven Bartlett",
    "id": "100044966566880",
    "isVerified": true,
    "url": "https://www.facebook.com/SteveBartlettShow"
  },
  "caption": "Feel trapped in life? Watch this …\n\n#podcast",
  "hashtags": ["podcast"],
  "publishedAt": "2025-07-17T13:00:38Z",
  "viewCount": 5986517,
  "reactionCount": 150288,
  "commentCount": 884,
  "durationSeconds": 60.8,
  "language": "en",
  "transcriptSource": "speech_to_text",
  "isAutoGenerated": true,
  "hasSpeech": true,
  "transcript": "Have you ever seen that video of the ant in the pen? No. They get an ant on a piece of paper, and …",
  "segmentCount": 30,
  "wordCount": 259,
  "billedMinutes": 2,
  "input": "https://www.facebook.com/reel/636216875539019",
  "inputType": "video"
}
```

A video without a transcript comes back as a free record that holds the reason and nothing else:

```json
{
  "status": "missing",
  "platform": "facebook",
  "videoId": "1447510737466246",
  "url": "https://www.facebook.com/reel/1447510737466246/",
  "missingReason": "no_speech",
  "message": "no clear speech detected (music, sounds, or words the model is not sure of); free",
  "input": "https://www.facebook.com/reel/1447510737466246",
  "inputType": "video"
}
```

#### Fields that need a word of explanation

- `status`: `ok` (transcript, charged), `missing` (the video exists but has nothing to transcribe: `no_speech`,
  `incomplete_transcript`, `no_audio`, or `too_long` past your limit; free) or `error` (the link could not be read;
  free).
- `errorCode` and `retryable`: `not_found`, `invalid_url`, `account_not_found`, `account_empty` are final;
  `blocked`, `network`, `transcription_capacity` and `transcription_failed` are worth retrying later
  (`retryable: true`).
- `transcriptSource`: `captions` (captions the creator added to the video, as Facebook serves them; Facebook's automatic
  captions are not used) or `speech_to_text`.
- `language`: ISO 639-1 code of the language spoken and transcribed. We never translate.
- `segments`: `start`, `end` and `duration` in seconds. `includeSrt` and `includeVtt` add ready-to-save `.srt` and
  `.vtt` files.
- `viewCount`, `reactionCount`, `commentCount`: the counts Facebook shows on the video, exact (not rounded as "1.2M").
  Facebook sometimes serves a video in its reels player, which shows no view count: `viewCount` is then empty.
- `billedMinutes`: the started minutes of video billed for a speech-to-text transcript (a 36-second reel is 1
  minute); 0 for captions.
- `input` and `position`: which of your inputs produced the record, and the rank of the video in a Page's list.
  Records are written as each video finishes: sort by `input` and `position` for the source's order.

The dataset has three views: Overview, Transcripts and Video details. **For a spreadsheet, export the Transcripts
view as Excel (XLSX)**: one row per video. A CSV opened in Excel may read a caption that starts with `=`, `+`, `-` or
`@` as a formula: prefer XLSX or JSON. Excel also cuts a cell after 32,767 characters (about 35 minutes of speech).
Control characters and text-direction marks are removed from captions and transcripts. The run's summary is the
`OUTPUT` record of the default key-value store: per input, videos, transcripts, missing, errors and duplicates.

### Input examples

The latest reels of two Pages and one reel, with subtitle files:

```json
{
  "urls": ["@SteveBartlettShow", "https://www.facebook.com/natgeo", "https://www.facebook.com/reel/636216875539019"],
  "maxVideosPerAccount": 5,
  "includeSrt": true
}
```

Text only, at most 200 transcripts:

```json
{
  "urls": ["https://www.facebook.com/watch/?v=…", "https://fb.watch/…"],
  "transcriptFormat": "text",
  "maxItems": 200
}
```

### Pricing

Pay per result: **$0.001 per transcript plus $0.0045 per started minute of video** transcribed by speech-to-text,
plus a **start fee of $0.004 per GB of memory per run** ($0.004 at the default 512 MB). A 40-second reel costs $0.0055,
a 2-minute video $0.010. A video returned from captions the creator added costs only the $0.001 transcript fee. Videos
without speech, incomplete transcripts, deleted or private videos, errors and duplicates are free. Platform usage is
included.

| Apify plan | Per transcript | Per started minute |
|---|---|---|
| Free | $0.001 | $0.0045 |
| Bronze | $0.001 | $0.0040 |
| Silver | $0.001 | $0.0036 |
| Gold and above | $0.001 | $0.0034 |

Your maximum charge per run is never exceeded: the length of each video is read before it is transcribed, and the run
stops at the last transcript it can charge and says so in its status. `maxItems` caps the transcripts of a run: ask
for 10, get 10.

Free records are a by-product, not a product: a run stops, and says why, once 100 videos or inputs (an unknown Page
counts) plus 2 per transcript have given no transcript, or once 15 minutes of audio plus 2 per billed minute have
been transcribed for nothing (music-only reels). `maxDurationMinutes` (default 30, at most 60) skips longer videos
before anything is transcribed. A single video that would cost more than what is left of your maximum charge gets a
free `over_budget` record and the run goes on with cheaper ones.

### Use cases

- Content research: what creators and brands in a niche say in their reels, searchable and quotable.
- Repurposing: turn your own reels and videos into captions, blog posts, newsletters and subtitles.
- LLM pipelines: summaries, topic tagging, hooks and scripts analysis on hundreds of videos.
- Accessibility: SRT and VTT files to edit or upload.

### Limits

- **Public videos only.** Private videos, groups, stories, posts and photos are not supported; a video Facebook shows
  only to signed-in visitors comes back as a free `not_found` error.
- **Pages:** the 10 latest reels at most (Facebook's limit without an account); some Pages show none. Personal
  profiles are not read.
- **Speech or music.** The speech-to-text service does not return exactly the same text on every pass over the same
  video; on clear speech the difference does not matter, on music it can. So we keep a margin: a video is charged
  only when its speech is clear; everything in between comes back as a free `no_speech` record. Clearly sung lyrics
  still count as speech. Short or distant speech over loud music may be classed as `no_speech`.
- **Speed.** Videos that need speech-to-text take about 3 seconds each on large runs, because our speech-to-text capacity is shared fairly between runs and paced. If it is
  temporarily used up, the videos that need it get free `transcription_capacity` records (`retryable: true`) and the
  status says when to retry; they never count against your run's limits and never make it fail.
- Longest video: 60 minutes (`maxDurationMinutes`, 30 by default).
- **Connection:** keep the default (automatic). If you set your own connection and it refuses everything, the run
  stops at once with a free record per input and says so.

### Memory and speed

512 MB is the default and the best setting: the run waits on speech-to-text, not on memory, and the Actor uses
under 100 MB. More memory does not make it faster (the maximum is 1 GB).

### FAQ

**Is it legal to scrape Facebook transcripts?** This Actor reads only public videos, the way a visitor who is not
signed in sees them, and accepts no Facebook terms on your behalf. In *Meta v. Bright Data* (N.D. Cal., January
2024\) the court found that collecting public data while logged out did not breach Meta's terms; it is a first-instance
decision, not binding everywhere. Transcripts reproduce what people say, which may be protected by copyright and may
contain personal data: check that your use (research, analysis, quotation, accessibility) is allowed where you are,
and handle personal data under the rules that apply to you (GDPR in Europe). We keep nothing after the run; the
transcripts ElevenLabs stores are deleted during the run and the deletion is checked (the run log says so if one
could not be confirmed).

**Do I need a Facebook account or any setup?** No. Keep the default connection: its cost is included in the price.

**Why is a video "no_speech" when I hear someone?** Check `OUTPUT.speechFilter` for that video: very short or very
uncertain speech over music is filtered. If a video with clear speech comes back `no_speech`, open an issue with the
link: we answer within 48 hours.

**What happens when Facebook changes something?** The Actor runs every day on known videos, so we see a change
before you do. A page Facebook refuses is retried automatically, and a run that fails on our side ends as Failed
and charges no transcript.

# Actor input Schema

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

One per line: reel or video URLs (facebook.com/reel/…, /watch/?v=…, /{page}/videos/…, share links, fb.watch/…) or public Pages (@name or page URL) for their latest reels.

## `maxVideosPerAccount` (type: `integer`):

For Pages: how many of their latest reels to transcribe. Without an account, Facebook shows at most 10 of them, and some Pages show none (those get a free record that says so).

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

Optional. The language you expect, as a code or a name (en, es, hi, English…). It is a preference, not an instruction: each video comes back in the language actually spoken, never translated (requestedLanguageFound: false when it differs). With Hindi, Urdu or Punjabi, a video heard as English is a free uncertain_language record (the model may have translated it). Empty: detected for each video.

## `transcriptFormat` (type: `string`):

transcript is the whole text in one string; segments is a list of {start, end, duration, text} in seconds.

## `includeSrt` (type: `boolean`):

Add an srt field: the transcript as a ready-to-save .srt subtitle file.

## `includeVtt` (type: `boolean`):

Add a vtt field: the transcript as a ready-to-save .vtt subtitle file.

## `maxDurationMinutes` (type: `integer`):

Longer videos get a free too_long record instead of a transcript. Their length is read before anything is transcribed.

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

Optional cap for the whole run, counted in charged transcripts. Respected exactly. Free records (no speech, deleted, private) do not count; they are bounded separately (100 plus 2 per transcript).

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

Connection. Keep the default (automatic). Costs are included in the price.

## Actor input object example

```json
{
  "urls": [
    "https://www.facebook.com/reel/636216875539019"
  ],
  "maxVideosPerAccount": 10,
  "transcriptFormat": "both",
  "includeSrt": false,
  "includeVtt": false,
  "maxDurationMinutes": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every video: transcript, segments, metadata, and the free missing/error records with their reason.

## `overview` (type: `string`):

One line per video: status, account, language, words, reason.

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

Per-input counts, videos without speech, errors, speech-to-text and network statistics.

# 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.facebook.com/reel/636216875539019"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("quarra/facebook-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 = {
    "urls": ["https://www.facebook.com/reel/636216875539019"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("quarra/facebook-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 '{
  "urls": [
    "https://www.facebook.com/reel/636216875539019"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call quarra/facebook-transcript-scraper --silent --output-dataset

```

## MCP server setup

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