# Twitch Scraper - Streams, Clips, VODs & Channel Data (`abotapi/twitch-scraper`) Actor

From $1/1K. Scrape Twitch data at scale, including channels, live streams, clips, VODs, and top games. Extract HLS m3u8 stream URLs, clip MP4 downloads in multiple qualities, chapter markers, and metadata. Supports keyword search with rich, analytics-ready output.

- **URL**: https://apify.com/abotapi/twitch-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Videos, Social media, Developer tools
- **Features:** Limited permissions
- **Stats:** 44 total users, 14 monthly users, 95.4% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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

## Twitch Scraper: Channels, Live Streams, Clips, VODs, and Chat Replay

Twitch Scraper turns Twitch into structured data. Get channel profiles, currently-live streams by game, top games, clips with direct MP4 download links, VOD lists, VOD chapter markers, and VOD chat-replay messages, plus a unified keyword search across channels and games. Paste channel logins, a game category slug, video IDs, or any twitch.tv link, then export the result to JSON, CSV, or Excel, or read it through the API.

### Why This Scraper?

- **Ten modes in one actor.** Channel profile (with clips and VODs attached), live streams by game, a game-streams alias, top games, unified search, clips, VOD lists, VOD metadata with chapters, VOD chat replay, and a URL mode that auto-detects what you paste.
- **Playable media links as a default field.** Each live stream and VOD metadata record carries a ready-to-play HLS playlist URL, and each clip carries direct MP4 download links in 1080p, 720p, 480p, and 360p.
- **Chat history without pulling the archive yourself.** VOD chat-replay records come back with the commenter, the message text, the timestamp offset, and any badges or emotes already parsed out.
- **VOD chapter markers.** Game-change segments with position and duration, useful for building highlight timestamps.
- **Two ways to avoid re-collecting data.** Resume one interrupted run by pasting its run or dataset ID, or turn on incremental mode for scheduled runs that return only new, updated, or expired records.
- **Built for automation.** Pipe results into Notion, Linear, Airtable, or your other Apify integrations straight from the run.

### Use Cases

- **Stream discovery tools:** surface currently live channels in a game, sorted by viewers, for a directory or alert bot.
- **Channel and esports tracking:** monitor a roster of channels' VOD history, clip performance, and follower counts over time.
- **Highlight and clip curation:** pull top clips per channel with direct MP4 URLs for re-use or archiving.
- **Chat research:** collect VOD chat-replay messages for sentiment, moderation, or community studies.
- **Category and trend monitoring:** track which games are trending right now with top games, per-category viewer, follower and live-channel (`broadcastersCount`) counts.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `recordType` | `"live_stream"` (also `channel`, `game`, `clip`, `video`, `vod_metadata`, `vod_comment`, `search_channel`, `search_game`) |
| `id` | `"123456789"` |
| `login` | `"examplelogin"` |
| `displayName` | `"Example Display"` |
| `url` | `"https://www.twitch.tv/examplelogin"` |
| `title` | `"Sample stream title"` (live stream, clip, video, VOD metadata) |
| `viewersCount` | `12345` (live channels and streams) |
| `viewCount` | `54321` (clip, video, VOD metadata) |
| `followersCount` | `100000` |
| `gameName` / `gameSlug` / `gameId` | `"Sample Game"` / `"sample-game"` / `"000000"` |
| `boxArtUrl` | `"https://static-cdn.jtvnw.net/ttv-boxart/000000-285x380.jpg"` |
| `thumbnailUrl` | preview or profile image URL |
| `isPartner` / `isAffiliate` | `false` / `true` |
| `durationSeconds` | `185` (clip, video, VOD metadata) |
| `tags` | `["English", "FPS"]` |
| `streamHlsUrl` | playable HLS playlist URL (live stream, live channel) |
| `vodHlsUrl` | playable HLS playlist URL (VOD metadata) |
| `clipMp4Urls` | `{ "1080": "https://.../1080.mp4", "720": "https://.../720.mp4" }` (clip) |
| `chapters` | `[{ "type": "GAME_CHANGE", "positionMs": 0, "gameName": "Game A" }]` (VOD metadata) |
| `messageText` / `commenterLogin` | chat message text and commenter login (VOD chat replay) |
| `changeType` / `changedFields` | `"UPDATED"` / `["viewersCount", "title"]` (incremental mode only) |
| `scrapedAt` | `"2026-05-09T00:00:00.000Z"` |

