# JioHotstar Scraper - Movies, Shows, Sports & Episodes (`abotapi/hotstar-com-scraper`) Actor

Scrape the JioHotstar catalog by content type or URL. Extract shows, movies, episodes, sports, clips and live content, with one structured record per catalog URL including title, show name, content IDs and source URL.

- **URL**: https://apify.com/abotapi/hotstar-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 catalog records

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

## JioHotstar Catalog Scraper

Scrape the JioHotstar catalog (hotstar.com, India's streaming platform) by catalog
type or pasted content URL. The actor reads the catalog the platform itself publishes:
one record per content URL, carrying the row kind (show, movie, episode, sport page,
clip, channel, genre or language landing), the display titles shown in the URL, the
show id and the content id. Sitemap mode walks one catalog dataset at a time (tens
of thousands of rows per map file on the episode surface); URL mode turns any pasted
catalog URL into its record. Recurring change tracking is first-class.

### Why This Scraper?

- One record per catalog URL with the kind, humanized title, show title, show id and
  content id, straight from the URLs the platform itself publishes for the index.
- Walks the catalog by type: Episode, Shows, Movie, Sport landings, live pages,
  clips, channels, genres and languages are all selectable.
- Reads the catalog anonymously: no account, no session, no cookie jar.
- Every record is a stable, resolvable URL, so downstream consumers can fetch
  metadata or join against their own systems.
- Recurring `incrementalMode` watch: the first run returns everything as NEW, later
  runs return only what the platform added (NEW) or dropped (EXPIRED) from the
  mapped catalog.
- Resume from a previous run id to continue a long, capped crawl.
- Optional MCP connector export to Notion, Linear, Airtable or Apify.

### Scope and limits (read before running)

- The catalog lane is ANONYMOUS and walking it needs no login.
- Playback, personal rows (watchlist, continue-watching) and content metadata behind
  the platform's session gate (descriptions, durations, reviews) are OUT OF SCOPE:
  the platform's own pages carry an empty template for automated readers, and the
  session layer that unlocks the rich metadata is a phone-OTP-style signup that
  this actor does not fake (machine-blocked, same verdict class as other
  phone-OTP-gated platforms). If a temp-email signup ever gets accepted, the rich
  metadata lane can be folded in as a future attempt on top of the same record ids.
- Keyword search is NOT a mode: the site's own robots.txt disallows its search
  paths, so search-by-keyword is not part of this actor's scope. Use the site's
  search page in a browser instead, then paste the URL here (URL mode accepts any
  catalog URL on hotstar.com's `in` tree).

### Data You Get

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

| field | example value |
|---|---|
| recordId | `1000158864` |
| kind | `episode` |
| title | `Episode Title Example` |
| showTitle | `Sample Show Name` |
| showId | `7326` |
| contentId | `1000158864` |
| url | `https://www.hotstar.com/in/shows/sample-show/0000/episode-title-example/0000/watch` |
| scrapedAt | `2026-01-01T00:00:00Z` |
| changeType | `NEW` |
| changedFields | `[]` |
| firstSeenAt | `2026-01-01T00:00:00Z` |
| lastSeenAt | `2026-01-01T00:00:00Z` |

### How to Use

Walk the episode surface by default (10 catalog maps, 50,000 URLs each, sliced by
Max items):

```json
{
  "mode": "sitemap",
  "mapType": "EPISODE",
  "maxItems": 50
}
```

A different catalog type:

```json
{
  "mode": "sitemap",
  "mapType": "SHOWS",
  "maxItems": 100
}
```

Paste catalog URLs (URL mode; every pasted URL returns its one record):

```json
{
  "mode": "url",
  "urls": [
    "https://www.hotstar.com/in/shows/sample-show/0000",
    "https://www.hotstar.com/in/movies/sample-movie/0000"
  ]
}
```

Daily change watch of a mapped catalog surface:

```json
{
  "mode": "sitemap",
  "mapType": "EPISODE",
  "maxItems": 0,
  "incrementalMode": true,
  "emitExpired": true
}
```

### Input Parameters

| parameter | type | default | description |
|---|---|---|---|
| mode | select | `sitemap` | `sitemap` walks the site's own published catalog lists. `url` processes pasted catalog URLs. |
| mapType | select | `EPISODE` | Sitemap mode only: which catalog dataset to walk (EPISODE, SHOWS, MOVIE, SPORT, CLIPS, CHANNELS, GENRES, LANGUAGES and the rest of the site's own list). |
| urls | stringList | empty | URL mode only: hotstar catalog URLs. Each pasted URL returns one record. |
| maxItems | integer | `20` | The single cap: stop after collecting this many records. `0` = unlimited. |
| maxPages | integer | `0` | Cap on how many catalog map files (each up to 50,000 rows) to download per run. `0` = no map limit, the run then stops at Max items. |
| resumeFromRunId | string | empty | Continue ONE interrupted run from its previous run id or dataset id. |
| incrementalMode | boolean | `false` | Recurring monitoring of the same catalog walk: later runs return only NEW and EXPIRED rows instead of the full snapshot. |
| stateKey | string | | Optional name for the incremental state, so two differently configured walks never mix state. |
| emitUnchanged | boolean | `false` | Incremental mode only: also return rows that have not changed, marked UNCHANGED. Returns, and bills, extra rows. |
| emitExpired | boolean | `false` | Incremental mode only: also return rows the previous run tracked that are no longer in the mapped catalog. Only after a complete uncapped scan. Bills extra synthetic rows. |
| mcpConnectors | mcpConnector | | Optional side channel export. |
| notionParentPageUrl | string | | Notion connector only. |
| maxNotifyListings | integer | `50` | Cap on records written to each connector. |
| proxy | proxy | default | Leave the default: the catalog answers an India connection, so the default pins an India exit. |

### Output Example

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

```json
{
  "recordId": "1000158864",
  "url": "https://www.hotstar.com/in/shows/sample-show/7326/sample-episode/1000158864/watch",
  "kind": "episode",
  "title": "Sample Episode",
  "showTitle": "Sample Show",
  "showId": "7326",
  "contentId": "1000158864",
  "scrapedAt": "2026-01-01T00:00:00Z"
}
```

### Notes

- Playback is out of scope, always: the actor reads catalog entries only, and never
  mints a session for the player route or any personal row.
- Content metadata (description, duration, episode list) is not part of this actor:
  the catalog pages serve an empty template to automated readers and the metadata
  lane is session-gated. If a signup lane ever propagates a reusable session, a
  rich metadata lane on the same record ids is a future attempt.
- The single catalog cap is Max items; when it cuts a walk short the next run can
  resume (Resume from a previous run) instead of re-scanning what is already there.

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

Optional: the run PIPEs a condensed summary per record (title, kind, URL) into a
connector you authorize under Apify, Settings, API & Integrations. Notion gets a
page per record under the parent page you set in `notionParentPageUrl`; the other
connectors get a best-effort write or digest; each connector NEVER receives the
full record, only a summary, and the complete record always stays in the dataset.
Set `mcpConnectors` in the input to switch it on, and `maxNotifyListings` to cap
how many records flow per run.

This never changes what the dataset returns: with no connector selected the export
code never runs and behaviour is identical.

# Actor input Schema

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

How the run finds catalog rows. 'sitemap' walks the site's own published catalog URL lists (the index at hotstar.com/in/new-sitemap.xml, as linked from the site's robots.txt): one content URL per row, filtered by the catalog type below. 'url' processes the content URLs you paste instead: each catalog URL returns one record, and rows are read out of the URL itself (show, show id, episode title, content id).

## `mapType` (type: `string`):

Which of the site's own catalog datasets to walk. EPISODE (the largest, tens of thousands of rows per map) is the default; SHOWS and MOVIE carry the show and movie landings; SPORT-related types carry the sport pages; the rest carry the smaller catalog surfaces (channels, genres, languages).

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

Paste hotstar.com catalog URLs: show pages (https://www.hotstar.com/in/shows/show-name/000), episode pages (https://www.hotstar.com/in/shows/show-name/000/episode-name/000/watch), or movie pages (https://www.hotstar.com/in/movies/movie-name/000). Each pasted URL returns one record read out of the URL itself; the Max items cap stops the walk if you paste many.

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

Stop after collecting this many records across all catalog maps and URLs. Set 0 for unlimited.

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

Safety bound on how many catalog map files (each up to 50,000 rows) to download per run. Leave empty (0) for no map limit: the run then stops at Max items, not here. Does not impose a separate cap below Max items.

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

Paste a previous run ID or dataset ID to continue a large crawl without returning or charging for records already collected there. Use this after an interrupted or capped run. For recurring monitoring of the same catalog walk, use Incremental mode below instead.

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

Turn this on for daily or weekly recurring monitoring of the same catalog walk, for example to watch what the platform adds or drops from the catalog. The first run returns everything as NEW. Later runs normally return only NEW and EXPIRED records: the catalog is static per URL, so a previously seen URL never reports UPDATED. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately per mode, catalog type and URL setup; use State key 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, catalog type and URLs, so two different walks never mix state.

## `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 a 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 in the catalog, marked EXPIRED. Only produced once a run has fully scanned the mapped catalog: not when Max items capped it, not when Resume was used, and not when the run was refused. This returns, and bills, extra synthetic rows, so leave it off unless you need removal tracking.

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

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per record; other connectors get a best-effort write or digest. Each connector receives a condensed summary per record, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. 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 record pages are created. Required to enable the Notion export; ignored by other connectors.

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

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

## `proxy` (type: `object`):

The catalog is served by the site's India-only region, so the default pins an India exit. If a run reports that it could not read any results, try again (the shared pool is a rotating one), or use a custom proxy endpoint of your own.

## Actor input object example

```json
{
  "mode": "sitemap",
  "mapType": "EPISODE",
  "urls": [
    "https://www.hotstar.com/in/shows/perry-mason/1971002854"
  ],
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "IN"
  }
}
```

# Actor output Schema

## `overview` (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": "sitemap",
    "mapType": "EPISODE",
    "urls": [
        "https://www.hotstar.com/in/shows/perry-mason/1971002854"
    ],
    "maxItems": 20,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "IN"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/hotstar-com-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": "sitemap",
    "mapType": "EPISODE",
    "urls": ["https://www.hotstar.com/in/shows/perry-mason/1971002854"],
    "maxItems": 20,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "IN",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/hotstar-com-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": "sitemap",
  "mapType": "EPISODE",
  "urls": [
    "https://www.hotstar.com/in/shows/perry-mason/1971002854"
  ],
  "maxItems": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "IN"
  }
}' |
apify call abotapi/hotstar-com-scraper --silent --output-dataset

```

## MCP server setup

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