# Facebook Page Reels Scraper — Facebook Reels API, Views, MP4 (`steadyfetch/facebook-reels-scraper`) Actor

Facebook page reels as rows: paste a page link and get views, duration, description, music, post date and a playable MP4 link — with an opt-in transcript per reel. No login, no cookies, exact limit. A page with no reels, a private page and a reel we cannot transcribe are never charged.

- **URL**: https://apify.com/steadyfetch/facebook-reels-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 reels

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

## Facebook Page Reels Scraper — Facebook Reels API, Views, MP4

**Paste a reel link and get that reel. Paste a page link and get its reels.** Most reel scrapers take a page and refuse the link you actually have; this one takes both, in the same run, in the same columns. One JSON row per reel: the reel ID and permalink, its description, the date it was posted, its duration in seconds, its view count, the music it uses, the page that posted it, a playable MP4 link and the moment that link expires — and, from a pasted reel link, the like, comment and share counts too. **From $1.00 per 1,000 reels** on the Business plan ($3.00 on the free plan), platform usage included, no start fee. You are charged only when a row lands in your dataset: a page with no reels, a page Facebook will not show without a login, a reel that is gone and a reel we could not transcribe all cost $0.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/facebook-reels-scraper`

- **Actor id:** `steadyfetch/facebook-reels-scraper`
- **Input:** `{ "pages": ["ladbible"] }` — the one field you have to set. Or `{ "reelUrls": ["https://www.facebook.com/reel/1234567890"] }` for individual reels.
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `0.50` — the run stops when it reaches it.
- **Your cap is the cap.** `maxItems` (default 30) caps the whole run — the run never delivers or bills a row past it, across every page and every link. If your input carries `resultsLimit`, `limit` or `count` instead — the names other reels scrapers use — the smallest of them is read as `maxItems` and one uncharged note row says so. Add `maxTotalChargeUsd` and the run stops at whichever comes first; rows already in hand ship, uncharged if they could not be billed. An ask past what this actor serves in one run — more than 2,000 rows, or a run clock outside 30-3,600 seconds — is never refused: the run starts, continues at the nearest end of the range, and one uncharged row names what you asked for and what bound it.

**Just want to see it work?** Click **Start** with nothing set and the run returns built-in sample rows — the exact output shape, read from nothing and charged nothing.

**Want the spoken words too?** Turn on **Include transcript** (`includeTranscript`) and every delivered reel is also transcribed — `transcript`, `transcriptLanguage`, `transcriptDurationSeconds` and timestamped `transcriptSegments` ride the same row, at $0.0075 per transcript actually produced.

### What one row looks like

A real delivered row, built by this actor's own parser from a harvested page listing:

```json
{
  "reelId": "2430902544051423",
  "postId": "1389159413245942",
  "url": "https://www.facebook.com/reel/2430902544051423",
  "description": "Who has the better snacks, England or Scotland? David Tennant decides ⚔️\n\n#davidtennant #england #scotland",
  "createdAt": "2026-05-20T17:15:38.000Z",
  "durationSeconds": 685.44,
  "viewCount": 3000000,
  "viewCountIsExact": false,
  "pageName": "LADbible",
  "pageId": "100064556895235",
  "pageUrl": "https://www.facebook.com/LADbible",
  "pageIsVerified": true,
  "source": "page_feed",
  "transcriptStatus": "not_requested",
  "status": "delivered",
  "charged": true
}
```

`videoUrl`, `videoUrlExpiresAt` and `thumbnailUrl` ride the same row — they are signed links that stop working after a few hours, so they are not reprinted here. From a pasted reel link the row also carries `likeCount`, `commentCount`, `shareCount` and the exact view count. Every column is listed in **What you get**, below; a reel that could not be delivered carries the same columns with `null` in them plus a `status` saying why, and no result fee.

### What you put in

| Input | What it takes |
|---|---|
| `pages` | Facebook pages, one per line — a page name (`ladbible`), a page slug, or a full page link. The page's reels come back newest first, after any pinned reel. |
| `reelUrls` | Individual reel links: `facebook.com/reel/<id>`, `facebook.com/share/r/<code>`, `facebook.com/watch/?v=<id>`, `fb.watch/<code>`, `facebook.com/<page>/videos/<id>`. Each returns its own row. |
| `startUrls` | One list for both kinds, for moving over from another scraper. Every line is sorted into the right door **before anything is bought**, so a reel link never costs a page read and a page link never costs a lookup. |
| `maxItems` | The hard ceiling on rows, split evenly across your pages. Default 30, up to 2,000 per run; ask for more and the run continues at 2,000 with one uncharged row saying so. |
| `newerThan` / `olderThan` | A date window on the reel's **own post date**. |
| `includeTranscript` | The spoken words, charged only when one was produced. |
| `includeSeen` · `resumeFromDatasetId` | Re-run the same pages without paying for reels you already have. |

Mix pages and reel links freely. A reel link put in the `pages` box is routed to the reel door automatically rather than refused, and one uncharged note row says so.

### What you get

| Column | What it is |
|---|---|
| `reelId` · `postId` · `url` | The reel's own ID, the post ID behind it, and the permalink you can open. |
| `description` | The reel's text, as the page wrote it. Present on page listings and on pasted links alike. |
| `createdAt` | When the reel was posted, ISO-8601 UTC, from Facebook's own creation time. |
| `durationSeconds` | The reel's length. |
| `viewCount` | Plays, as Facebook publishes them. |
| `likeCount` · `commentCount` · `shareCount` | Numbers on a row that came from a **pasted reel link**; explicit `null` on a row that came from a **page listing** — Facebook's reels feed does not carry them, and the row says so rather than buying a second read you did not ask for. |
| `videoUrl` · `videoUrlExpiresAt` | A directly playable MP4 link, and the moment Facebook's signature on it stops working (about five days out). |
| `thumbnailUrl` | The reel's cover image. |
| `musicTitle` · `musicArtist` | The attached audio track where the reel has one. |
| `pageName` · `pageId` · `pageUrl` · `pageIsVerified` | The page that posted it. |
| `source` | `page_feed` or `link` — which door this row came through. |
| `transcript` · `transcriptLanguage` · `transcriptDurationSeconds` · `transcriptSegments` · `transcriptStatus` · `transcriptReason` · `transcriptCharged` | The transcript leg, always present, explicit `null` when there is no transcript and a reason that says which. |
| `charged` · `chargedEvents` · `status` · `statusReason` · `retryable` | What billed, what happened, and whether a re-run is worth it. |

**What is null, and why.** `likeCount`, `commentCount` and `shareCount` are on a row served by the reel-link door, because that is the only Facebook surface that publishes them. A page listing carries views and duration, so on those rows the three counts are `null` — the Facebook answer, not a scrape miss. **Every column is present on every row: a value or `null`, never a column that comes and goes.**

**Column names are ours and they stay put.** Facebook's wire is read in one place and renamed once, so a Facebook rename does not rename your columns, and a fixed-schema table or sheet never breaks on a new row.

### What is never charged

1. A page with no reels, a page that does not exist, and a page Facebook will not show without a login — each one ships as a labelled row that says which. Open the page in a private window to check it yourself.
2. A pasted reel link Facebook answers is gone or private: one uncharged row saying so, never an empty run that looks like success.
3. A read that failed on our side — a wall, a rate limit, a timeout: one uncharged row that says it is worth a re-run.
4. Reels dropped by your own `newerThan` / `olderThan` window. They filter on the reel's own post date, after the listing, and the status line says how many were dropped.
5. Reels beyond your `maxItems` cap, and reels your account already had. A page listing skips those (`includeSeen` off) and the next new reel takes its place; a reel you pasted a LINK to is one you named, and there is no next reel behind it, so it always comes back either way — marked `repeat: true`, with no transcript leg and no result fee.
6. A transcript that could not be produced — no speech, music only, no audio track, an expired media link, a transcription failure, or a reel longer than 180 seconds. The reel row still delivers and still bills; the transcript does not.
7. Starting a run. There is no start fee, per reel or per run.

### Only today's reels?

Set `newerThan` — `2026-09-01`, or `7 days`. Page reels arrive newest first, so a daily run with the default limit catches everything posted since. The filter compares against the reel's **creation time**, which is on every row, and the status line reads back exactly what happened: *listed 20, 13 older than your date were dropped (not charged), 7 delivered.*

### How often the data changes

A busy page posts reels daily; a quiet one posts monthly. `viewCount` keeps climbing for weeks after a reel is posted, so a weekly re-run on the same page is the cheapest way to watch a reel's reach — with `includeSeen` off you pay only for what is new. That memory is a key-value store in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission (or Actor runs set to Full access); without it the run says so on its status line and cannot skip what you already have (Write is what lets the run remember what it delivered; without it every run pays again). The `videoUrl` is a **signed** link and Facebook's signature expires about five days out (`videoUrlExpiresAt` tells you when), so download the MP4 in the same week you fetch the row.

### Pricing

| Event | Free plan | Bronze | Silver | Business (Gold) |
|---|---|---|---|---|
| **Reel** — one row from a page listing | $0.003 | $0.002 | $0.0015 | **$0.001** |
| **Reel lookup** — one row from a pasted reel link | $0.012 | $0.008 | $0.006 | **$0.005** |
| **Reel transcript** — charged only when one was produced | $0.015 | $0.010 | $0.0075 | **$0.0075** |

A page listing row and a pasted-link row never both bill for the same reel: a row bills exactly one of **Reel** or **Reel lookup**, and `chargedEvents` on the row names which. Platform usage is included in all three; there is no start fee.

### Related actors

- Any reel, any length, by the minute: **Media Transcriber** — https://apify.com/steadyfetch/media-transcriber
- Facebook and Instagram ads with their creative text and video: **Facebook Ad Library Scraper** — https://apify.com/steadyfetch/facebook-ad-library-scraper
- Instagram reels with their transcripts: **Instagram Reel Transcript Scraper** — https://apify.com/steadyfetch/instagram-reel-transcript-scraper

### Something not right?

Open the **Issues** tab on this actor with your run ID and the input you used, and it gets looked at. If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Actor input Schema

## `pages` (type: `array`):

Facebook pages, one per line — a page name ("ladbible"), a page slug, or a full page link (https://www.facebook.com/ladbible); "\_demo" is the sample switch, not a page. A page with no reels, and a page Facebook will not show without a login, are delivered as labelled rows and are never charged. A reel link put here is routed to the reel box automatically, not refused, and a page's reels come back newest first. Leave this empty and the run returns built-in sample rows instead of collecting anything, so you can see the output shape at no result fee.

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

Individual reel links, one per line — https://www.facebook.com/reel/1234567890, https://www.facebook.com/share/r/abc123/, https://www.facebook.com/watch/?v=1234567890, https://fb.watch/abc123/ or https://www.facebook.com/<page>/videos/1234567890. A pasted reel link is a first-class input here, not a workaround: each one returns its own row with the like, comment, share and view counts a page listing does not carry. Charged as one "Reel lookup" when the row lands; a link Facebook answers is gone costs nothing.

## `startUrls` (type: `array`):

One list for both kinds of link, for buyers moving over from another scraper. A page or profile URL lists its reels; a reel, share, watch or fb.watch link returns that one reel. Each line is sorted into the right door before anything is bought, so a reel link never costs a page read and a page link never costs a lookup.

## `includeTranscript` (type: `boolean`):

OFF (default): rows carry the reel's description and metadata only. ON: every delivered reel also carries `transcript`, `transcriptLanguage` and timestamped `transcriptSegments`, at $0.0075 per produced transcript on top of the row. Facebook reels run about two minutes on average; one longer than 180 seconds is delivered without a transcript, uncharged, and the row names our Media Transcriber, which transcribes any length by the minute.

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

Hard ceiling — the run never delivers or bills more reel rows than this, across every page and every link, whatever else is set. With more than one page the limit is split evenly between them, so 60 across 3 pages collects 20 from each; a page that runs short leaves an uncharged row saying so rather than borrowing another page's share. Other reels scrapers call this `resultsLimit`, `limit` or `count`; those names are read as this field too and the smallest of them wins, with one uncharged note row saying which was read. Ask for more than this actor can serve in one run and the run still starts: it continues at the actor's own ceiling of 2,000 rows, with one uncharged row saying what you asked for and what bound it.

## `newerThan` (type: `string`):

A date, YYYY-MM-DD, or a relative window like "7 days". Reels posted before it are dropped, uncharged, and the status line says how many. Page reels arrive newest first, so a daily run with the default limit catches everything posted since.

## `olderThan` (type: `string`):

A date, YYYY-MM-DD, or a relative window like "30 days". Reels posted after it are dropped, uncharged, and counted on the status line.

## `maxRunSeconds` (type: `integer`):

The run stops cleanly before this many seconds and reports what is left, instead of being killed by a timeout. A time limit ends the collecting, never the delivering: reels already in hand are always written out. With the transcript on, budget roughly 6-10 seconds per reel. Anything outside the actor's own range of 30 to 3,600 seconds still starts a run: the clock is set to the nearest end of that range and one uncharged row says so.

## `includeSeen` (type: `boolean`):

OFF (default): a reel already delivered to your account is skipped — no row, no charge — and the next new reel takes its place, so you still get the number you asked for. ON: those reels are delivered again anyway, marked `repeat: true`, without a transcript leg and still not charged. That is about reels found in a page listing. A reel you pasted a LINK to is one you named, and there is no next reel behind it to take its place, so it always comes back either way — marked `repeat: true`, with no transcript leg and no result fee. Reels older than 90 days in your account's memory count as new again either way.

## `resumeFromDatasetId` (type: `string`):

The dataset ID of a previous run of this actor. Reels already in it are treated as reels you already have and are skipped, so a follow-up run collects only what is new even from a different Apify account. Selecting it here is what grants this run permission to read it.

## Actor input object example

```json
{
  "pages": [],
  "reelUrls": [],
  "startUrls": [],
  "includeTranscript": false,
  "maxItems": 30,
  "maxRunSeconds": 1800,
  "includeSeen": false
}
```

# Actor output Schema

## `reels` (type: `string`):

One row per reel delivered, with the same columns whichever door it came through: reel ID, permalink, description, post date, duration, view count, music title, page name and page URL, a playable MP4 link with the moment it expires, and — from a pasted reel link — the like, comment and share counts. Three events, two flags on every row: `charged` is the row itself (`reel` on a page listing, `reel-lookup` on a pasted link, named in `chargedEvents`), and `transcriptCharged` is the `transcript` event, true only on a row whose `transcriptStatus` is `transcribed`. A reel with no speech, no audio track, an expired media link, a speech-service problem or a length over 180 seconds is delivered with `transcript: null`, its `transcriptStatus` naming which, and no transcript charge.

## `misses` (type: `string`):

One uncharged row for every page or link that returned less than it was asked for, saying which happened: `page_not_found` (Facebook has no page or no reels tab at that link), `page_no_reels` (the page is there and has posted none), `page_private` (Facebook will not show it without a login), `reel_not_found` (Facebook answered that the reel is gone), `reel_unavailable` and `feed_unavailable` (the read failed on our side — a re-run is the fix), `stopped_at_limit` (one of your own limits or the run clock stopped it first), `date_filtered` (your own date window dropped it) and `vendor_budget` (this actor reached its own monthly collection allowance). None of these is charged.

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

Reels delivered by door, transcripts produced, what was asked for, reels your account already had, reels your date window dropped, what stopped the run, and the charged-event totals for `reel`, `reel-lookup` and `transcript`.

# 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 = {
    "pages": [],
    "reelUrls": [],
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/facebook-reels-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 = {
    "pages": [],
    "reelUrls": [],
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/facebook-reels-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 '{
  "pages": [],
  "reelUrls": [],
  "startUrls": []
}' |
apify call steadyfetch/facebook-reels-scraper --silent --output-dataset

```

## MCP server setup

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