Channel records also carry the description, banner image, primary color, live status flags, last broadcast title, and linked social accounts. Clip records also carry the slug, embed URL, and curator login. VOD metadata records also carry the description, publish date, and mute info. VOD chat records also carry the commenter ID, user color, badges, emotes, and the video ID and URL they belong to.

### How to Use

1. Pick a **mode** (ten in total): `channel`, `live_streams` (or its alias `game_streams`), `top_games`, `search`, `clips`, `videos`, `vod_metadata`, `vod_comments`, or `url`.
2. Fill in only the fields for that mode's section. Other sections are ignored.
3. Set **Max results** (and **Max pages** for paginated modes) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV, or Excel, or read it through the API.

#### URL mode

Paste any of these twitch.tv URL shapes in `urls`:

| URL shape | Example | Returns |
|---|---|---|
| Channel page | `https://www.twitch.tv/shroud` | channel profile, clips, VODs |
| Mobile channel page | `https://m.twitch.tv/ninja` | channel profile, clips, VODs |
| Category page | `https://www.twitch.tv/directory/category/just-chatting` | live streams in the category |
| Legacy category page (by name) | `https://www.twitch.tv/directory/game/Just%20Chatting` | live streams in the category |
| All categories | `https://www.twitch.tv/directory` | most-watched categories |
| VOD page | `https://www.twitch.tv/videos/2767213751` | VOD metadata |
| Clip page (channel path) | `https://www.twitch.tv/shroud/clip/TangentialBillowingJalapenoYee-ccA2oB1pEYh4CHWJ` | the clip |
| Clip page (clips host) | `https://clips.twitch.tv/TangentialBillowingJalapenoYee-ccA2oB1pEYh4CHWJ` | the clip |
| Channel videos tab | `https://www.twitch.tv/shroud/videos` | recent VODs of the channel |
| Channel clips tab | `https://www.twitch.tv/shroud/clips?range=7d` | top clips (`range` = `24hr`, `7d`, `30d`, `all`) |
| Search page | `https://www.twitch.tv/search?term=valorant` | channels and categories matching the term |

A URL that matches none of these is skipped with a warning in the run log. With the `urls` field left untouched, 10 default URLs cover every shape in the table (the channel-path clip URL resolves to the same clip as the clips-host form, so it is not repeated). Behaviour change from the previous version: the default list went from 11 URLs to 10 distinct targets (the repeated channel-path clip URL was dropped, `m.twitch.tv/shroud` became `m.twitch.tv/ninja`, and the legacy category example became `directory/game/Valorant`). Incremental state saved by callers who left `urls` untouched keeps its key, so the 8 unchanged targets are not delivered or billed again as NEW; only the targets that are new in this default are NEW on their first run. **Max results is split evenly across the URLs** (at least 1 each while the total allows; the run still stops at Max results, so set it to at least the number of URLs to cover every URL), so the default 30 gives each of the 10 default URLs a share of 3 records; raise Max results to see more per URL, and a URL that returns fewer than its share does not hand the rest to the others. `channels` also accepts pasted channel URLs, and `gameSlug` also accepts a category name.

**Channel profile, plus its clips and recent VODs:**

```json
{
  "mode": "channel",
  "channels": ["shroud", "ninja", "pokimane"],
  "includeMediaUrls": true,
  "maxResults": 30
}
```

**Live streams in a game, with playable HLS URLs:**

```json
{
  "mode": "live_streams",
  "gameSlug": "valorant",
  "streamSort": "VIEWER_COUNT",
  "includeMediaUrls": true,
  "maxResults": 50,
  "maxPages": 2
}
```

