# TikTok & YouTube Shorts Script Generator (`optirefine/short-form-script-generator`) Actor

Transform RSS feeds and raw notes into publish-ready TikTok and Shorts scripts. Includes optional ElevenLabs voiceovers and a built-in AI worker so you can start generating immediately or connect your own model.

- **URL**: https://apify.com/optirefine/short-form-script-generator.md
- **Developed by:** [OptiRefine](https://apify.com/optirefine) (community)
- **Categories:** AI, Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 85.7% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 scripts

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/platform/actors/running/actors-in-store#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

>

**TikTok & YouTube Shorts Script Generator**
transforms trending RSS articles, raw statistics, or your own custom notes into structured, high-retention short-form video scripts. It automatically generates a captivating hook, engaging body beats, and a strong call-to-action (CTA), each paired with visual cues and exact timestamps.

**No API key, no account, no setup.** Script generation is built in and runs on an embedded Llama 3.1 8B model. Pick a category, press Start, and get scripts ready for production.

### ✨ Key Features & Benefits

- ⚡ **Ready-to-Record:** Outputs segmented scripts optimized for the fast pacing of TikTok, YouTube Shorts, and Instagram Reels.
- 🎙️ **Optional AI Audio:** Paste your ElevenLabs API key to automatically generate perfectly timed `.mp3` voiceovers.
- 🧠 **Free AI Inference:** Runs entirely on Llama 3.1 8B. You do not pay any extra API costs for AI generation.
- 🗂️ **Multi-Niche Personas:** Tailored prompt structures for Tech, Gaming, News, Beauty, Sports, and more.
- 🛡️ **Risk-Free Runs:** Failed generations (unparseable AI text) are not billed. You only pay for successful, usable data.

### 🎯 Use Cases

- **Faceless Channels:** Connect an RSS feed and let the Actor write your daily video content automatically.
- **Sports & Analytics Creators:** Instantly convert raw match data into punchy player breakdowns and tactical analysis scripts.
- **Marketing Agencies:** Generate bulk scripts across different client niches in a single automated run.

### 🚀 How to Run (Input)

Pick a mode. That is the whole configuration.

**Curated RSS** — choose categories and the Actor pulls recent articles:

```json
{
  "mode": "rss",
  "categories": ["tech"],
  "maxItems": 10
}

```

**Your own input** — supply the material directly:

```json
{
  "mode": "direct",
  "items": [
    { 
      "category": "tech", 
      "topic": "A hidden phone setting",
      "data": "Most phones ship with a battery saver that is off by default." 
    }
  ]
}

```

#### Optional: add voiceover

Set `elevenLabsApiKey` and every segment is also rendered to audio, with its true measured duration. Leave it empty and you get scripts only — same Actor, same run, no extra step.

### 📦 Output Structure

Every item pushed to the default dataset looks like this (RSS source, voiceover enabled — the richest shape):

```json
{
  "category": "tech",
  "topic": "A hidden phone setting",
  "sourceType": "rss",
  "sourceUrl": "[https://example.com/article](https://example.com/article)",
  "duration": "40 seconds",
  "platform": "TikTok/YouTube Shorts",
  "segments": [
    {
      "label": "HOOK",
      "start": "0:00",
      "end": "0:05",
      "speech": "This setting is off by default on every phone.",
      "visual": "Close-up of settings screen",
      "audioUrl": "[https://api.apify.com/v2/key-value-stores/](https://api.apify.com/v2/key-value-stores/)<store-id>/records/0000-00-hook.mp3",
      "actualStart": 0.0,
      "actualEnd": 4.2,
      "durationSec": 4.2
    }
  ],
  "hook": "This setting is off by default on every phone.",
  "wordCount": 96,
  "rawScript": " HOOK: This setting is off by default on every phone. | VISUAL: Close-up of settings screen",
  "status": "ok",
  "generatedAt": "2026-08-15T12:00:00Z",
  "audioStatus": "ok",
  "totalDurationSec": 38.7
}

```

A few fields are conditional, not always present:

- **`audioStatus`** and **`totalDurationSec`** appear only when `elevenLabsApiKey` was set for the run. Scripts-only runs omit both.
- **`audioUrl`**, **`actualStart`**, **`actualEnd`**, and **`durationSec`** on each segment appear only when that segment's audio was rendered — the `audioUrl` value is whatever public URL the Apify key-value store returns for that key, shown above as an example, not a fixed format.
- **`sourceUrl`** is `null` in `direct` mode; `sourceType` is `"direct"` or `"rss"` accordingly.
- **`visual`** is always present but can be an empty string `""` if the model's line had no ` | VISUAL: ...` cue.

`start`/`end` are the model's plan. `actualStart`/`actualEnd` (when present) are measured from the rendered audio — cut your visuals to those. The two will differ from your requested duration, and that is expected.

Items the model produced that could not be parsed go to a separate `failed-generations` dataset instead, shaped as:

```json
{
  "category": "tech",
  "topic": "...",
  "sourceType": "rss",
  "sourceUrl": "https://...",
  "status": "unparseable",
  "reason": "Model output could not be parsed after 2 attempts.",
  "rawScript": "...",
  "generatedAt": "2026-08-15T12:00:00Z"
}

```

### 🗂️ Categories

`tech` · `trends` · `beauty` · `fashion` · `sports` · `gaming` · `news` · `learning` · `kid friendly` · `music` · `general`

Each carries its own persona, hook strategy, and structure. Override the built-in feeds per category with `feedOverrides`.

**`maxItems` and multiple categories.** In `rss` mode, `maxItems` is the budget for the whole run, not per category: it is divided evenly across the selected categories while collecting, and the collected items are then interleaved category by category (tech, gaming, tech, gaming, ...) before any truncation to `maxItems`. Interleaving means an earlier category cannot fill the whole budget and starve a later one — each gets a share, and the results alternate between them.

One limit worth knowing: if `maxItems` is lower than the number of categories you select, each share floors at one item and only the first `maxItems` categories in your list are represented. At the default `maxItems: 10`, selecting all 11 categories leaves the last one out. Set `maxItems` to at least the number of categories you select if you want every one to appear.

A run can also return fewer items than `maxItems` for an unrelated reason: an RSS entry only gets read as an article if its feed summary is at least 500 characters, or a fetched page extracts to at least 200 characters of usable body text — thin or paywalled pages are skipped rather than sent to the model with nothing to say.

### 📝 Notes

- **Failed generations are not billed.** If the model returns something unparseable after retries, that item goes to the `failed-generations` dataset with its raw output — inspectable, not charged.
- **`maxItems` is capped at 50 per run.** Generation draws on a shared daily allowance, and the cap stops one run from consuming it. Start another run if you need more.
- **ElevenLabs characters bill to your account**, not through Apify. The run log reports an estimate up front and the total at the end.
- **`ttsConcurrency` defaults to 2** because ElevenLabs' free tier allows as few as two simultaneous requests. Raise it only if your plan allows.

***

### 🛠️ Advanced: Running against your own generator & Development

Generation is built in, so this is not part of normal setup and the Actor's form has no fields for it. If you want generation on hardware you control, or want to change the prompts, run your own copy: deploy the worker in [`worker/`](https://www.google.com/search?q=worker/README.md) to your Cloudflare account and set `WORKER_URL` and `WORKER_SECRET` on your own build, as under "Operator setup" below.

The Actor still honours `workerUrl` and `workerSecret` passed through the API, but only **as a pair**. A URL with a blank secret is rejected rather than quietly falling back: the built-in secret is never sent to a worker that was named in the input, only to the one the Actor ships with.

#### Development Checks

```bash
pip install -r requirements-dev.txt
python -m pytest
cd worker && npm install && npm test

```

149 Python tests, 39 worker tests, no network calls in either suite. Before releasing, run the [live smoke test](https://www.google.com/search?q=docs/SMOKE_TEST.md) — the only check that talks to a real worker and real Workers AI.

#### Operator Setup

The built-in generator is configured through two Actor environment variables, `WORKER_URL` and `WORKER_SECRET`, declared in `.actor/actor.json`. **Both** are stored by reference, so neither the worker's address nor its secret appears in any committed file or in the source uploaded to the platform:

```bash
apify secrets add sfsgWorkerUrl    <the worker's URL>
apify secrets add sfsgWorkerSecret <the ACTOR_SECRET set on the worker>
apify push

```

The values live in `~/.apify/secrets.json` on the operator's machine and are encrypted on upload. `actor.json` carries only `@sfsgWorkerUrl` and `@sfsgWorkerSecret`.

`WORKER_SECRET` must equal the `ACTOR_SECRET` set on the Cloudflare Worker. A mismatch is not a startup error — every item fails with a 401 instead. The secret therefore lives in two places that have to stay in step: Cloudflare (`wrangler secret put ACTOR_SECRET`) and the local Apify secret store (`apify secrets add`). Rotating one without the other gives you a green build and a run where every single item fails.

Note that `apify push`, `apify call`, and `apify builds ls` all hang after finishing their work on apify-cli 1.7.1 — the operation succeeds server-side and the command never exits. `--json` helps `call` reliably and the others only sometimes (`push --json` and `push -w 300` both still hung; `builds ls --json` worked once and returned nothing the next time). Do not read a timeout as a failure, and verify functionally rather than by waiting: call the Actor and check the result, which is what actually proves which build is live.

````

# Actor input Schema

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

Where source material comes from. Fields for the other mode are ignored.
## `categories` (type: `array`):

RSS mode only. Which niches to pull articles for.
## `feedOverrides` (type: `object`):

RSS mode only. Replace the built-in feeds for a category, e.g. {"tech": ["https://example.com/feed.xml"]}.
## `items` (type: `array`):

Direct mode only. One object per script: {"category": "tech", "topic": "...", "data": "..."}.
## `maxItems` (type: `integer`):

Hard cap on scripts generated per run. Start another run if you need more.
## `duration` (type: `string`):

Drives the script's timestamps.
## `platform` (type: `string`):

Which platform the script is written for. Shapes tone and pacing.
## `elevenLabsApiKey` (type: `string`):

Optional. Leave empty for scripts only. Supply a key and each segment is also voiced. Characters are billed to your ElevenLabs account.
## `voiceId` (type: `string`):

ElevenLabs voice ID. Only used when an ElevenLabs API key is supplied.
## `ttsModelId` (type: `string`):

ElevenLabs model. Only used when an ElevenLabs API key is supplied.
## `generationConcurrency` (type: `integer`):

How many scripts to generate at once. The default is tuned for the built-in generator; raise it only when running against your own worker.
## `ttsConcurrency` (type: `integer`):

Keep low. ElevenLabs allows as few as 2 simultaneous requests on the free tier.
## `runDeadlineSeconds` (type: `integer`):

Stop starting new items after this many seconds. Keeps the run inside Apify's five-minute window; remaining items are reported as skipped, not failed.
## `itemTimeoutSeconds` (type: `integer`):

Give up on a single item after this long. Bounds work already in flight when the run deadline hits. Must stay below the Actor's own timeout; the default leaves 20 seconds of headroom inside Apify's five-minute window.

## Actor input object example

```json
{
  "mode": "rss",
  "categories": [
    "tech"
  ],
  "maxItems": 10,
  "duration": "40 seconds",
  "platform": "TikTok/YouTube Shorts",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "ttsModelId": "eleven_turbo_v2_5",
  "generationConcurrency": 5,
  "ttsConcurrency": 2,
  "runDeadlineSeconds": 210,
  "itemTimeoutSeconds": 280
}
````

# Actor output Schema

## `dataset` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("optirefine/short-form-script-generator").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("optirefine/short-form-script-generator").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 '{}' |
apify call optirefine/short-form-script-generator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,optirefine/short-form-script-generator"
        }
    }
}

```

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/NwhO1MVpD2O0QC38u/builds/OoORNLW4cSuUynUNx/openapi.json
