# Twitch Chat Scraper (`maximedupre/twitch-chat`) Actor

Collect public chat from Twitch VOD replays, live channels, or recent channel VODs. Get message text, timing, sender details, badges, subscriber status, emotes, and source context in an Apify dataset. No Twitch login or API token is needed.

- **URL**: https://apify.com/maximedupre/twitch-chat.md
- **Developed by:** [Maxime Dupré](https://apify.com/maximedupre) (community)
- **Categories:** Social media, Developer tools, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.05 / 1,000 chat messages

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### 🎮 Twitch chat from VODs and live channels

For stream researchers, moderators, developers, and analysts, Twitch Chat collects public messages from VOD replays, live channels, or recent channel VODs. Each dataset row includes message text, timing, sender details, badges, subscriber status, emotes, and Twitch source details, so you can review or analyze the chat in your own tools.

Use it to:

- Export messages from a public VOD with **[Twitch VOD Chat](https://apify.com/maximedupre/twitch-chat/examples/twitch-vod-chat)**.
- Build a recent broadcast archive with **[Twitch Chat History](https://apify.com/maximedupre/twitch-chat/examples/twitch-chat-history)**.
- Collect visible messages from a live channel with **[Twitch Live Chat](https://apify.com/maximedupre/twitch-chat/examples/twitch-live-chat)**.
- Save a timestamped replay for review with **[Twitch Chat Replay](https://apify.com/maximedupre/twitch-chat/examples/twitch-chat-replay)**.
- Download chat rows for later analysis with **[Twitch Chat Download](https://apify.com/maximedupre/twitch-chat/examples/twitch-chat-download)**.
- Collect chat rows that you can search in your dataset with **[Twitch Chat Search](https://apify.com/maximedupre/twitch-chat/examples/twitch-chat-search)**.

#### 📦 Twitch chat rows

The Actor saves one row for each collected chat message. VOD replay and channel-history rows use these fields. Fields that Twitch does not provide can be missing.

| Field | Type | What it does |
|---|---|---|
| `text` | string | The text in the chat message. |
| `messageId` | string | The source ID of the message, when Twitch provides one. |
| `sentAt` | string (date-time) | The time the message was sent, when Twitch provides it. |
| `streamOffsetSeconds` | number | The message position in the VOD, in seconds, when Twitch provides it. |
| `collectionMode` | string | The input mode: `vod` for a VOD replay or `channelHistory` for channel VOD history. |
| `source` | object | The Twitch VOD that contains the message. |
| `source.type` | string | The source kind. VOD replay and channel history rows use `vod`. |
| `source.id` | string | The Twitch VOD ID, when available. |
| `source.url` | string (URL) | The public Twitch page for the VOD, when available. |
| `source.title` | string | The VOD title, when available. |
| `source.channel` | string | The Twitch channel name, when available. |
| `source.channelId` | string | The Twitch channel ID, when available. |
| `source.game` | string | The game listed for the VOD, when available. |
| `sender` | object | The Twitch user who sent the message. |
| `sender.userId` | string | The sender's Twitch user ID, when available. |
| `sender.username` | string | The sender's Twitch username, when available. |
| `sender.displayName` | string | The name shown in Twitch chat, when available. |
| `sender.color` | string | The sender's chat name color, when available. |
| `sender.isSubscriber` | boolean | Whether the sender was a subscriber, when Twitch provides the status. |
| `sender.subscriptionTier` | string | The sender's subscription tier, when available. |
| `sender.roles` | array of strings | The Twitch chat roles shown for the sender, when available. |
| `sender.badges` | array of objects | The Twitch chat badges shown for the sender, when available. |
| `sender.badges[].name` | string | A badge name. |
| `sender.badges[].version` | string | The badge version from Twitch, when available. |
| `emotes` | array of objects | The emotes used in the message, with source IDs when available. |
| `emotes[].name` | string | The emote name, when available. |
| `emotes[].id` | string | The source emote ID, when available. |
| `emotes[].startIndex` | integer | The start position of the emote in the message text, when available. |
| `emotes[].endIndex` | integer | The end position of the emote in the message text, when available. |

Example VOD row:

```json
{
  "text": "mraHappy mraHappy mraHappy",
  "collectionMode": "vod",
  "source": {
    "type": "vod",
    "id": "2832981655",
    "url": "https://www.twitch.tv/videos/2832981655",
    "title": "Twitch Unity Guilds presents DRAGON GAMES : Legends of the Missing Temple w/ @stevesaylor and friends!",
    "channel": "twitch",
    "channelId": "12826",
    "game": "Dungeons & Dragons"
  },
  "messageId": "4b74d16e-e8fe-4bf1-a8ce-a5a7d761b787",
  "sentAt": "2026-07-30T19:52:40.179Z",
  "streamOffsetSeconds": 100,
  "sender": {
    "userId": "811215975",
    "username": "anonymousxjuan",
    "displayName": "AnonymousxJuan",
    "color": "#FF0000",
    "isSubscriber": false,
    "badges": [
      {
        "name": "went-outside",
        "version": "1"
      }
    ]
  },
  "emotes": [
    {
      "name": "mraHappy",
      "id": "emotesv2_0aca6a8a7e1c429ab0675022920e22a4",
      "startIndex": 0,
      "endIndex": 7
    },
    {
      "name": "mraHappy",
      "id": "emotesv2_0aca6a8a7e1c429ab0675022920e22a4",
      "startIndex": 9,
      "endIndex": 16
    },
    {
      "name": "mraHappy",
      "id": "emotesv2_0aca6a8a7e1c429ab0675022920e22a4",
      "startIndex": 18,
      "endIndex": 25
    }
  ]
}
```

#### 💬 Live Twitch messages

Live rows use the same message, source, and sender fields. They can also include live event and reply details. A live source may not provide a stream offset or emote list.

| Field | Type | What it does |
|---|---|---|
| `text` | string | The text in the chat message. |
| `messageId` | string | The source ID of the message, when Twitch provides one. |
| `sentAt` | string (date-time) | The time the message was sent, when Twitch provides it. |
| `streamOffsetSeconds` | number | The message position in the stream, when Twitch provides it. |
| `collectionMode` | string | The input mode: `live` for an active public channel. |
| `source` | object | The live Twitch channel that contains the message. |
| `source.type` | string | The source kind: `live`. |
| `source.id` | string | The Twitch channel ID, when available. |
| `source.url` | string (URL) | The public Twitch page for the channel, when available. |
| `source.title` | string | The live stream title, when available. |
| `source.channel` | string | The Twitch channel name, when available. |
| `source.channelId` | string | The Twitch channel ID, when available. |
| `source.game` | string | The game listed for the live stream, when available. |
| `sender` | object | The Twitch user who sent the message. |
| `sender.userId` | string | The sender's Twitch user ID, when available. |
| `sender.username` | string | The sender's Twitch username, when available. |
| `sender.displayName` | string | The name shown in Twitch chat, when available. |
| `sender.color` | string | The sender's chat name color, when available. |
| `sender.isSubscriber` | boolean | Whether the sender was a subscriber, when Twitch provides the status. |
| `sender.subscriptionTier` | string | The sender's subscription tier, when available. |
| `sender.roles` | array of strings | The Twitch chat roles shown for the sender, when available. |
| `sender.badges` | array of objects | The Twitch chat badges shown for the sender, when available. |
| `sender.badges[].name` | string | A badge name. |
| `sender.badges[].version` | string | The badge version from Twitch, when available. |
| `emotes` | array of objects | The emotes used in the message, when available. |
| `emotes[].name` | string | The emote name, when available. |
| `emotes[].id` | string | The source emote ID, when available. |
| `emotes[].startIndex` | integer | The start position of the emote in the message text, when available. |
| `emotes[].endIndex` | integer | The end position of the emote in the message text, when available. |
| `event` | object | Extra live-chat event details, when available. |
| `event.messageType` | string | The Twitch message type, when available. |
| `event.bits` | integer | The number of Twitch Bits in the message, when available. |
| `reply` | object | The parent message context for a live-chat reply, when available. |
| `reply.parentMessageId` | string | The source ID of the message being replied to, when available. |
| `reply.parentUsername` | string | The username of the parent message sender, when available. |
| `reply.parentText` | string | The text of the parent message, when available. |

Example live row:

```json
{
  "text": "@CARD_878  hesRight",
  "collectionMode": "live",
  "source": {
    "type": "live",
    "id": "71092938",
    "url": "https://www.twitch.tv/xqc",
    "title": "🧑‍🌾LIVE🧑‍🌾HERE🧑‍🌾LOCK IN🧑‍🌾DRAMA🧑‍🌾NEWS🧑‍🌾VIDEOSV🧑‍🌾REACTS🧑‍🌾TRHNIGS🧑‍🌾GAMES🧑‍🌾VIDEOGAMES🧑‍🌾MAYBE EVEN THINGS AND SUCH",
    "channel": "xqc",
    "channelId": "71092938",
    "game": "Dark and Darker"
  },
  "messageId": "8de6dce8-6681-4a91-8117-139aea7bc240",
  "sentAt": "2026-08-02T03:40:47.617Z",
  "sender": {
    "userId": "926294771",
    "username": "mtmf1_709",
    "displayName": "mtmf1_709",
    "isSubscriber": false
  },
  "event": {
    "messageType": "PRIVMSG"
  },
  "reply": {
    "parentMessageId": "175512e3-0243-434b-8bb1-67ee31a98376",
    "parentUsername": "card_878",
    "parentText": "wade otag is gato backwards"
  }
}
```

#### ▶️ Run a Twitch chat collection

1. Choose `vod`, `live`, or `channelHistory` in `target`.
2. Add VOD URLs, a live channel, or channel IDs and names for the target you chose. Other target-option sections are ignored.
3. Start the run and open the `dataset` link in the output. Each saved row is one chat message.

#### ⚙️ Input

| Field | Type | What it does |
|---|---|---|
| `target` | string | Choose `vod`, `live`, or `channelHistory`. The other Target Options sections are ignored. It defaults to `vod`. |
| `vodUrls` | array of objects | Add one or more public Twitch VOD URLs. The same options apply to each VOD in the run. Twitch must make the chat replay available. |
| `vodUrls[].url` | string (URL) | One public Twitch VOD URL. |
| `vodStartOffsetSeconds` | integer | Start each VOD this many seconds into the stream. Leave empty to start at the beginning. |
| `liveChannel` | string | Enter one public Twitch channel name or URL that is live now. The Actor stops when the run ends. |
| `liveMaxMessages` | integer | Stop after this many live chat messages. Leave empty to collect all available messages during the run, until the source is exhausted. |
| `channelIds` | array of strings | Add one or more public Twitch channel IDs or names. The Actor finds recent archived broadcasts for each channel and collects their available chat. |
| `channelMaxVods` | integer | Stop after this many recent archived broadcasts for each channel. Leave empty to use all recent broadcasts Twitch makes available until the source is exhausted. |
| `maxMessagesPerVod` | integer | For VOD chat replay and channel VOD history, stop each VOD after this many messages. Leave empty to collect all available messages until the source is exhausted. This setting is ignored for live channel chat. |

Example input:

```json
{
  "target": "vod",
  "vodUrls": [
    {
      "url": "https://www.twitch.tv/videos/2832981655"
    }
  ],
  "vodStartOffsetSeconds": 100,
  "maxMessagesPerVod": 5
}
```

#### 🧾 Output

| Field | Type | What it does |
|---|---|---|
| `dataset` | string (URL) | Link to the collected chat messages in the default dataset. It opens the dataset overview in Apify Console. |

The dataset holds the VOD, channel-history, or live message rows described above.

#### 💳 Pricing

This Actor uses pay-per-event pricing. The `chat-message` event is charged for each visible Twitch chat message that is successfully saved from a public VOD replay or active public channel. Your total depends on how many messages are saved.

| Event | Price | What it covers |
|---|---:|---|
| `chat-message` | $0.00005 | One visible Twitch chat message from a public VOD replay or active public channel is saved to the dataset. |

#### 🔌 Integrations

https://www.youtube.com/watch?v=bNACk1\_S\_6w\&list=PLObrtcm1Kw6MUrlLNDbK9QRg8VDJg0gOW\&index=4

Open the dataset in Apify Console or fetch its items through the Apify API. Use the JSON data in your own review, search, moderation, or analysis workflow.

#### ❓ FAQ

##### Does this collect live Twitch chat?

Yes. Choose `live` and enter a public channel that is live now. The Actor collects visible messages during the run and does not monitor the channel after the run ends.

##### Can I get a full available VOD replay?

Yes. Leave `maxMessagesPerVod` empty to collect all available messages until the source is exhausted. Twitch must expose the VOD chat replay for the Actor to collect it.

##### What if Twitch does not show the VOD chat replay?

The Actor cannot collect chat that Twitch does not expose publicly. This includes private, login-gated, expired, disabled-replay, or subscriber-only chat when the source does not make it available.

##### Can I collect more than one VOD or channel history?

Yes. Add more than one VOD URL or channel ID or name. The same options apply to each VOD in the run, and `channelMaxVods` sets the recent broadcast limit for each channel.

##### Can I start partway through a VOD?

Yes. Set `vodStartOffsetSeconds` to the start point you want. Leave it empty to start at the beginning.

##### Is this official Twitch chat?

No. This is an Apify Actor for chat that Twitch exposes publicly. It does not need a Twitch login or API token.

##### Does it download Twitch video or clips?

No. It collects chat messages and related Twitch data. It does not download or transform Twitch video or clip media.

##### Why is a field missing from a row?

Some fields are available only when Twitch provides them for that message or source. Check optional fields before using them instead of filling them with guessed values.

##### How can I search or export the chat?

Open the default dataset in Apify Console or fetch its items through the Apify API. You can then search the messages and use Apify's dataset export tools.

### 📝 Changelog

**0.0: Initial release**

### 🆘 Support

For issues, questions, or feature requests, [file a ticket](https://console.apify.com/actors/maximedupre~twitch-chat/issues) and I'll fix or implement it in less than 24h 🫡

### 🔗 Related Actors

- [Twitch Scraper](https://apify.com/maximedupre/twitch-scraper) — Find channel, stream, game, clip, and VOD metadata before choosing chat sources.
- [Twitch VOD Chat Scraper — Full Chat Replay Export](https://apify.com/haketa/twitch-vod-chat-scraper) — Export a full chat replay from one public Twitch VOD.
- [Twitch Messages Scraper](https://apify.com/nifty.codes/twitch-chat-scraper) — Collect live chat messages from an active Twitch channel.
- [Twitch Streams & Channels Scraper](https://apify.com/fetch_cat/twitch-streams-channels-scraper) — Find public streams, channels, categories, and recent videos before a chat run.
- [Twitch VOD Downloader](https://apify.com/agentx/twitch-vod-downloader) — Get structured VOD data or a stored video when chat is not the goal.

**Made with ❤️ by Maxime Dupré**

# Actor input Schema

## `target` (type: `string`):

Choose what to collect from Twitch.

## `vodUrls` (type: `array`):

Add one or more public Twitch VOD URLs. The same options apply to each VOD in this run. Twitch must make the chat replay available.

## `vodStartOffsetSeconds` (type: `integer`):

Start collecting each VOD this many seconds into the stream. Leave empty to start at the beginning.

## `liveChannel` (type: `string`):

Enter one public Twitch channel name or URL that is live now. The Actor does not monitor it after the run ends.

## `liveMaxMessages` (type: `integer`):

Stop after this many live chat messages. Leave empty to collect all available messages during the run, until the source is exhausted.

## `channelIds` (type: `array`):

Add one or more public Twitch channel IDs or names. The Actor finds recent archived broadcasts for each channel and collects their available chat.

## `channelMaxVods` (type: `integer`):

Stop after this many recent archived broadcasts for each channel. Leave empty to use all recent broadcasts Twitch makes available until the source is exhausted.

## `maxMessagesPerVod` (type: `integer`):

For VOD chat replay and Channel VOD history, stop each VOD after this many messages. Leave empty to collect all available messages until the source is exhausted. This setting is ignored for Live channel chat.

## Actor input object example

```json
{
  "target": "vod",
  "vodUrls": [
    {
      "url": "https://www.twitch.tv/videos/123456789"
    }
  ],
  "vodStartOffsetSeconds": 300,
  "liveChannel": "channel_name",
  "liveMaxMessages": 1000,
  "channelIds": [
    "channel_name"
  ],
  "channelMaxVods": 5,
  "maxMessagesPerVod": 10000
}
```

# Actor output Schema

## `dataset` (type: `string`):

Collected chat messages in the default dataset.

# 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 = {
    "target": "vod",
    "vodUrls": [
        {
            "url": "https://www.twitch.tv/videos/2832981655"
        }
    ],
    "vodStartOffsetSeconds": 0,
    "maxMessagesPerVod": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("maximedupre/twitch-chat").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 = {
    "target": "vod",
    "vodUrls": [{ "url": "https://www.twitch.tv/videos/2832981655" }],
    "vodStartOffsetSeconds": 0,
    "maxMessagesPerVod": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("maximedupre/twitch-chat").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "target": "vod",
  "vodUrls": [
    {
      "url": "https://www.twitch.tv/videos/2832981655"
    }
  ],
  "vodStartOffsetSeconds": 0,
  "maxMessagesPerVod": 25
}' |
apify call maximedupre/twitch-chat --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=maximedupre/twitch-chat",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/lBbpa7aEYCv6AjjsY/builds/th8R90UkqTA9M5fkr/openapi.json