**Top clips for a channel, with MP4 download URLs:**

```json
{
  "mode": "clips",
  "channels": ["xqc"],
  "clipPeriod": "LAST_WEEK",
  "includeMediaUrls": true,
  "maxResults": 25
}
```

**VOD chat replay for one video:**

```json
{
  "mode": "vod_comments",
  "videoIds": ["2767213751"],
  "maxResults": 200
}
```

#### Output views

The dataset has one view per record kind: **Records** (overview), **Channels**, **Live streams**, **Categories**, **Clips**, **Videos**, **VOD comments**, and **Changes (incremental)**. Pick one in the Console Output tab to see only that kind's columns.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/twitch-scraper").call(run_input={"mode": "live_streams", "gameSlug": "valorant"})
for stream in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(stream["displayName"], stream["viewersCount"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/twitch-scraper').call({ mode: 'live_streams', gameSlug: 'valorant' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets, or webhooks from the **Integrations** tab.

#### Media URLs and pagination caps

Channel and search-channel rows also carry `channelId`, `profileImageURL`, a nested `stream` (while live), `latestVideo`, `topClip`, `nextSchedule` (next scheduled stream, null when none), `lastBroadcastGameName` (game of the last broadcast) and, in search mode, the `keyword` that matched.

`includeMediaUrls` attaches a playable HLS URL to live stream, live channel, and VOD metadata records (clip MP4 URLs are always included). `maxResults` stops a run after that many records; `0` means the hard cap of 100000 records. In `channel` mode with `maxResults` `0`, each channel returns its profile plus 5 clips and 5 recent VODs. `maxPages` caps how many pages (about 30 items each) a paginated mode walks, `0` to walk until the source is exhausted or `maxResults` is hit. In `channel`, `clips`, and `videos` modes with multiple logins, the cap is shared roughly evenly across the requested channels.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and this run skips every record already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same mode and filters. Each record is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED`, or `EXPIRED` (only after a run that scanned the whole tracked scope, and only with `emitExpired`). `stateKey` names or shares the stored state. A record is `UPDATED` when a content field changes; live counters (`viewersCount`, `viewCount`, `followersCount`), rotating `thumbnailUrl`, signed `clipMp4Urls`, HLS playlist URLs and `scrapedAt` are never compared and never appear in `changedFields`, but stay in the output. `ignoreFieldsForChanges` adds more fields (names or dotted paths) to that list. With incremental mode off, output is exactly as before.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `channel` | Which scrape to run: `channel`, `live_streams`, `game_streams` (alias of `live_streams`), `top_games`, `search`, `clips`, `videos`, `vod_metadata`, `vod_comments`, or `url`. Only the fields for the chosen mode are read. |
| `channels` | array | `["shroud"]` | Twitch logins, one per line. Used by `channel`, `clips`, and `videos` modes. |
| `clipPeriod` | string | `LAST_WEEK` | Time window for clip collection: `LAST_DAY`, `LAST_WEEK`, `LAST_MONTH`, `ALL_TIME`. Used by `clips` mode. |
| `gameSlug` | string | (none, sample `valorant`) | Category URL slug or category name (for example `Just Chatting`; a name that matches nothing fails the run with a clear message). Used by `live_streams` and `game_streams` modes. |
| `streamSort` | string | `VIEWER_COUNT` | Sort order for the live stream list: `VIEWER_COUNT`, `RECENT`, `RELEVANCE`. |
| `searchQuery` | string | (none, sample `valorant`) | Free-text keyword. Used by `search` mode. |
| `videoIds` | array | (none, sample `["2767213751"]`) | Numeric video IDs. Used by `vod_metadata` and `vod_comments` modes. |
| `urls` | array | 10 example URLs, one per supported shape | Any twitch.tv URLs, one per line. Used by `url` mode; each is auto-detected (see the URL mode table). |
| `resumeFromRunId` | string | (none) | Continue one specific interrupted run: paste its run or dataset ID and this run skips every record already collected there. |
| `incrementalMode` | boolean | `false` | Turn on for scheduled or recurring runs against the same mode and filters. Classifies each record against remembered state. |
| `stateKey` | string | (none) | Name or share an incremental-mode state. Left empty, a key is derived from the mode and its filters. |
| `emitUnchanged` | boolean | `false` | Incremental mode only: also return (and bill) records that have not changed since the last run. |
| `emitExpired` | boolean | `false` | Incremental mode only: also return (and bill) records no longer found, after a run that fully scanned the tracked scope. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only: field names (or dotted paths like `stream.title`) not compared when detecting changes. They stay in the output. |
| `includeMediaUrls` | boolean | `true` | Attach a playable HLS URL to live stream, live channel, and VOD metadata records. Clip MP4 URLs are always included. |
| `keywords` | array | (none) | Search mode only: several queries in one run, merged with `searchQuery`. Each row carries its query in `keyword`. In search mode with several keywords the Max results limit applies to each keyword, so a run can return up to keywords x Max results records. |
| `maxItems` | integer | (none) | Alias of `maxResults` (wins when set; `0` or empty means use `maxResults`). In search mode with several keywords it applies to each keyword, so a run can return up to keywords x Max results records. |
| `maxResults` | integer | `30` | Stop after this many records. `0` means the hard cap of 100000 records (still capped by `maxPages`, hard cap 1000 pages). In url mode the number is split evenly across the URLs (at least 1 each while the total allows; set it to at least the number of URLs to cover every URL). In search mode with several keywords the limit applies to each keyword, so a run can return up to keywords x Max results records. |
| `maxPages` | integer | `0` | Pages to walk for paginated modes, about 30 items per page. `0` means walk until the source is exhausted or `maxResults` is hit (hard cap 1000 pages). |
| `proxyConfiguration` | object | Apify Proxy | Connection settings. Apify Datacenter is the default and works for this site; a backup connection is used automatically if it is unavailable. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "recordType": "live_stream",
  "id": "123456789",
  "login": "examplelogin",
  "displayName": "Example Display",
  "url": "https://www.twitch.tv/examplelogin",
  "title": "Sample stream title",
  "viewersCount": 12345,
  "createdAt": "2026-01-01T00:00:00Z",
  "streamType": "live",
  "gameName": "Sample Game",
  "gameSlug": "sample-game",
  "gameId": "000000",
  "boxArtUrl": "https://static-cdn.jtvnw.net/ttv-boxart/000000-285x380.jpg",
  "thumbnailUrl": "https://static-cdn.jtvnw.net/previews-ttv/live_user_examplelogin-0000x0000.jpg",
  "broadcasterId": "00000000",
  "broadcasterDescription": "Sample broadcaster bio.",
  "profileImageUrl": "https://static-cdn.jtvnw.net/jtv_user_pictures/0000-profile_image-70x70.png",
  "isPartner": true,
  "isAffiliate": false,
  "followersCount": 100000,
  "tags": ["English", "FPS"],
  "streamHlsUrl": "https://usher.ttvnw.net/api/channel/hls/examplelogin.m3u8?sig=0000&token=%7B...%7D",
  "scrapedAt": "2026-05-09T00:00:00.000Z"
}
```

### Plan Requirement

The default Apify Datacenter connection works for this site: pages and the data API both answered on it in testing. One field is the exception: on a Datacenter connection Twitch withholds a channel's linked social accounts, so `socialMedias` is empty on channel records and the run says so in its log and status ("Twitch withheld socialMedias on this connection ... It is usually returned when running with Apify proxy group: RESIDENTIAL."); every other field is read normally. Apify proxy access needs a paid Apify plan. If the default connection is refused, the actor continues on a backup connection and logs "Served over the backup connection (the default connection was unavailable)." On a free plan it first prints a notice that the selected connection is unavailable and that free plans do not include residential proxies. If nothing can be loaded, the run stops with one of these messages instead of an empty result:

- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required" (also shown when your own proxy URLs have bad credentials).
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Twitch pages. Upgrade ... or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."

Choosing residential is optional and billed to your Apify plan; it is only needed if you want `socialMedias` on channel records.

### FAQ

#### How much does it cost?

Pay per event. The events and when each is charged:

| Event | When it is charged |
| --- | --- |
| Actor Start (`apify-actor-start`) | once per run |
| Result (`apify-default-dataset-item`) | once for each record pushed to the dataset |
| Media URL enrichment (`media-url-enrichment`) | once for each pushed record that carries an attached playable HLS URL (`includeMediaUrls` on) |

The price of each event is shown on the **Pricing** tab; paid Apify plans get lower tiered rates. Unchanged or suppressed records in incremental mode are not billed. Use **Max results** to cap the cost of a run (in search mode with several keywords the limit applies to each keyword, so a run can return up to keywords x Max results records). Cost guard for chat replay: every `vod_comments` message is its own billed record and a long VOD can hold hundreds of thousands, so keep **Max results** (default 30) set to what you need, and note that `0` means the hard cap of 100000 records (and 1000 pages), not unlimited.

#### Is it legal to scrape Twitch?

This actor collects only publicly available channel, stream, clip, video, and chat data. You are responsible for how you use it: follow Twitch's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Usernames, chat messages, and other user-generated content may carry privacy or copyright considerations, so handle them accordingly.

#### Can I get only new or changed records on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated, reappeared, or (if enabled) expired records, and unchanged ones are not billed.

#### What's the difference between Resume and Incremental mode?

**Resume** (`resumeFromRunId`) continues one specific interrupted run: it skips whatever that run already delivered. **Incremental mode** (`incrementalMode`) is for running the same mode and filters again and again on a schedule and getting only what changed since last time. They solve different problems and can be combined for a first run.

#### Why did my run fail instead of returning an empty dataset?

If the site refuses a request and a refreshed session does not help, the run fails with a clear message rather than silently returning nothing, so an empty result is never confused with an unreadable one. A missing required field for the chosen mode (for example an empty `channels` list in `channel` mode, or no `gameSlug` in `live_streams` mode) also fails the run, and the message names the inputs to set. `channels` defaults to `shroud`, so a bare call in the default `channel` mode returns data.

If Twitch asks for a fresh verification in the middle of a multi-page read (`live_streams`, `url` categories, `vod_comments`) and it cannot be completed, the pages already read are kept and delivered, the run ends with a status message saying only the first N pages could be read, and the scan counts as incomplete (no EXPIRED detection, no completed-scan baseline saved; with incremental mode on, the records already delivered are still remembered so the next run does not deliver and bill them again as new). This partial result applies only when the fresh verification itself failed: a lost connection or a refused proxy in the middle of a read still fails the run with the connection message. Only rows actually delivered are billed. If not even the first page could be read, the run fails with a clear message and delivers nothing. If the verification fails only on a later target or URL after earlier pages were read but returned no records, the run fails with a message saying the pages read before it returned no records, so nothing was collected or charged. A channel with no matching content, a search with no hits, or an incremental run with nothing changed all succeed normally and simply return fewer (or zero) records, with the reason noted on the run's status.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear, or Airtable.

### 🔗 Want more video data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>📱 <a href="https://apify.com/abotapi/tiktok-live-recorder"><b>Tiktok Live Recorder</b></a><br>Record TikTok live streams to MP4 with full metadata, all stream quality URLs, and...</td><td>📱 <a href="https://apify.com/abotapi/universal-media-extractor"><b>Universal Media Extractor</b></a><br>Extract videos, audio, and metadata from 1000+ websites including YouTube, TikTok...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/tiktok-scraper"><b>TikTok Profile, Hashtag, Search, Video &amp; Trending Scraper</b></a><br>Scrape TikTok without login. Extract profiles, bios, follower stats and videos; search by...</td><td>📱 <a href="https://apify.com/abotapi/viewstats-channel-analytics"><b>ViewStats Scraper</b></a><br>Scrape YouTube channel analytics from ViewStats. Look up channels by handle, URL, or...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/lemon8-media-scraper"><b>Lemon8 Media Scraper</b></a><br>Lightweight Lemon8 scraper for extracting image and video URLs from posts, with optional...</td><td>📱 <a href="https://apify.com/abotapi/wattpad-scraper"><b>Wattpad Scraper</b></a><br>Scrape Wattpad stories by keyword, tag, category, language or URL. Extract authors...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/twitch-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. Each mode reads only the inputs in its own section below. Mode precedence: the selected mode alone decides what runs; inputs in other mode sections are ignored, and URL mode reads only urls.

## `channels` (type: `array`):

Twitch usernames (the part after twitch.tv/) or pasted channel URLs, one per line. Example: shroud, ninja, https://www.twitch.tv/pokimane. Defaults to shroud so a bare API call in channel mode returns data.

## `clipPeriod` (type: `string`):

Time window for clips collection.

## `gameSlug` (type: `string`):

Category slug (from twitch.tv/directory/category/SLUG) or the category name, e.g. valorant, just-chatting or 'Just Chatting'. A name that matches no category fails the run with a clear message.

## `streamSort` (type: `string`):

Sort order for the live stream list.

## `searchQuery` (type: `string`):

Free-text keyword.

## `keywords` (type: `array`):

Search mode: run several queries in one run. Combined with Search query. Each keyword tags its rows in the `keyword` output field. The Max results limit applies to each keyword, so a run can return up to keywords x Max results records.

## `videoIds` (type: `array`):

Numeric video IDs (the trailing segment of a twitch.tv/videos/ID URL), one per line. Example: 2767213751.

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

Paste any twitch.tv URLs, one per line. Mixing kinds is fine. Left untouched, 10 example URLs cover every supported URL shape (the channel-path clip form is the same as the clips.twitch.tv form): Max results is split evenly across the URLs (at least 1 each while the total allows; the run still stops at Max results, so set it to at least the number of URLs to cover every URL).

## `includeMediaUrls` (type: `boolean`):

When true, each live stream record carries an HLS m3u8 URL and each VOD carries an HLS playlist. Adds ~1 extra request per live-stream record (other modes incur negligible extra cost). Clip MP4 URLs are always included at no extra charge.

## `maxResults` (type: `integer`):

Maximum records to return. 0 = the hard cap of 100000 (still capped by maxPages). In search mode with several keywords the limit applies to each keyword, so a run can return up to keywords x Max results records. In url mode it is split evenly across the URLs, at least 1 each while the total allows.

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

Alias of Max results; takes precedence when set (hard cap 100000). 0 or empty = use Max results. In search mode with several keywords it applies to each keyword, so a run can return up to keywords x Max results records.

## `maxPages` (type: `integer`):

Pages to walk for paginated modes (~30 items per page). 0 = walk until the source is exhausted or maxResults is hit, with a hard cap of 1000 pages.

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

Apify Datacenter works for this site and is the default. If the selected connection is unavailable, the actor falls back to a backup connection automatically.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull without returning or charging for records already collected there. Use this after an interrupted run, or when continuing a large crawl in another run. For recurring monitoring of the same mode+filters, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching records as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED records. A record is UPDATED when any content field changes (title, game, status, tags, etc.). Live counters (viewersCount, viewCount, followersCount), rotating thumbnail URLs, signed clip media URLs, HLS playlist URLs and scrapedAt are never compared and never listed in changedFields, but stay in the output. Add more with "Ignore fields when detecting changes". Turn on "Emit unchanged" or "Emit expired" only when you also want those records returned (and billed). State is kept separately for each mode+filters setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode and its filters — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked scope — not when Max results/Max pages capped it, when Resume was used, or when a channel/game has more clips or videos than this run's per-target budget. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Incremental mode only. Field names (or dotted paths such as stream.title) that are NOT compared when deciding whether a record changed; they stay in the output but never make a record UPDATED or appear in changedFields. Counters, thumbnails, signed media URLs and scrapedAt are already ignored.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "channel",
  "channels": [
    "shroud"
  ],
  "clipPeriod": "LAST_WEEK",
  "gameSlug": "valorant",
  "streamSort": "VIEWER_COUNT",
  "searchQuery": "valorant",
  "videoIds": [
    "2767213751"
  ],
  "urls": [
    "https://www.twitch.tv/shroud",
    "https://www.twitch.tv/directory/category/just-chatting",
    "https://www.twitch.tv/directory/game/Valorant",
    "https://www.twitch.tv/videos/2767213751",
    "https://clips.twitch.tv/TangentialBillowingJalapenoYee-ccA2oB1pEYh4CHWJ",
    "https://m.twitch.tv/ninja",
    "https://www.twitch.tv/shroud/videos",
    "https://www.twitch.tv/shroud/clips",
    "https://www.twitch.tv/search?term=valorant",
    "https://www.twitch.tv/directory"
  ],
  "includeMediaUrls": true,
  "maxResults": 30,
  "maxPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `channels` (type: `string`):

No description

## `streams` (type: `string`):

No description

## `categories` (type: `string`):

No description

## `clips` (type: `string`):

No description

## `videos` (type: `string`):

No description

## `comments` (type: `string`):

No description

## `changes` (type: `string`):

No description

# 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 = {
    "mode": "channel",
    "channels": [
        "shroud"
    ],
    "gameSlug": "valorant",
    "searchQuery": "valorant",
    "videoIds": [
        "2767213751"
    ],
    "urls": [
        "https://www.twitch.tv/shroud",
        "https://www.twitch.tv/directory/category/just-chatting",
        "https://www.twitch.tv/directory/game/Valorant",
        "https://www.twitch.tv/videos/2767213751",
        "https://clips.twitch.tv/TangentialBillowingJalapenoYee-ccA2oB1pEYh4CHWJ",
        "https://m.twitch.tv/ninja",
        "https://www.twitch.tv/shroud/videos",
        "https://www.twitch.tv/shroud/clips",
        "https://www.twitch.tv/search?term=valorant",
        "https://www.twitch.tv/directory"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/twitch-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 = {
    "mode": "channel",
    "channels": ["shroud"],
    "gameSlug": "valorant",
    "searchQuery": "valorant",
    "videoIds": ["2767213751"],
    "urls": [
        "https://www.twitch.tv/shroud",
        "https://www.twitch.tv/directory/category/just-chatting",
        "https://www.twitch.tv/directory/game/Valorant",
        "https://www.twitch.tv/videos/2767213751",
        "https://clips.twitch.tv/TangentialBillowingJalapenoYee-ccA2oB1pEYh4CHWJ",
        "https://m.twitch.tv/ninja",
        "https://www.twitch.tv/shroud/videos",
        "https://www.twitch.tv/shroud/clips",
        "https://www.twitch.tv/search?term=valorant",
        "https://www.twitch.tv/directory",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/twitch-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 '{
  "mode": "channel",
  "channels": [
    "shroud"
  ],
  "gameSlug": "valorant",
  "searchQuery": "valorant",
  "videoIds": [
    "2767213751"
  ],
  "urls": [
    "https://www.twitch.tv/shroud",
    "https://www.twitch.tv/directory/category/just-chatting",
    "https://www.twitch.tv/directory/game/Valorant",
    "https://www.twitch.tv/videos/2767213751",
    "https://clips.twitch.tv/TangentialBillowingJalapenoYee-ccA2oB1pEYh4CHWJ",
    "https://m.twitch.tv/ninja",
    "https://www.twitch.tv/shroud/videos",
    "https://www.twitch.tv/shroud/clips",
    "https://www.twitch.tv/search?term=valorant",
    "https://www.twitch.tv/directory"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/twitch-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/twitch-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/UEOQX7JdF1AXTNx3b/builds/02fOjNnGcvHYbwvxk/openapi.json
