# X Broadcast Viewer History & Replay Link Scraper (`dataspryva/x-broadcast-viewer-history-and-replay-link-scraper`) Actor

Get a public X broadcast's title, state, start/end times, viewer counts over time, thumbnail, broadcaster profile, and an optional replay stream link, resolved from a broadcast link or ID.

- **URL**: https://apify.com/dataspryva/x-broadcast-viewer-history-and-replay-link-scraper.md
- **Developed by:** [Dataspryva](https://apify.com/dataspryva) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## X Broadcast Viewer History & Replay Link Scraper

Collect public X broadcasts from a broadcast link, a bare broadcast ID, an announcement post, or a legacy Periscope link. Get the title, state, timing, viewer counts and viewer-history graph, an HLS playback link, a stable cover image, the broadcaster's public profile, and — when the broadcast has ended and its chat is public — the chat replay text. No X account, cookies, or API key required.

### What this actor does

- **One target list, many link forms:** `x.com/i/broadcasts/...`, `twitter.com/i/broadcasts/...`, bare IDs, post links that announce a broadcast, and legacy Periscope `/v/<id>` or `/<user>/<id>` links.
- **Metadata and timing:** title, state, creation/start/end times, duration, dimensions, media key, language, tags, and the announcement post.
- **Viewer statistics:** current/total counters, the viewer-history graph **when X exposes it**, and derived peak/average viewers. The graph is sample counts, not a fabricated timeline.
- **Replay and playback:** an HLS playback URL and stream status. Live broadcasts return the live HLS URL; ended broadcasts return the replay URL.
- **Public chat replay:** optional text messages from an ended, public chat. Live and private chats are not collected; join/heart events are ignored.
- **Broadcaster profile:** followers, bio, location, account creation date, pinned posts, and verification details, with a compact fallback.
- **Stable cover image:** the expiring X thumbnail is stored in the run's key-value store; the original X URL is kept beside it.
- **Honest status rows:** invalid links, old `pscp.tv/w` share links, Spaces, deleted broadcasts, and rate limits each get a clear message instead of a silent failure.
- **Empty fields are omitted** — no `null` values or placeholder strings.

### Output per record

Each row has a `type`:

| `type` | Meaning |
|---|---|
| `broadcast` | One broadcast's metadata, viewer statistics, playback, chat summary and broadcaster |
| `viewer timeline point` | One sampled point of the viewer-history graph (only when timeline rows are enabled) |
| `chat message` | One public chat text message (only when chat replay is enabled and available) |
| `status` | A problem row with a `status` and a human-readable `message` |

#### Broadcast fields

- **Identity:** `id`, `url`, `title`, `state`, `status` (X's machine state when it publishes one)
- **Timing:** `createdAt`, `updatedAt`, `startedAt`, `endedAt`, `durationSeconds`
- **Viewers:** `viewersNow`, `viewersTotal`, `viewCountGraph[]`, `viewerHistoryAvailable`, `viewerGraphPoints`, `peakViewers`, `averageViewers`, `viewerTimelineSampled`
- **Media:** `thumbnailUrl` (stable), `thumbnailUrlOriginal`, `width`, `height`, `mediaKey`, `cameraRotation`, `isHighLatency`
- **Replay:** `replayAvailable`, `playbackKind` (`live` / `replay` / `upcoming`), `streamStatus`, `replayVideoUrl`, `replayVideoFormat`
- **Chat:** `chatHistoryStatus`, `chatMessageCount`, `chatEventCount`, `chatHistoryTruncated`
- **Broadcaster:** `broadcaster` (public profile; compact fallback)
- **Context:** `language`, `tags[]`, `announcementTweetId`, `announcementTweetUrl`, `sourceTweetId`, `sourceTweetUrl` (when the input was a post), `chatOption`, `privateChat`, `isLocked`, `noHearts`, `hasModeration`, `source`, `collectedAt`

#### Viewer timeline fields

`broadcastId`, `broadcastUrl`, `title`, `pointIndex` (original graph position), `viewers`, `elapsedSeconds`, `timestamp`, `timeEstimated`, `collectedAt`.

#### Chat message fields

`broadcastId`, `broadcastUrl`, `title`, `messageId`, `username`, `displayName`, `text`, `timestamp`, `elapsedSeconds`, `profileImageUrl`, `twitterId`, `verified`, `participantIndex`, `collectedAt`.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `broadcastIds` | array | – | Broadcast links, bare IDs, announcement posts, or legacy Periscope `/v/<id>` / `/<user>/<id>` links (required) |
| `includeViewerStats` | boolean | `true` | Current/total viewers, viewer-history graph and derived peak/average |
| `includeViewerTimeline` | boolean | `false` | Emit one row per viewer-history point |
| `maxViewerTimelinePoints` | integer | `0` | Max timeline rows per broadcast; `0` = all; capped runs sample evenly |
| `includeReplayVideo` | boolean | `false` | Resolve the HLS playback URL and stream status |
| `includeChatHistory` | boolean | `false` | Collect public chat replay text after the broadcast ends |
| `maxChatMessages` | integer | `1000` | Max public chat text messages per broadcast |
| `includeThumbnail` | boolean | `true` | Store the cover image and return a stable URL plus the original |
| `includeBroadcasterProfile` | boolean | `true` | Add the broadcaster's public profile; falls back to the compact record |

#### Example: broadcast metadata

```json
{
  "broadcastIds": ["https://x.com/i/broadcasts/1YGNrbPLejQGw"]
}
```

#### Example: viewer history plus replay

```json
{
  "broadcastIds": ["1YGNrbPLejQGw"],
  "includeViewerTimeline": true,
  "includeReplayVideo": true
}
```

#### Example: public chat replay

```json
{
  "broadcastIds": ["https://x.com/i/broadcasts/1BdGYEQPeDNGX"],
  "includeChatHistory": true,
  "maxChatMessages": 200
}
```

#### Example: mixed links and a deleted broadcast

```json
{
  "broadcastIds": [
    "https://x.com/i/broadcasts/1YGNrbPLejQGw",
    "https://www.periscope.tv/v/1kvJpmapzboxE",
    "https://x.com/i/broadcasts/1AAAAAAAAAAAA"
  ],
  "includeReplayVideo": true
}
```

### Use cases

- **Live-event monitoring:** track audience size during and after a public broadcast.
- **Newsroom verification:** confirm viewer counts, timing and the announcement post for a public statement.
- **Archival:** preserve broadcast metadata, a stable cover image and a replay URL while they are still available.
- **Creator comparison:** compare audience, duration and chat activity across broadcasters.
- **Dashboard feeds:** load the viewer timeline or chat rows directly into a chart or spreadsheet.
- **Chat research:** study public chat replay around an event without collecting private or live chat.

### FAQ

**Do I need an X account, cookies or a proxy?**
No. The actor reads publicly visible broadcast data and selects a working connection automatically. Just add one or more targets and run it.

**What counts as an X broadcast?**
X live video, the product that replaced Periscope. Its canonical link is `x.com/i/broadcasts/<id>`; `twitter.com` links redirect there. Legacy Periscope replay pages no longer exist, but their broadcast IDs can still resolve at x.com when the broadcast survives. If X returns an upcoming broadcast, the row reports the state X publishes and `playbackKind: "upcoming"`; X's guest payload does not expose reliable scheduled start times or pre-live slate URLs, so those fields are omitted rather than guessed.

**Why is the viewer-history graph missing for some broadcasts?**
X exposes `view_count_graph` for only a subset of broadcasts: 26 of 50 (52%) in our October 2026 sample, and rarely for older ones. The actor never invents points; when X has no graph it reports `viewerHistoryAvailable: false` and omits peak/average.

**Are the viewer timeline timestamps exact?**
X returns viewer counts only, without per-point times. Timestamps are derived evenly from the broadcast's real start/end (or the collection time for a live broadcast) and flagged `timeEstimated: true`. `averageViewers` is the mean of X's sampled points.

**What do `viewersTotal` and `viewersNow` mean?**
They are X's own counters. `viewersTotal` is not a unique-viewer count, and on an ended broadcast `viewersNow` is the last concurrent count X reported, not a live figure.

**Is the replay a downloadable video?**
No. `replayVideoUrl` is an HLS playback URL for a standard player or your own pipeline. For a live broadcast it is the live HLS URL (`playbackKind: "live"`); the replay appears after the broadcast ends. Some regional CDN edges return 403 unless the request carries `Referer: https://x.com/`; add that header if your player is refused.

**Why is a replay or chat missing?**
A deleted or non-public broadcast has no replay. A live broadcast has no replay yet. Chat replay is collected only after a broadcast ends, only for public chats, and only when X still serves the chat history; many chats contain only join/heart events, so `chatMessageCount` can be 0. History is read in pages of up to 1,000 events with a 50-page cap per broadcast; `maxChatMessages` caps text rows, and `chatHistoryTruncated` marks a capped result.

**Do legacy Periscope links work?**
`/v/<id>` and `/<user>/<id>` links resolve by their trailing broadcast ID. Old `pscp.tv/w/...` share links are not broadcast IDs and return a `legacyShareLinkUnsupported` status row.

**Does this work on X Spaces?**
No. Spaces are audio rooms; they return a `spacesNotSupported` status row.

**Is this affiliated with X?**
No. This is an independent, third-party actor that reads publicly visible broadcast data and is not operated by or affiliated with X Corp.

# Actor input Schema

## `broadcastIds` (type: `array`):

Paste broadcast links such as https://x.com/i/broadcasts/1YGNrbPLejQGw, bare broadcast IDs, post links that announce a broadcast, or legacy Periscope links (/v/<id> or /<user>/<id>). Old pscp.tv/w share links cannot be resolved and return a status row.

## `includeViewerStats` (type: `boolean`):

Include current/total viewers, the viewer-history graph when X exposes it, and derived peak/average viewers. Turn off for a lighter output.

## `includeViewerTimeline` (type: `boolean`):

Push one "viewer timeline point" row per point of the broadcast's viewer-history graph. No rows are emitted when X provides no graph.

## `maxViewerTimelinePoints` (type: `integer`):

Maximum timeline rows per broadcast. 0 means all points. When capped, points are evenly sampled across the full broadcast and each row keeps its original index.

## `includeReplayVideo` (type: `boolean`):

Resolve the HLS playback URL and stream status. For a live broadcast the URL is the live stream; for an ended broadcast it is the replay.

## `includeChatHistory` (type: `boolean`):

Collect text messages from the broadcast's public chat replay after it ends. Live chat and private chats are not collected. No text is emitted when the chat only contains join/heart events.

## `maxChatMessages` (type: `integer`):

Maximum public chat text messages per broadcast. Older events and join/heart events are not counted.

## `includeThumbnail` (type: `boolean`):

Store the broadcast cover image in the run's key-value store and return its stable URL plus the original X image URL.

## `includeBroadcasterProfile` (type: `boolean`):

Add the broadcaster's public profile (followers, bio, location, account creation date, pinned posts). Falls back to the compact broadcaster record if the profile is unavailable.

## Actor input object example

```json
{
  "broadcastIds": [
    "https://x.com/i/broadcasts/1YGNrbPLejQGw",
    "1YGNrbPLejQGw",
    "https://x.com/SpaceX/status/1649033533846097927"
  ],
  "includeViewerStats": true,
  "includeViewerTimeline": false,
  "maxViewerTimelinePoints": 0,
  "includeReplayVideo": false,
  "includeChatHistory": false,
  "maxChatMessages": 100,
  "includeThumbnail": true,
  "includeBroadcasterProfile": true
}
```

# Actor output Schema

## `broadcasts` (type: `string`):

Collected broadcast rows and status rows.

# 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 = {
    "broadcastIds": [
        "https://x.com/i/broadcasts/1YGNrbPLejQGw"
    ],
    "includeViewerStats": true,
    "includeViewerTimeline": false,
    "maxViewerTimelinePoints": 0,
    "includeReplayVideo": false,
    "includeChatHistory": false,
    "maxChatMessages": 100,
    "includeThumbnail": true,
    "includeBroadcasterProfile": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataspryva/x-broadcast-viewer-history-and-replay-link-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 = {
    "broadcastIds": ["https://x.com/i/broadcasts/1YGNrbPLejQGw"],
    "includeViewerStats": True,
    "includeViewerTimeline": False,
    "maxViewerTimelinePoints": 0,
    "includeReplayVideo": False,
    "includeChatHistory": False,
    "maxChatMessages": 100,
    "includeThumbnail": True,
    "includeBroadcasterProfile": True,
}

# Run the Actor and wait for it to finish
run = client.actor("dataspryva/x-broadcast-viewer-history-and-replay-link-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 '{
  "broadcastIds": [
    "https://x.com/i/broadcasts/1YGNrbPLejQGw"
  ],
  "includeViewerStats": true,
  "includeViewerTimeline": false,
  "maxViewerTimelinePoints": 0,
  "includeReplayVideo": false,
  "includeChatHistory": false,
  "maxChatMessages": 100,
  "includeThumbnail": true,
  "includeBroadcasterProfile": true
}' |
apify call dataspryva/x-broadcast-viewer-history-and-replay-link-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataspryva/x-broadcast-viewer-history-and-replay-link-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/z0s6TC1YoFaRHOdjO/builds/RQ0JBxm63hdzUYFf3/openapi.json
