# YouTube AI Video Classifier - IAB & Custom Labels (`fetchfinch/youtube-content-classifier`) Actor

Classify public YouTube videos into IAB 3.1 or custom labels with source-attributed transcript evidence, metadata signals, timestamps, confidence, and provenance.

- **URL**: https://apify.com/fetchfinch/youtube-content-classifier.md
- **Developed by:** [Fetch Finch](https://apify.com/fetchfinch) (community)
- **Categories:** AI, Videos, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$12.00 / 1,000 evidence-backed video classifications

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?

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

## YouTube AI Video Classifier — IAB & Custom Labels

Classify public YouTube videos into the top level of IAB Content Taxonomy 3.1 or your own labels. Every label identifies whether it is supported by transcript evidence, public video metadata, or both. Transcript timestamps and metadata excerpts are resolved against trusted source data rather than generated freely by the AI.

This is a focused validation release. It accepts direct video URLs and IDs only and deliberately limits each run to 25 videos.

### What makes the output useful

- IAB 3.1 IDs or stable customer-supplied label IDs
- One primary and up to two secondary labels
- Source attribution for every label: transcript, metadata, or both
- Timestamped transcript excerpts and validated metadata signals
- Transcript language, automatic/manual status, and analyzed coverage
- Qualitative confidence and explicit `insufficientEvidence`
- Model, prompt, classifier, taxonomy, source, and cache provenance
- Item-level failures: one unavailable video does not fail the batch
- No YouTube API key or Gemini key required from the customer

This Actor classifies content for dataset enrichment, contextual discovery, research, cataloging, and routing. It does **not** detect sponsorships, brand mentions, brand-safety risks, viewer demographics, or monitor new uploads.

### Input

#### IAB 3.1 top-level classification

```json
{
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/shorts/VIDEO_ID"
    ],
    "taxonomy": "iab-3.1",
    "preferredTranscriptLanguages": ["en", "de"],
    "maxTranscriptCharacters": 24000,
    "concurrency": 2
}
```

The initial release implements the 37 top-level IAB 3.1 rows. Leaf-level classification is intentionally deferred until demand and accuracy are validated. The output always exposes `taxonomy.scope: "top-level"` so integrations cannot mistake it for leaf-level coverage.

#### Custom taxonomy

```json
{
    "videoUrls": ["dQw4w9WgXcQ"],
    "taxonomy": "custom",
    "customLabels": [
        {
            "id": "tutorial",
            "name": "Tutorial",
            "description": "The presenter teaches a repeatable task step by step.",
            "examples": ["coding walkthrough", "repair instructions"],
            "exclusions": ["brief product demonstration without instruction"]
        },
        {
            "id": "review",
            "name": "Review",
            "description": "The presenter evaluates a product, work, or service.",
            "examples": ["hands-on product assessment"],
            "exclusions": ["uncritical announcement"]
        }
    ]
}
```

Custom taxonomies require 2–25 unique labels. Definitions are important: ambiguous labels produce ambiguous classifications.

### Example result

```json
{
    "status": "SUCCEEDED",
    "input": "https://www.youtube.com/watch?v=EXAMPLE_ID",
    "video": {
        "videoId": "EXAMPLE_ID",
        "url": "https://www.youtube.com/watch?v=EXAMPLE_ID",
        "title": "Build a REST API",
        "channelName": "Example Developer"
    },
    "taxonomy": {
        "type": "iab-3.1",
        "version": "3.1",
        "scope": "top-level"
    },
    "classifications": [
        {
            "id": "596",
            "name": "Technology & Computing",
            "rank": "primary",
            "supportSources": ["transcript", "metadata"],
            "evidence": [
                {
                    "segmentId": "S0004",
                    "startMs": 72000,
                    "endMs": 106000,
                    "startTimestamp": "1:12",
                    "endTimestamp": "1:46",
                    "text": "We will create the API routes and connect them to the database..."
                }
            ],
            "metadataEvidence": [
                {
                    "field": "title",
                    "value": "Build a REST API"
                }
            ]
        }
    ],
    "confidence": "high",
    "summary": "A step-by-step tutorial for building a REST API.",
    "topics": ["REST APIs", "routing", "databases"],
    "insufficientEvidence": false,
    "transcript": {
        "languageCode": "en",
        "languageName": "English",
        "isAutoGenerated": false,
        "segmentCount": 184,
        "analyzedWindowCount": 31,
        "coverageRatio": 1,
        "samplingStrategy": "full"
    },
    "provenance": {
        "sources": ["youtube-public-metadata", "youtube-public-captions"],
        "extractor": "yt-dlp-2026.07.04",
        "model": "gemini-3.5-flash-lite",
        "classifierVersion": "0.4.0",
        "promptVersion": "youtube-content-classifier-relay-3",
        "classifiedAt": "2026-08-12T12:00:00.000Z",
        "cacheHit": false
    }
}
```

Failed items produce `status: "FAILED"`, a stable `errorCode`, a readable message, and `retryable`. They are not billable.

### Source-attributed evidence and long transcripts

Each classification includes `supportSources`, which is `transcript`, `metadata`, or both. Transcript captions are combined into deterministic evidence windows such as `S0004`; the model may select only supplied IDs, and the Actor resolves them back to trusted text and timestamps. Metadata evidence contains a field name and an exact value or excerpt. Both the relay and Actor reject metadata text that is not present in the extracted title, description, channel name, declared category, or tags.

A label may be metadata-only when the transcript does not directly establish it. For example, song lyrics are not accepted as transcript proof that a video is a pop-culture meme; an exact title, description, category, or tag must support that claim instead.

When a transcript exceeds `maxTranscriptCharacters`, windows are selected uniformly from the beginning through the end. `coverageRatio` describes the fraction of transcript-window characters analyzed; it is not an accuracy score.

### Limitations

- Only public videos with accessible captions can receive an evidence classification.
- Private, deleted, members-only, login-walled, and some age-restricted videos are unsupported.
- Live streams may not have a finalized caption track.
- IAB mode covers top-level Content Taxonomy 3.1 categories in this release.
- AI labels are inferences, not YouTube-provided categories or measured audience attributes.
- YouTube may block cloud IPs. Configure an Apify residential proxy if direct extraction becomes unreliable.
- The Actor returns short evidence excerpts, not complete transcripts.

### Maintainer setup

The customer never supplies an AI credential. Gemini is accessed through the separately deployed hardened relay in [`relay/`](./relay/README.md); the Gemini key exists only on that static-IP server and is never placed in the Actor.

The Actor needs an encrypted relay URL and one HMAC credential. Follow the relay deployment guide to generate and configure them, then store them with `apify secrets add` as:

- `youtubeClassifierRelayUrl`
- `youtubeClassifierRelayCredential`

`.actor/actor.json` maps these encrypted values to the Actor environment. The credential signs a narrow classification request; it cannot call Gemini directly or select arbitrary prompts, models, tools, or upstream URLs.

For local development, provide input without putting the key in the command line:

```bash
apify run --input '{"videoUrls":["dQw4w9WgXcQ"],"taxonomy":"iab-3.1"}'
```

Important environment settings:

| Variable | Default | Purpose |
| --- | --- | --- |
| `CLASSIFIER_RELAY_URL` | required | HTTPS URL of the maintainer's hardened relay |
| `CLASSIFIER_RELAY_CREDENTIAL` | required | Encrypted JSON containing the relay HMAC key ID and base64 secret |
| `CLASSIFIER_RELAY_TIMEOUT_MS` | `75000` | Actor-to-relay request timeout |
| `ACTOR_DAILY_CLASSIFICATION_LIMIT` | `90` | Secondary, best-effort Actor-side request guard; `0` disables it |
| `YT_DLP_PATH` | `yt-dlp` | Optional override for the pinned caption-extractor binary |

The Actor-side guard counts uncached relay attempts, including retries, and resets at midnight Pacific time. It is defense in depth only. The relay is authoritative and centrally enforces durable daily, per-minute, concurrency, payload, and idempotency limits across Actor runs.

#### Paid Actor event

Configure pay-per-event monetization in Apify Console:

| Event | Suggested validation price | When emitted |
| --- | ---: | --- |
| `evidence-classification` | `$0.012` | One schema-valid classification successfully stored |

Do not enable a synthetic default-dataset-item charge. Error rows use an uncharged dataset write. The code couples each successful dataset write with `evidence-classification` via `Actor.pushData(result, eventName)`.

### Development

```bash
npm install
npm run build
npm test
npm run lint
npm run format:check
apify validate-schema .actor/input_schema.json
```

The Docker image pins and verifies `yt-dlp` version `2026.07.04`. Update the version and SHA-256 together after running the live caption canary tests.

### Data and policy notes

This unofficial Actor is not affiliated with YouTube, Google, or IAB Tech Lab. It accesses public metadata and captions, does not use account cookies, and does not attempt to bypass access restrictions. YouTube access can change and is subject to YouTube's terms and applicable law.

IAB identifiers and names are derived from the [IAB Tech Lab Taxonomies repository](https://github.com/InteractiveAdvertisingBureau/Taxonomies). `yt-dlp` is an independent open-source project used solely for public metadata and caption discovery.

# Actor input Schema

## `videoUrls` (type: `array`):

Public YouTube video, Shorts, Live, youtu.be URLs, or 11-character video IDs.

## `taxonomy` (type: `string`):

Use the top level of IAB Content Taxonomy 3.1 or supply your own labels.

## `customLabels` (type: `array`):

Required when taxonomy is custom. Each label needs a stable ID, name, and useful definition. Maximum 25 labels.

## `preferredTranscriptLanguages` (type: `array`):

BCP-47 language codes in preference order. Falls back to an available track.

## `maxTranscriptCharacters` (type: `integer`):

Long transcripts are sampled evenly while retaining timestamped evidence windows.

## `concurrency` (type: `integer`):

Keep this low for Gemini free-tier projects.

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

Optional. Residential proxy fallback can improve caption retrieval from cloud IPs.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "taxonomy": "iab-3.1",
  "customLabels": [],
  "preferredTranscriptLanguages": [
    "en"
  ],
  "maxTranscriptCharacters": 24000,
  "concurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `runSummary` (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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchfinch/youtube-content-classifier").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 = { "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }

# Run the Actor and wait for it to finish
run = client.actor("fetchfinch/youtube-content-classifier").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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ]
}' |
apify call fetchfinch/youtube-content-classifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fetchfinch/youtube-content-classifier"
        }
    }
}

```

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/7tavX9E1nTJ0ahp1i/builds/N6bl4orc5g9HGTBow/openapi.json
