# Streaming Manifest QC (`immemorial_sarinda/streaming-manifest-qc`) Actor

Check a supplied HLS master, static VOD playlists, and declared resource inventory for metadata inconsistencies. Get one complete JSON report with playlist, reference, segment, map, and finding records.

- **URL**: https://apify.com/immemorial_sarinda/streaming-manifest-qc.md
- **Developed by:** [Picknotes](https://apify.com/immemorial_sarinda) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$100.00 / 1,000 completed reports

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/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

## Streaming Manifest QC

Check a supplied HLS master, static VOD playlists, and declared resource inventory for metadata inconsistencies. Get one complete JSON report with playlist, reference, segment, map, and finding records.

### Price

USD 0.10 per completed report, with platform usage included. The synthetic demo has the same price. A complete report can contain error findings: completion means the supported input was fully processed, not that a stream will play. Malformed, unsupported, incomplete, or oversized input does not produce a completed report. If delivery becomes uncertain, check the run's dataset before retrying: an item may already have been stored and billed.

### Quickstart

Run with:

```json
{"useDemo":true}
```

For your own data, set `useDemo` to false, confirm `authorizedNonpersonalNonconfidentialData` as true, and put the entire bundle JSON in the string field `manifestBundleJson`. For example, this Python script creates an input file containing a small supported bundle:

```python
import json
bundle = {
    "schemaVersion": "n31-input/1",
    "master": {
        "uri": "https://example.test/master.m3u8",
        "text": '#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-STREAM-INF:BANDWIDTH=1000,CODECS="avc1.4d401e"\nvideo/main.m3u8\n'
    },
    "media": [{
        "uri": "https://example.test/video/main.m3u8",
        "text": "#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-TARGETDURATION:6\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-ENDLIST\n#EXTINF:6.0,\n../s.ts\n"
    }],
    "inventory": {"https://example.test/s.ts": {"sizeBytes": 100}}
}
with open("INPUT.json", "w", encoding="utf-8") as f:
    json.dump({
        "useDemo": False,
        "authorizedNonpersonalNonconfidentialData": True,
        "manifestBundleJson": json.dumps(bundle, ensure_ascii=False)
    }, f, ensure_ascii=False)
```

Only submit data you are authorized to process that is nonpersonal and nonconfidential. Do not include secrets, signed URLs, credentials, private labels, or personal data. The platform may store INPUT before execution. No PII or secret detection is provided. Output hashes are identifiers, not anonymization guarantees.

### Supported checks and limits

This is a narrow RFC 8216 version 1–7 VOD metadata profile, not general HLS validation. Each media playlist requires both `EXT-X-PLAYLIST-TYPE:VOD` and `EXT-X-ENDLIST`. Supported declarations include variants/renditions, target duration, media/discontinuity sequences, segment durations, byte ranges, and initialization maps. Findings cover missing supplied playlist references, missing or empty inventory resources, out-of-bounds ranges, target durations, minimum versions, and selected rendition declaration consistency.

All playlist text and inventory are supplied by you. Nothing is fetched from their URIs; no media or keys are downloaded or decoded. Inventory existence and sizes are your declarations. The report does not verify actual bandwidth, codecs, resolution, playback, synchronization, DRM, ownership, or rights.

Live/event playlists, unproven VOD, encryption, LL-HLS, unsupported tags/attributes, and query/signed URLs are rejected. URI identities use the restricted unsigned HTTP/HTTPS profile: lowercase ASCII DNS-style hosts, no port, userinfo, query, or fragment. Relative references are resolved against the supplied playlist URI; identity remains exact after literal dot-segment resolution.

Limits include one master plus up to 15 media playlists, 10,000 segment occurrences, 10,000 map declarations, 256 variants, 256 rendition declarations, 10,016 inventory entries, 40,000 physical lines, and 30,000 findings. The native bundle is at most 2,097,152 UTF-8 bytes. The native report is at most 4,194,304 bytes; combined delivery bounds may be smaller: 4 + input bytes + report bytes must not exceed 4,500,000, and the full ASCII-escaped dataset POST must also fit 4,500,000 bytes. Overbound reports are rejected whole, never truncated. Durations have at most six fractional digits and use half-up rounding for target-duration checks.

Missing supplied playlists make the report incomplete and prevent delivery. Missing segment/map inventory entries are findings in an otherwise complete report. Error findings do not by themselves prevent a completed report.

### Retrieve and extract the full report

Open the run's output link to its raw dataset items, or export the dataset as JSON with all fields and no projection or flattening. Save the resulting JSON array as `items.json`. One successful run produces one dataset item. The table preview may visually truncate long text; the full report is the JSON string in `reportJson`.

Run this self-contained Python extraction recipe. It checks the saved report bytes and hash before writing `report.json`:

```python
import hashlib
import json
from pathlib import Path

items = json.loads(Path("items.json").read_text(encoding="utf-8"))
if not isinstance(items, list) or len(items) != 1:
    raise ValueError("Expected exactly one full dataset item")
item = items[0]
if item.get("schemaVersion") != "bounded-report-delivery/1":
    raise ValueError("Unexpected dataset envelope")
raw = item["reportJson"].encode("utf-8")
if len(raw) != item["reportBytes"]:
    raise ValueError("Report byte count mismatch")
if hashlib.sha256(raw).hexdigest() != item["reportSha256"]:
    raise ValueError("Report SHA-256 mismatch")
report = json.loads(raw)
if report.get("schemaVersion") != "n31-report/1":
    raise ValueError("Unexpected report schema")
if report.get("status") != "complete" or report.get("eligibleForCompleteReport") is not True:
    raise ValueError("Report is not complete")
Path("report.json").write_bytes(raw)
print(json.dumps(report["summary"], indent=2))
```

These checks detect file damage or inconsistent extraction; they do not authenticate a source or validate actual media. Keep `inputManifest`, `reportSha256`, `reportBytes`, `deliveryKey`, and `receipt` alongside the report for traceability. Report sections are `summary`, `playlists`, `references`, `segments`, `maps`, `findings`, and `limitations`. Raw URIs, playlist text, and labels are omitted; rows use URI SHA-256 identifiers. Findings use a playlist index, physical line, optional segment ordinal, code, and severity.

The report's inherited `charged` and `priceHypothesisUsd` fields describe the offline report format, not your account's billing state. Likewise, the dataset receipt records an expected completion event and is not settlement proof. Use the run's platform billing record for actual charges.

# Actor input Schema

## `useDemo` (type: `boolean`):

Use the included synthetic HLS bundle. Omit manifestBundleJson and omit consent or set it to false in demo mode.

## `authorizedNonpersonalNonconfidentialData` (type: `boolean`):

Confirm you are authorized to submit this nonpersonal, nonconfidential manifest bundle. Must be true for your own input.

## `manifestBundleJson` (type: `string`):

Exact strict UTF-8 JSON text with schemaVersion n31-input/1, one master, supplied media playlists and declared resource inventory. At most 2097152 UTF-8 bytes. Own-input mode only.

## Actor input object example

```json
{
  "useDemo": true,
  "authorizedNonpersonalNonconfidentialData": false
}
```

# Actor output Schema

## `results` (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 = {
    "useDemo": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("immemorial_sarinda/streaming-manifest-qc").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 = { "useDemo": True }

# Run the Actor and wait for it to finish
run = client.actor("immemorial_sarinda/streaming-manifest-qc").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 '{
  "useDemo": true
}' |
apify call immemorial_sarinda/streaming-manifest-qc --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,immemorial_sarinda/streaming-manifest-qc"
        }
    }
}
```

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/X6JJXAyAhe5T4rpk4/builds/1ablF1h6FvDtGwg26/openapi.json
