# YouTube Creator Portfolio Planner (`zinin/youtube-creator-portfolio-planner`) Actor

Build a ranked creator shortlist from YouTube discovery datasets. Deduplicate channels, compare observed seed overlap and subscriber bands, and keep the evidence behind each creator assessment.

- **URL**: https://apify.com/zinin/youtube-creator-portfolio-planner.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Social media, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.40 / 1,000 creator assessments

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

## YouTube Creator Portfolio Planner — turn a channel-discovery batch into a ranked, evidence-backed shortlist

Feed it an existing YouTube channel-discovery dataset and get back one ranked creator assessment per unique channel — deduplicated seed overlap, a conservative subscriber estimate with its own confidence label, and an audience-band verdict — never an audience-similarity or ROI prediction.

![YouTube Creator Portfolio Planner — hero diagram](https://raw.githubusercontent.com/TimmyZinin/apify-actor-assets/d94b27ab62fe5f65d0258277507ea8d341761c8d/value-add-20260923/youtube-creator-portfolio-planner/readme-hero.webp)

This Actor does not scrape YouTube. It reads a channel-discovery batch you already have — most naturally from [YouTube Channel Lookalike Finder](https://apify.com/zinin/youtube-channel-lookalike-finder) and/or a `streamers/youtube-scraper`-shaped dataset — and consolidates it into one ranked assessment per unique candidate channel: how many of your seed channels recommended it, a conservative subscriber estimate, whether it falls inside the audience-size band you configured, and a shortlist rank. It is a creator-partnerships research tool for teams working from a discovery batch they already collected — it is not an audience-similarity score, a demographics engine, or a claim about campaign ROI.

### What you get

For each distinct candidate channel found across your source batch, you get one assessment record containing:

- **`channelId`** and **`channelName`** — stable identity and first accepted non-empty source display name (`channelNameConflict: true` when the source reported more than one distinct name for one channel ID).
- **`seedChannels`**, **`seedOverlapCount`**, and **`duplicateSeedMentionCount`** — up to 50 actual seed channels that recommended this candidate, the true unbounded count of distinct seeds behind it, and deduplicated repeat mentions.
- **`subscriberCount`**, **`subscriberCountApproximate`**, **`subscriberCountState`**, and **`subscriberCountConflict`** — a conservative estimate (exact from a native field, otherwise floor-parsed from YouTube's own truncated "1.27M subscribers"-style text), and one of four distinct states — `observed`, `hidden`, `invalid`, or `missing` — never conflated (see **Evidence and boundaries**).
- **`audienceBand`**, **`status`**, and **`action`** — your configured band and whether this candidate falls `within_audience_band` (→ `shortlist`), `outside_audience_band` (→ `deprioritize`), or `audience_size_unknown` (→ `review`).
- **`shortlistRank`** — a deterministic rank across the whole delivered batch: band fit first, then seed overlap, then subscriber count.
- **`evidence`** — the matched value, resolving rule, and source row — up to 12 entries, subscriber evidence always retained before optional seed references, truncation itself disclosed.
- **`topics`** — always the literal string `"no_topics_available"`: never fabricated.
- **Honest diagnostics for everything else** — a row with neither a `similarChannelId` nor a `channelId`, an invalid seed identity, or a channel with no usable signal and no seeds at all are all free, separate rows.

What this Actor is explicitly **not**: no audience-overlap percentage, no demographic breakdown, no engagement or ROI prediction. `seedOverlapCount` reflects how many of *your own* seed channels recommended this candidate — not evidence the two channels share real viewers.

### Who uses it

- **Creator partnerships and influencer-marketing teams** consolidating "similar channels" discovery pulls across several seed creators into one ranked shortlist, instead of manually cross-referencing spreadsheets.
- **Talent scouts and channel researchers** who want a repeatable audience-band filter (e.g. "only channels between 100K and 2M subscribers") applied consistently across a batch.
- **Sponsorship and media-buying teams** who need a defensible, evidence-linked reason a channel made the shortlist, rather than an opaque score.
- **Data-quality teams** auditing an upstream discovery scrape, using `subscriberCountState`/`subscriberCountConflict` as a signal for how clean the underlying data is.

Not a scraper: point it at an existing discovery Dataset producing the row shape in **Input contract**, most naturally [YouTube Channel Lookalike Finder](https://apify.com/zinin/youtube-channel-lookalike-finder) (`users30d=9`, `successfulRuns30d=57`) or a `streamers/youtube-scraper`-shaped dataset (`users30d=10475`, `successfulRuns30d=2,425,614`). This Actor never starts either one, and never fetches a page from the open web.

### How to run

1. **Console (fastest for a one-off run).** Open the Actor, go to Input, paste rows into **Source records (inline)** or fill in **Source Dataset**, leave defaults, hit **Start**.
2. **From an existing discovery Dataset.** Put its Dataset ID in `datasetId` — read with your own run's permissions, no separate authorization step.
3. **Chained via Integrations.** Add this Actor as an "Actor/task run" integration on your upstream Actor; map `{{resource.defaultDatasetId}}` into this Actor's `datasetId` field explicitly (see **Integration recipes**) for a `READ` grant — a bare Dataset ID or webhook payload alone does not grant access.
4. **Via the API.** Call `POST /v2/acts/dRa5f5Qv1jdKIs5fJ/runs` (or `run-sync-get-dataset-items`) with `datasetId` (or `items`) and, for a spending ceiling, the run option `maxTotalChargeUsd`.
5. **Set your audience band.** `audienceMin`/`audienceMax` define the eligibility window (default 0 to unbounded); out-of-band candidates are still delivered, never dropped.
6. **Read the results.** One row per unique candidate lands in the run's default Dataset, sorted by `shortlistRank`, with a matching `OUTPUT`/`SUMMARY` record.

Every run is independent processing — see **Operating guide** for what that means for re-running against the same batch.

### Pricing

Pay-per-event, one event type: **`result-found`** ("Creator assessment" in the platform's pricing UI — the primary pricing noun used throughout this listing, the Dataset terminology, and the pricing record itself) — one delivered creator assessment (one unique channel). There is **no start fee**, and every free diagnostic (a missing identity, an insufficient-data row, a budget stop, an empty batch) requests no charge event at all.

| Tier | Price per creator assessment | Discount |
|---|---|---|
| Free (default) | $0.012 | — |
| Bronze | $0.0108 | 10% |
| Silver | $0.0096 | 20% |
| Gold | $0.0084 | 30% |
| Platinum | $0.0084 | 30% |
| Diamond | $0.0084 | 30% |

These are Apify's platform-wide account pricing tiers — not a volume schedule this Actor invents — assigned automatically by your account's usage; you don't select one. One creator assessment is one unique channel across the whole batch, never billed per discovery row.

**A note on the evidence in this README:** every output example below comes from the product owner's own verification runs against this Actor's accepted build — cost to the owner, not sales or customer revenue.

Set the platform's own **Max total charge (USD)** run option to cap spend. If testing the zero-paid path via a raw API call, do not rely on literal `maxTotalChargeUsd: 0` — use a small positive value below every tier's price (e.g. $0.005, below Gold/Platinum/Diamond's $0.0084) instead — see **Evidence and boundaries**.

### Input contract

| Field | Type | Default | Notes |
|---|---|---|---|
| `items` | array (JSON editor), max 1000 entries | prefilled real example | Inline source records. Ignored if `datasetId`, `runId`, or a resolvable `payload.resource` is also given. |
| `datasetId` | string (Dataset picker, READ) | — | Dataset ID from a channel-discovery run. Read with your own run's permissions. Overrides inline `items`. |
| `runId` | string, max 64 chars | — | An Actor run ID to read instead of a Dataset ID; its default Dataset resolves automatically under your own permissions — no upstream Actor started. |
| `payload` | object (JSON editor) | — | Optional `ACTOR.RUN.SUCCEEDED`-shaped payload carrying `resource.defaultDatasetId`/`resource.id`. The `datasetId` resource picker is the recommended way to grant private-Dataset access; a bare payload reference alone does not. |
| `maxInputRows` | integer | 100 | Hard cap on source records examined before grouping (1–1000). Still bounded to 4 MiB total / 64 KiB per row. |
| `maxResults` | integer | 10 | Max useful creator-assessment results this run can deliver (1–1000), independent of the platform's Max total charge option. |
| `audienceMin` | integer | 0 | Lower band bound. Below-band candidates are still delivered, marked `outside_audience_band`. |
| `audienceMax` | integer | unbounded | Upper band bound; empty means no cap. Both bounds: non-negative safe integers ≤10,000,000,000 (a disclosed engineering ceiling), `audienceMin` ≤ `audienceMax` — else rejected as invalid input. |

**Source precedence, exactly:** an explicit `datasetId` or `payload.resource.defaultDatasetId` (they must agree if both are present) beats a `runId` or `payload.resource.id`, which beats inline `items`. A dataset/run source is never merged with inline rows. A malformed or conflicting reference is rejected as invalid input, never silently downgraded to the inline example. Private-Dataset `READ` access is conditional on the resource picker or an authorized Integrations mapping — a bare Dataset ID string, or a webhook payload's `resource.defaultDatasetId` value alone, does not grant access to data you aren't already permitted to read.

**Accepted row fields** (any subset present is used; nothing here is strictly required except a channel identity — see **Evidence and boundaries** for what happens with less):

| Field | Accepted as | Role |
|---|---|---|
| `similarChannelId` | string, ≤128 chars | A discovery-edge row: recommended on a seed channel's About page. Paired with `similarChannelName`/`similarSubscriberCountText`. |
| `requestedChannelId` / `requestedChannel` | string, ≤128 chars / string | The seed channel behind this edge. Over 128 chars, it's excluded (`invalid_seed_identity`), never truncated into a false match. |
| `channelId` | string, ≤128 chars | A native enrichment row: the candidate's own ID, used when `similarChannelId` is absent. Paired with `channelName`/`numberOfSubscribers`. |
| `similarSubscriberCountText` | string | YouTube's own truncated display text (e.g. `"1.27M subscribers"`), floor-parsed, never rounded. An explicit `"hidden"` mention is a distinct disclosed-hidden signal, not an unparseable value. |
| `numberOfSubscribers` | integer | Exact subscriber count from a native source. Always preferred over display-text parsing when valid. |
| `checkedAt` | string (ISO 8601) | Resolves conflicting subscriber observations — the most recently, validly timestamped wins; a calendar-invalid timestamp never wins (see **Evidence and boundaries**). |
| `found`, `error`, `errors`, `failed`, `status` | various | `found: false`, `failed: true`, or a recognized failure-status word (`failed`/`error`/`timed-out`/`aborted`) is ALWAYS a free diagnostic, even alongside `found: true`/`partial: true` on the same row. Only a non-empty `error`/`errors` value with none of those hard-failure signals present is spared when the row also carries `found: true` **and** `partial: true`, read as disclosed incompleteness on otherwise-usable data rather than a hard failure. |

Neither `similarChannelId` nor `channelId` present on a row is a free `missing_channel_id` diagnostic — a bare video ID is deliberately never treated as a channel identity.

**Hard read bounds, always enforced regardless of `maxInputRows`:** at most 1000 source rows, at most 4 MiB of total source JSON per run, at most 64 KiB per individual row (an oversized row is dropped and disclosed). This Actor reads only the fixed `https://api.apify.com` origin with your run's own token — it never fetches an arbitrary URL and never starts an upstream Actor.

### Output examples

Every example below is a real record from an actual accepted run of this Actor's build `0.1.3` (Actor ID `dRa5f5Qv1jdKIs5fJ`, build `PvVOx0wCVVUPpuJv7`), reproduced exactly — nothing is hand-edited. Source dates come from `checkedAt` (2026-09-22T05:46:32.738Z); the runs were processed 2026-09-23 — a saved snapshot, not a live re-check.

#### 1. First useful assessment — a full, real dataset row

This run used the Actor's own real inline prefill: 5 real "similar channels" discovery rows for seed channel `@mkbhd`, default `audienceMin`/`audienceMax`. Both diagrams on this page illustrate this exact run — the same 5 real candidates, all delivered as paid results.

Actor input (the Actor's own real prefill; all 5 rows share this shape):

```json
{
  "items": [
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
      "similarChannelName": "Auto Focus",
      "similarSubscriberCountText": "1.27M subscribers",
      "widgetPosition": 2,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    }
  ]
}
```

First Dataset output row (of five delivered from this run — the same public source Dataset `3XjcYLjp2T3gGx6d2` used via `datasetId` independently confirmed the identical result, also 5 delivered):

```json
{
  "entityId": "channel:UC2J-0g_nxlwcD9JBK1eTleQ",
  "found": true,
  "status": "within_audience_band",
  "action": "shortlist",
  "confidence": "approximate_display_text",
  "sourceRowCount": 1,
  "channelNameConflict": false,
  "channelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
  "channelName": "Auto Focus",
  "seedChannels": [
    {
      "seedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "seedDisplay": "@mkbhd",
      "mentionCount": 1,
      "sourceRowIndex": 1
    }
  ],
  "seedOverlapCount": 1,
  "seedEvidenceTruncated": false,
  "duplicateSeedMentionCount": 0,
  "subscriberCount": 1270000,
  "subscriberCountApproximate": true,
  "subscriberCountHidden": false,
  "subscriberCountState": "observed",
  "subscriberCountConflict": false,
  "subscriberSelectionReason": "most_recent_checked_at",
  "audienceBand": {
    "min": 0,
    "max": null
  },
  "topics": "no_topics_available",
  "observedAt": "2026-09-22T05:46:32.738Z",
  "sourceObservationWindow": {
    "earliest": "2026-09-22T05:46:32.738Z",
    "latest": "2026-09-22T05:46:32.738Z",
    "validCount": 1,
    "totalRowCount": 1
  },
  "shortlistRank": 1,
  "evidence": [
    {
      "field": "similarSubscriberCountText",
      "value": "1.27M subscribers",
      "rule": "conservative_display_text_parse:most_recent_checked_at",
      "sourceRowIndex": 1
    },
    {
      "field": "requestedChannelId",
      "value": "@mkbhd (UCBJycsmduvYEL83R_U4JriQ)",
      "rule": "seed_recommended_candidate",
      "sourceRowIndex": 1
    }
  ],
  "businessPartial": false,
  "sourceRowIndexes": [
    1
  ],
  "partial": false,
  "sourceCoverage": {
    "complete": true,
    "reason": null,
    "droppedOversized": 0
  },
  "product": "youtube-creator-portfolio-planner",
  "schemaVersion": "1.0",
  "sourceDatasetId": null,
  "sourceRunId": null,
  "suppliedSourceRunId": null,
  "sourceRunIdVerified": null,
  "processedAt": "2026-09-23T04:44:11.135Z"
}
```

`"1.27M subscribers"` is YouTube's own truncated display text, floor-parsed to `1,270,000` — never rounded up. `shortlistRank: 1` reflects this being the top-ranked candidate in this 5-channel batch (all tied `seedOverlapCount: 1`, within the default unbounded band).

#### 2. Derived partial-flag control — same real values, an explicit incompleteness flag injected

This is **not** a demonstration of removed columns or actual upstream data incompleteness. It is the identical real discovery batch as example 1, with each source row's own `partial` field deliberately set to `true` (and `found: true` added), to exercise this Actor's disclosed-incompleteness path under a controlled, known input. Every underlying value is identical and real; only the incompleteness flag is a deliberate test control, not an observed condition from any actual scrape.

Actor input (identical to example 1, except `"partial": true, "found": true` added):

```json
{
  "items": [
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
      "similarChannelName": "Auto Focus",
      "similarSubscriberCountText": "1.27M subscribers",
      "widgetPosition": 2,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": true,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      },
      "found": true
    }
  ],
  "maxInputRows": 100,
  "maxResults": 10
}
```

Resulting row (`businessPartial: true`, `partial: true` — otherwise identical to example 1's row):

```json
{
  "entityId": "channel:UC2J-0g_nxlwcD9JBK1eTleQ",
  "found": true,
  "status": "within_audience_band",
  "action": "shortlist",
  "confidence": "approximate_display_text",
  "sourceRowCount": 1,
  "channelNameConflict": false,
  "channelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
  "channelName": "Auto Focus",
  "seedChannels": [
    {
      "seedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "seedDisplay": "@mkbhd",
      "mentionCount": 1,
      "sourceRowIndex": 1
    }
  ],
  "seedOverlapCount": 1,
  "seedEvidenceTruncated": false,
  "duplicateSeedMentionCount": 0,
  "subscriberCount": 1270000,
  "subscriberCountApproximate": true,
  "subscriberCountHidden": false,
  "subscriberCountState": "observed",
  "subscriberCountConflict": false,
  "subscriberSelectionReason": "most_recent_checked_at",
  "audienceBand": {
    "min": 0,
    "max": null
  },
  "topics": "no_topics_available",
  "observedAt": "2026-09-22T05:46:32.738Z",
  "sourceObservationWindow": {
    "earliest": "2026-09-22T05:46:32.738Z",
    "latest": "2026-09-22T05:46:32.738Z",
    "validCount": 1,
    "totalRowCount": 1
  },
  "shortlistRank": 1,
  "evidence": [
    {
      "field": "similarSubscriberCountText",
      "value": "1.27M subscribers",
      "rule": "conservative_display_text_parse:most_recent_checked_at",
      "sourceRowIndex": 1
    },
    {
      "field": "requestedChannelId",
      "value": "@mkbhd (UCBJycsmduvYEL83R_U4JriQ)",
      "rule": "seed_recommended_candidate",
      "sourceRowIndex": 1
    }
  ],
  "businessPartial": true,
  "sourceRowIndexes": [
    1
  ],
  "partial": true,
  "sourceCoverage": {
    "complete": true,
    "reason": null,
    "droppedOversized": 0
  },
  "product": "youtube-creator-portfolio-planner",
  "schemaVersion": "1.0",
  "sourceDatasetId": null,
  "sourceRunId": null,
  "suppliedSourceRunId": null,
  "sourceRunIdVerified": null,
  "processedAt": "2026-09-23T04:44:28.438Z"
}
```

Notice the assessment itself is unchanged from example 1; only `businessPartial`/`partial` flip to `true` because the source row disclosed its own incompleteness — exactly what a genuinely partial real scrape would produce.

#### 3. Below-price run — free `budget_stopped` diagnostic, no partial charge

```json
[
  {
    "product": "youtube-creator-portfolio-planner",
    "schemaVersion": "1.0",
    "status": "budget_stopped",
    "reason": "Run charge limit cannot cover any result; no source was read.",
    "found": false,
    "processedAt": "2026-09-23T04:44:41.773Z"
  }
]
```

This run used the platform run option `maxTotalChargeUsd: 0.005` — below every tier's per-assessment price (even Gold/Platinum/Diamond's $0.0084). The Actor never reads the source batch, delivers zero paid assessments, and writes this one free diagnostic. See **Evidence and boundaries** for the API-level zero-cap caveat.

#### 4. Empty batch — free `no_results` diagnostic

Actor input:

```json
{ "items": [], "maxInputRows": 100, "maxResults": 10 }
```

Dataset output:

```json
[
  {
    "product": "youtube-creator-portfolio-planner",
    "schemaVersion": "1.0",
    "status": "no_results",
    "found": false,
    "reason": "no_usable_rows_in_batch",
    "sourceCoverage": {
      "complete": true,
      "reason": null,
      "droppedOversized": 0
    },
    "processedAt": "2026-09-23T04:44:49.606Z"
  }
]
```

An empty or entirely-unusable batch is a free diagnostic, never a silent zero-row success and never a charge.

### Field dictionary

**Per-assessment result fields:**

| Field | Type | Meaning |
|---|---|---|
| `entityId` | string | `"channel:<channelId>"` — stable key for this candidate. |
| `found` | boolean | `true` on every billed assessment; `false` on every free diagnostic. |
| `status` | string | `within_audience_band`, `outside_audience_band`, or `audience_size_unknown` on an assessment; a diagnostic reason (`invalid_row`, `source_reported_failure`, `missing_channel_id`, `invalid_seed_identity`, `insufficient_data`, `invalid_input`, `budget_stopped`, `no_results`, `max_results_reached`, `result_dropped_oversized`, `source_rows_dropped`, `partial_source`, `source_unavailable`) on a free row. |
| `action` | string | `shortlist` (within band), `deprioritize` (outside band), or `review` (unknown count). |
| `channelId` / `channelName` | string | The candidate's stable ID and first accepted non-empty source display name. |
| `channelNameConflict` | boolean | `true` if the source reported more than one distinct name — the true, unbounded set, not just the up-to-5 names stored for display. |
| `seedChannels` | array | Up to 50 `{ seedChannelId, seedDisplay, mentionCount, sourceRowIndex }` entries. |
| `seedOverlapCount` | number | The TRUE, unbounded count of distinct seed channels, even when `seedChannels` display is truncated. |
| `duplicateSeedMentionCount` | number | Repeated mentions of the same seed, deduplicated out of `seedOverlapCount`. |
| `seedEvidenceTruncated` | boolean | `true` when the 12-slot evidence budget couldn't fit every seed's evidence — subscriber evidence is always retained first. |
| `subscriberCount` | number/null | The resolved estimate, or `null` when `subscriberCountState` is `hidden`, `invalid`, or `missing`. |
| `subscriberCountApproximate` | boolean | `true` when parsed from display text rather than an exact numeric field. |
| `subscriberCountState` | string | `observed`, `hidden` (source explicitly said hidden), `invalid` (present but unparseable/out of bound), or `missing` (no field at all) — never conflated. |
| `subscriberCountHidden` | boolean | Convenience flag mirroring `subscriberCountState === "hidden"`. |
| `subscriberCountConflict` | boolean | `true` when multiple rows reported different values for the same channel. |
| `subscriberSelectionReason` | string/null | `most_recent_checked_at` (valid timestamp broke the tie) or `first_source_row` (no valid timestamp existed). |
| `audienceBand` | object | `{ min, max }` — the effective band this run applied. |
| `topics` | string | Always the literal `"no_topics_available"` — never inferred or fabricated. |
| `shortlistRank` | number | 1-based rank: audience-band fit, then `seedOverlapCount`, then `subscriberCount`. |
| `evidence` | array | Up to 12 entries, each `{ field, value, rule, sourceRowIndex }`. |
| `sourceRowCount` / `sourceRowIndexes` | number / array | True count and (capped at 50) list of contributing source rows. |
| `businessPartial` / `partial` | boolean | `businessPartial` is this module's own signal (disclosed `partial`, a subscriber conflict/invalid observation, a truncated list, or more names than displayed). `partial` also folds in run-level source incompleteness — check `partial` if you only check one. |
| `sourceCoverage` | object | Run-level: `{ complete, reason, droppedOversized }`. |
| `sourceDatasetId`, `sourceRunId`, `suppliedSourceRunId`, `sourceRunIdVerified` | string/null, boolean/null | Where the batch came from; `sourceRunId` is set only when independently verified. |
| `observedAt` | string / null | Latest strictly valid source `checkedAt` among all accepted rows contributing to this channel; null if unknown. No processing-time fallback. |
| `sourceObservationWindow` | object | `earliest` and `latest` preserve source timestamp strings; `validCount` counts valid capture timestamps and `totalRowCount` counts all contributing accepted rows, before the 50-reference display cap. Missing/invalid timestamps do not advance the window. |
| `processedAt` | string (ISO 8601) | When this Actor processed the row, separate from source capture time. |
| `reason` / `error` | string | Present on free diagnostic rows only. |

**Diagnostic-only Dataset fields:** `mode` identifies the selected source mode on invalid\_input/source\_unavailable; `count` counts represented source/oversized rows; `delivered` and `totalUseful` report confirmed deliveries and candidates on cap notices. A source\_unavailable notice accompanies a FAILED run. These notices have found:false and request no result event.

**Complete runtime OUTPUT/SUMMARY field table** (the Key-value store record under both the `OUTPUT` and `SUMMARY` keys — identical payload, two conventional key names; no `metadata` field exists in this runtime's output):

| Field | Meaning |
|---|---|
| `schemaVersion` | This product's output schema version string (`"1.0"`). |
| `product` | `"youtube-creator-portfolio-planner"`. |
| `outcome` | `delivered`, `budget_stopped`, `max_results_reached`, `no_results`, `diagnostic`, or `failed`. |
| `errorCode` | Short internal code on a non-nominal outcome (e.g. `SOURCE_UNAVAILABLE`), or `null`. |
| `fatalFailure` | `true` only when `outcome === "failed"`. |
| `deliveryUncertain` | A Dataset-write or charge acknowledgment is uncertain, including FREE\_WRITE\_FAILED; raw rows can exceed confirmed counters. |
| `replayBlocked` | `true` on either of two distinct refusal causes: the run's own `result-found` event counter didn't match its expected record (`EXISTING_RESULT`), or this run's claim key was already set from a prior attempt and automatic resurrection is unsupported (`EXISTING_CLAIM`) — either way, refusing to bill further avoids a duplicate charge. |
| `requested` | Source rows actually read this run. |
| `usefulResults` | Results found after oversized-row filtering, before budget/`maxResults` cap. |
| `delivered` | Paid, confirmed assessments actually written. |
| `paid` | Confirmed `result-found` charge events, cross-checked against `delivered`. |
| `free` | Free diagnostic rows actually written — see **Evidence and boundaries** for how this can exceed the 200-row cap. |
| `failed` | Useful results dropped for exceeding the 64 KiB per-row limit (rare). |
| `sourcePartial / sourcePartialReason` | Incomplete source read: max\_input\_rows\_reached, total\_byte\_cap\_reached, oversized\_rows\_dropped, pagination\_interrupted, invalid\_json\_page, unexpected\_shape or unexpected\_page\_size; otherwise null. |
| `sourceDroppedOversized` | Source rows dropped for exceeding 64 KiB before reaching assessment logic. |
| `budgetStopped` | `true` if the charge limit was reached before all useful assessments were delivered. |
| `resultsCapped` | `true` if `maxResults` was reached before all useful assessments were delivered. |
| `businessPartialResults` | Partial transform results counted before size, budget and result-cap filtering; not a delivered-result count. |
| `oversizedDiagnosticsDropped` | Diagnostic rows dropped for exceeding 64 KiB (rare). |
| `diagnosticsTruncated` | The combined initial list of business diagnostics and source-read notices exceeded 200; later result-size/cap notices are outside this limit. |
| `partialOverall` | `true` if any of the above fired — check this one field if you only check one. |
| `budgetMode` | `unlimited`, `finite`, or `local_no_charging` (running outside the platform). |
| `maxTotalChargeUsd` | The effective run charge cap enforced, or `null` when unlimited/local. |
| `replaySafety` | Fixed disclosure string: re-running is new processing, no cross-run dedup. |
| `businessSummary` | `{ rowsProcessed, resultsCount, diagnosticsCount, withinAudienceBandCount, outsideAudienceBandCount, audienceSizeUnknownCount, uniqueSeedCount, audienceBand, observedAt, sourceObservationWindow, sourceDatasetId, sourceRunId }` — `uniqueSeedCount` is the true unbounded seed identity, not the display-capped `seedChannels` list. |
| `processedAt` | ISO 8601 timestamp of this OUTPUT/SUMMARY write. |

### Evidence and boundaries

**Two distinct identity paths, never confused.** A discovery-edge row (`similarChannelId` present) and a native channel-enrichment row (`channelId` present, no `similarChannelId`) are handled by entirely separate logic — a bare video ID is never treated as a channel identity, and a row with neither is a free `missing_channel_id` diagnostic.

**Seed overlap is always counted on the true, unbounded set — never on the display slice.** `seedOverlapCount` reflects every distinct seed channel actually observed for a candidate, even when the displayed `seedChannels` array is capped at 50 — ranking uses the true count, not the display prefix.

**Subscriber evidence is never crowded out by seed evidence.** The core subscriber-resolution evidence entry is always pushed into the 12-slot evidence budget before any individual seed-recommendation evidence, and a `seed_evidence_truncated` disclosure entry reserves its own slot rather than silently dropping.

**A seed ID over the 128-character identity bound never contaminates the real count.** It is excluded outright before overlap counting, summary totals, or output — it produces only an `invalid_seed_identity` diagnostic, never quietly counted toward `seedOverlapCount`.

**Four subscriber states, never conflated.** `hidden` means the source explicitly said so (an actual `"...subscribers hidden"`-style signal); `invalid` means a subscriber field was present but failed to parse or exceeded the disclosed 10-billion engineering bound; `missing` means no subscriber field was present in any accepted row for this channel at all; `observed` means a usable value was resolved (exact or approximate). A hidden-count channel is never relabeled as missing, and vice versa.

The channel observation window covers every accepted source row, including seed-discovery rows without a subscriber field. Source-failed rows do not contribute. The batch `businessSummary.observedAt` and `businessSummary.sourceObservationWindow` describe all accepted channel observations; their scope is broader than a single delivered result. Capture timestamps are validated for calendar, timezone and up to nine fractional digits, with exact fractional ordering. Equivalent-instant window ties use a deterministic raw-string choice. This window describes source capture, not a guarantee that every selected field was measured simultaneously: the name can still come from the first accepted observation, while subscriber values follow the timestamp selection rule below. Dates are never invented from job execution time.

**Conflict resolution never fabricates freshness.** When multiple rows report different subscriber values for one channel, the candidate with the highest *calendar-valid* `checkedAt` wins — a lexically larger but impossible date (like a rolled-forward February 30th) never beats a genuinely valid earlier timestamp, since every component is checked against the real calendar, not just string comparison. When no candidate has any valid timestamp, the first source row wins deterministically, disclosed as `first_source_row`, never silently presented as `most_recent_checked_at`.

**Hard failures always override a contradictory `found:true, partial:true` claim.** A row with a genuinely disqualifying failure signal is treated as a diagnostic even if it also carries `found: true`/`partial: true`.

**Bounded, disclosed caps, applied only to display, never to the underlying count:** `seedChannels` capped at 50 per candidate (`seedOverlapCount` is the true total); `evidence` capped at 12 entries; `sourceRowIndexes` capped at 50 (`sourceRowCount` is the true total); `channelNames` display capped at 5 (`channelNameConflict` reflects the true, unbounded distinct-name set); bounded text fields capped at 300 characters.

**The initial diagnostic list writes at most 200 entries; total free rows can exceed that.** This list combines business diagnostics with partial\_source and source\_rows\_dropped notices. businessSummary.diagnosticsCount counts business diagnostics only, while diagnosticsTruncated covers the combined initial list. Later oversized-result and result/budget-cap notices are outside that cap. Verified against a real 1,000-row batch (995 synthetic `source_reported_failure` control rows plus the real 5-channel batch, `maxResults: 1`): exactly 200 business diagnostics were written, one further `max_results_reached` notice was added, for **201 total free rows** (`freeRows: 201` in that run's own `settled.json`) — never described as "capped at 200 total." The single correctly-ranked paid result (`Auto Focus`) was still delivered correctly, untouched by the 995 preceding synthetic failures.

**Budget behavior — read this before relying on a specific `maxTotalChargeUsd` value via the API.** A positive `maxTotalChargeUsd` below the price of one assessment correctly delivers zero paid assessments and one free `budget_stopped` diagnostic (output example 3, at $0.005 against a $0.0084 floor tier). Separately, the platform's API-level cap-normalization behavior means a raw `maxTotalChargeUsd: 0` set via a direct API call is not guaranteed to be honored as a literal zero-spend instruction. **When testing the zero-paid path via a direct API call, do not rely on the literal value `0` — inspect the run's actual, effective `options.maxTotalChargeUsd` after it starts, and prefer a small positive value below every applicable tier's price** (as in example 3) for certainty.

**Delivery is not transactional, and a `FAILED` run does not mean "nothing happened."** Each assessment is written to the Dataset and then charged as two separate steps; a run failing between them can leave an effectively free, unconfirmed write with no automatic retry — no cross-run exactly-once guarantee. `usefulResults` reflects assessments found before budget/result-cap application; `failed` reflects oversized-row drops (over 64 KiB); `paid`/`delivered` reflect only confirmed, charged deliveries. If pricing, charge-limit, or event-counter state can't be verified mid-run, the run finalizes `FAILED` with a safe explanation — `fatalFailure`/`deliveryUncertain`/`partialOverall` tell you which situation applies. **Inspect the original run's Dataset and `OUTPUT`/`SUMMARY` record before a manual retry** — a blind retry after an ambiguous outcome can duplicate paid assessments, since this Actor does not deduplicate across runs.

### Decision routing

- **`status: within_audience_band`, `action: shortlist`** — resolved subscriber count falls inside your band. `shortlistRank` orders these by seed overlap, then subscriber count.
- **`status: outside_audience_band`, `action: deprioritize`** — a resolved count exists but falls outside your band. Still delivered, ranked after within-band and unknown-audience candidates.
- **`status: audience_size_unknown`, `action: review`** — no subscriber value resolved (`subscriberCountState` is `hidden`, `invalid`, or `missing`). Ranked after within-band candidates and before outside-band candidates.
- **`subscriberCountConflict: true`** — inconsistent values across rows for the same channel. Check `subscriberSelectionReason` for how it resolved.
- **`businessPartial: true` / `partial: true`** — disclosed incompleteness, a conflict/invalid observation, or a display cap was hit — worth a second glance.
- **Free diagnostic rows (`found: false`)** — never an assessment. `missing_channel_id`, `insufficient_data`, `invalid_seed_identity` explain why a row was excluded; `budget_stopped`/`max_results_reached` mean a run-level cap was hit — re-run with a higher cap.

### Commercial playbooks

**Cross-seed shortlist consolidation.** Run a discovery pull against several seed creators at once and let this Actor deduplicate every candidate to one ranked row per channel — `seedOverlapCount` shows which candidates were recommended by more than one seed.

**Audience-band filtering per campaign tier.** Set `audienceMin`/`audienceMax` to your target range and treat `action: shortlist` rows as your working list — `outside_audience_band` rows stay visible for a future tier, never discarded.

**Escalating "review" candidates.** Filter to `action: review` for a worklist of channels whose subscriber count couldn't be resolved — distinct from channels that are simply outside your band.

**Data-quality triage.** A high proportion of `invalid`/`missing` subscriber states signals your upstream discovery source is inconsistently capturing subscriber data — independent of what this Actor delivers.

### Integration recipes

![YouTube Creator Portfolio Planner — workflow diagram](https://raw.githubusercontent.com/TimmyZinin/apify-actor-assets/d94b27ab62fe5f65d0258277507ea8d341761c8d/value-add-20260923/youtube-creator-portfolio-planner/readme-workflow.webp)

This Actor's ID is **`dRa5f5Qv1jdKIs5fJ`**. A natural upstream source is [YouTube Channel Lookalike Finder](https://apify.com/zinin/youtube-channel-lookalike-finder) (Actor ID `sMokIFjXOK7W7zbfU`); a `streamers/youtube-scraper`-shaped native channel source works identically. The recipes below describe how to configure each mechanism — not a claim that any specific trigger is currently installed. A saved public example is available in the Tasks tab.

**1. Console-native chaining (no code).** On your discovery Actor's run page, open **Integrations**, add an "Actor/task run" action targeting this Actor, and let the Integrations UI populate the payload with `{{resource.defaultDatasetId}}`. Map that value explicitly into this Actor's `datasetId` field — not the generic `payload` pass-through alone — to request READ access under LIMITED\_PERMISSIONS; the caller must already be authorized, and a private source can still deny access.

**2. Direct webhook (`POST /v2/webhooks`), full recipe.** Configure a webhook on your upstream discovery Actor for the `ACTOR.RUN.SUCCEEDED` event, scoped to that specific Actor via its `condition.actorId`, targeting this Actor's run-creation endpoint:

```json
{
  "isApifyIntegration": true,
  "shouldInterpolateStrings": true,
  "eventTypes": ["ACTOR.RUN.SUCCEEDED"],
  "condition": { "actorId": "sMokIFjXOK7W7zbfU" },
  "requestUrl": "https://api.apify.com/v2/acts/dRa5f5Qv1jdKIs5fJ/runs?maxTotalChargeUsd=0.50",
  "doNotRetry": true,
  "headersTemplate": "{\"Authorization\": \"Bearer <APIFY_TOKEN>\"}",
  "payloadTemplate": "{\"datasetId\": \"{{resource.defaultDatasetId}}\", \"payload\": {\"resource\": {\"defaultDatasetId\": \"{{resource.defaultDatasetId}}\", \"id\": \"{{resource.id}}\"}}, \"audienceMin\": 100000}"
}
```

Submit this to `POST https://api.apify.com/v2/webhooks` with your own `Authorization: Bearer <APIFY_TOKEN>` header. The `<APIFY_TOKEN>` placeholder is never a literal token embedded in the template — it is filled in from your own secrets-provisioning store at the moment the webhook is registered. `doNotRetry: true` avoids a duplicate-charge risk from an automatic retry. `datasetId` is set explicitly — the resource picker or authorized mapping can request READ access, but the field alone grants nothing; payload.resource alone does not establish access. A positive `maxTotalChargeUsd` on the request URL caps this chained run's spend; avoid the literal value `0` (see **Evidence and boundaries**).

**3. Generic automation tools (n8n, Zapier, Make, or your own tooling).** Use a generic Apify/HTTP node to call `run-sync-get-dataset-items` on this Actor's ID with a JSON body containing `datasetId` and your chosen `audienceMin`/`audienceMax`/`maxResults` — a plain authenticated HTTPS POST, no native Apify-integration support required. The same `isApifyIntegration`/`payloadTemplate` shape from recipe 2 applies to any integration definition your own tooling submits.

**4. Export / CRM-adjacent mapping.** Every run's Dataset supports CSV/Excel/JSON export via the Console or `GET /v2/datasets/{datasetId}/items?format=csv`. `channelId`, `channelName`, `seedOverlapCount`, `subscriberCount`, `status`, and `shortlistRank` alone drive a working creator shortlist in a spreadsheet or CRM import.

### Operating guide

1. **Confirm your source batch shape first.** Spot-check rows against **Input contract** — each needs either `similarChannelId` or `channelId`, or you'll mostly get `missing_channel_id` diagnostics.
2. **Set your audience band deliberately.** Both in-band and out-of-band candidates are always delivered and billed — a narrow band changes `action` labels, not your bill.
3. **Start with a small `maxResults` on a new source** to confirm output matches expectations before a full batch.
4. **Set `maxTotalChargeUsd` via the platform run option, and check its effective value after the run starts.** For a guaranteed-zero-paid API call, use a small positive value below $0.0084, not literal `0` — see **Evidence and boundaries**.
5. **Read the `OUTPUT`/`SUMMARY` record, not just the Dataset row count** — it reports `delivered`/`paid`/`free`/`failed`, whether `budgetStopped`/`resultsCapped` ended the run early, and `diagnosticsTruncated`.
6. **If a run finalizes `FAILED`, inspect its Dataset and `OUTPUT` record before retrying** — a blind retry can duplicate already-successful paid assessments.
7. **Re-running is always new billing** — no cross-run dedup key. Reserve it for a new or updated batch, not a routine safety retry.

### FAQ

**Does this Actor scrape YouTube itself?** No. It only reads an existing channel-discovery batch you or an upstream Actor already collected — no open-web fetch, no upstream Actor started.

**Can I use a source other than YouTube Channel Lookalike Finder?** Yes — any source producing `similarChannelId` discovery-edge rows or `channelId` native enrichment rows works, including a `streamers/youtube-scraper`-shaped dataset.

**Does `seedOverlapCount` mean the two channels share real viewers?** No. It reflects how many of your seed channels' "similar channels" widgets recommended this candidate — a discovery-overlap signal, not audience similarity.

**What's the difference between `hidden`, `invalid`, and `missing` subscriber states?** `hidden` is an explicit source signal; `invalid` is a present-but-unparseable/out-of-bound field; `missing` is no field at all. Never conflated with each other.

**Does re-running the same batch avoid double-billing?** No. Every run is independent with no cross-run deduplication.

**Is the "partial-columns" style example a demonstration of missing upstream data?** No — see output example 2. Same real values as the healthy example; only `partial`/`found` are deliberately set as a controlled test, not a simulation of real column removal.

**Why did one run report 201 free rows instead of the usual 200-row diagnostic cap?** The initial business-diagnostic plus source-read-notice list is capped at 200 entries, but later result-size/cap notices are additional — see **Evidence and boundaries**.

### Sources and rights

This Actor reads only Datasets and runs your own Apify account token already has permission to read; it never uses another user's credentials and never starts an upstream Actor on your behalf. The public prefilled example is a curated, non-sensitive real snapshot of a YouTube channel-discovery run for seed channel `@mkbhd` (`sourceActorId sMokIFjXOK7W7zbfU`, `sourceRunId cJ6Tezu4odFHY9tDM`, `sourceDatasetId wm2PnJsPBGeG68p3T`), observed 2026-09-22, reused with its original values unchanged — a saved example, not a live re-check. All output values in this README are copied verbatim from real runs of this Actor's accepted build; none are illustrative or invented. These verification runs were the product owner's own cost, not customer sales or revenue evidence.

#### Related tools

Related tools for adjacent workflows in social platforms and regional directories.

| Actor | What it does |
|---|---|
| [YouTube Channel Lookalike Finder](https://apify.com/zinin/youtube-channel-lookalike-finder) | Pair it in the social platforms and regional directories workflow: Find the similar/lookalike channels YouTube itself shows on any channel's About page: channelId, name,... |
| [YouTube Channel Videos List](https://apify.com/zinin/youtube-channel-videos-list) | Pair it in the social platforms and regional directories workflow: List up to 100 most recent uploads for any YouTube channel: videoId, title, view count, and publish age... |
| [YouTube Channel Intel](https://apify.com/zinin/youtube-channel-intel) | Pair it in the social platforms and regional directories workflow: Public intel card for any YouTube channel: subscribers, total views, video count, join date, country, full... |

| [YouTube Scraper](https://apify.com/streamers/youtube-scraper) | Collect video research before assembling a channel batch; map channel fields to this Actor's input contract. |
| [Fast YouTube Channel Scraper](https://apify.com/streamers/youtube-channel-scraper) | Collect channel research for a later assessment; validate fields against this Actor's contract before chaining. |

# Actor input Schema

## `items` (type: `array`):

Small real public example: https://api.apify.com/v2/datasets/3XjcYLjp2T3gGx6d2/items?clean=true . This saved snapshot is not a new scrape.

## `datasetId` (type: `string`):

Read an existing source dataset with your run permissions. Overrides inline items. The resource picker can request scoped READ permission. Integration mapping must use an authorized resource; a plain Dataset ID or webhook payload alone does not grant access. Public example: 3XjcYLjp2T3gGx6d2.

## `runId` (type: `string`):

Optional Actor run ID to read instead of Source Dataset. Its default Dataset is resolved with your run's own permissions — no elevated access is used, and no upstream Actor is started.

## `maxInputRows` (type: `integer`):

Maximum source records examined before grouping. At most 1000 rows, 4 MiB total and 64 KiB per row. Capped or incomplete source coverage is reported.

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

Maximum useful creator assessment results delivered, independently of the platform Max total charge setting. Free diagnostics are reported separately.

## `payload` (type: `object`):

Optional ACTOR.RUN.SUCCEEDED event payload with resource.defaultDatasetId and resource.id. Dataset resourcePicker is recommended for private datasets; a payload alone does not grant access.

## `audienceMin` (type: `integer`):

Lower bound of the audience-size eligibility band. Candidates below this are marked outside\_audience\_band, not discarded. Default 0 (full range).

## `audienceMax` (type: `integer`):

Upper bound of the audience-size eligibility band. Leave empty for no upper bound (full range). Candidates above this are marked outside\_audience\_band, not discarded.

## Actor input object example

```json
{
  "items": [
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCG7J20LhUeLl6y_Emi7OJrA",
      "similarChannelName": "The Studio",
      "similarSubscriberCountText": "1.15M subscribers",
      "widgetPosition": 1,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
      "similarChannelName": "Auto Focus",
      "similarSubscriberCountText": "1.27M subscribers",
      "widgetPosition": 2,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCEcrRXW3oEYfUctetZTAWLw",
      "similarChannelName": "WVFRM Podcast",
      "similarSubscriberCountText": "529K subscribers",
      "widgetPosition": 3,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCc_ycus0q7DSd85ZkmEY5ig",
      "similarChannelName": "Waveform Clips",
      "similarSubscriberCountText": "544K subscribers",
      "widgetPosition": 4,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCKQCaDW4LqL3l1ai8yisvUw",
      "similarChannelName": "20syl",
      "similarSubscriberCountText": "233K subscribers",
      "widgetPosition": 5,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    }
  ],
  "maxInputRows": 100,
  "maxResults": 10,
  "audienceMin": 0
}
```

# Actor output Schema

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

API URL for the default dataset items produced by this run.

## `summary` (type: `string`):

API URL for the OUTPUT record in the default key-value store.

# 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 = {
    "items": [
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCG7J20LhUeLl6y_Emi7OJrA",
            "similarChannelName": "The Studio",
            "similarSubscriberCountText": "1.15M subscribers",
            "widgetPosition": 1,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": false,
            "action": "ingest",
            "error": null,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": true,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
            }
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
            "similarChannelName": "Auto Focus",
            "similarSubscriberCountText": "1.27M subscribers",
            "widgetPosition": 2,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": false,
            "action": "ingest",
            "error": null,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": true,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
            }
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCEcrRXW3oEYfUctetZTAWLw",
            "similarChannelName": "WVFRM Podcast",
            "similarSubscriberCountText": "529K subscribers",
            "widgetPosition": 3,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": false,
            "action": "ingest",
            "error": null,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": true,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
            }
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCc_ycus0q7DSd85ZkmEY5ig",
            "similarChannelName": "Waveform Clips",
            "similarSubscriberCountText": "544K subscribers",
            "widgetPosition": 4,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": false,
            "action": "ingest",
            "error": null,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": true,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
            }
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCKQCaDW4LqL3l1ai8yisvUw",
            "similarChannelName": "20syl",
            "similarSubscriberCountText": "233K subscribers",
            "widgetPosition": 5,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": false,
            "action": "ingest",
            "error": null,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": true,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/youtube-creator-portfolio-planner").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 = { "items": [
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCG7J20LhUeLl6y_Emi7OJrA",
            "similarChannelName": "The Studio",
            "similarSubscriberCountText": "1.15M subscribers",
            "widgetPosition": 1,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": False,
            "action": "ingest",
            "error": None,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": True,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities.",
            },
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
            "similarChannelName": "Auto Focus",
            "similarSubscriberCountText": "1.27M subscribers",
            "widgetPosition": 2,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": False,
            "action": "ingest",
            "error": None,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": True,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities.",
            },
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCEcrRXW3oEYfUctetZTAWLw",
            "similarChannelName": "WVFRM Podcast",
            "similarSubscriberCountText": "529K subscribers",
            "widgetPosition": 3,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": False,
            "action": "ingest",
            "error": None,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": True,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities.",
            },
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCc_ycus0q7DSd85ZkmEY5ig",
            "similarChannelName": "Waveform Clips",
            "similarSubscriberCountText": "544K subscribers",
            "widgetPosition": 4,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": False,
            "action": "ingest",
            "error": None,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": True,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities.",
            },
        },
        {
            "requestedChannel": "@mkbhd",
            "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
            "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
            "similarChannelId": "UCKQCaDW4LqL3l1ai8yisvUw",
            "similarChannelName": "20syl",
            "similarSubscriberCountText": "233K subscribers",
            "widgetPosition": 5,
            "source": "about-page-similar-channels-widget",
            "status": "ok",
            "confidence": "display-text",
            "partial": False,
            "action": "ingest",
            "error": None,
            "checkedAt": "2026-09-22T05:46:32.738Z",
            "_exampleProvenance": {
                "sourceActorId": "sMokIFjXOK7W7zbfU",
                "sourceRunId": "cJ6Tezu4odFHY9tDM",
                "sourceDatasetId": "wm2PnJsPBGeG68p3T",
                "curatedPublicSnapshot": True,
                "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities.",
            },
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("zinin/youtube-creator-portfolio-planner").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 '{
  "items": [
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCG7J20LhUeLl6y_Emi7OJrA",
      "similarChannelName": "The Studio",
      "similarSubscriberCountText": "1.15M subscribers",
      "widgetPosition": 1,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UC2J-0g_nxlwcD9JBK1eTleQ",
      "similarChannelName": "Auto Focus",
      "similarSubscriberCountText": "1.27M subscribers",
      "widgetPosition": 2,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCEcrRXW3oEYfUctetZTAWLw",
      "similarChannelName": "WVFRM Podcast",
      "similarSubscriberCountText": "529K subscribers",
      "widgetPosition": 3,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCc_ycus0q7DSd85ZkmEY5ig",
      "similarChannelName": "Waveform Clips",
      "similarSubscriberCountText": "544K subscribers",
      "widgetPosition": 4,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    },
    {
      "requestedChannel": "@mkbhd",
      "requestedChannelId": "UCBJycsmduvYEL83R_U4JriQ",
      "requestedCanonicalChannelUrl": "http://www.youtube.com/@mkbhd",
      "similarChannelId": "UCKQCaDW4LqL3l1ai8yisvUw",
      "similarChannelName": "20syl",
      "similarSubscriberCountText": "233K subscribers",
      "widgetPosition": 5,
      "source": "about-page-similar-channels-widget",
      "status": "ok",
      "confidence": "display-text",
      "partial": false,
      "action": "ingest",
      "error": null,
      "checkedAt": "2026-09-22T05:46:32.738Z",
      "_exampleProvenance": {
        "sourceActorId": "sMokIFjXOK7W7zbfU",
        "sourceRunId": "cJ6Tezu4odFHY9tDM",
        "sourceDatasetId": "wm2PnJsPBGeG68p3T",
        "curatedPublicSnapshot": true,
        "redaction": "Allowlisted public business/product fields only; review excerpts and social captions truncated; no reviewer identities."
      }
    }
  ]
}' |
apify call zinin/youtube-creator-portfolio-planner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/youtube-creator-portfolio-planner"
        }
    }
}
```

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/dRa5f5Qv1jdKIs5fJ/builds/RcLCINskM6bcDmTpc/openapi.json
