# Personalized Outreach Video Generator – AI Voice Clone (`outvo/personalized-outreach-video-generator`) Actor

One recording. Hundreds of personalized videos. An AI voice clone greets each prospect by name, lip-synced, their website on screen. Built for cold email and LinkedIn outreach and video prospecting. $1.25 per delivered video, $0.99 on Business. Failed or degraded renders are never charged.

- **URL**: https://apify.com/outvo/personalized-outreach-video-generator.md
- **Developed by:** [Outvo](https://apify.com/outvo) (community)
- **Categories:** Videos, Marketing, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.99 / delivered video

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

### Turn any lead list into personalized outreach videos

Record one short video of yourself — *"Hey there, I loved what you're building"* — and
this Actor turns it into hundreds of videos where **your own voice says each prospect's
name** — cloned from that one recording — and **their website scrolls on screen next to
your face**. It is video prospecting at scale: the personalization that used to mean
recording Looms by hand, produced from one take. Feed it a lead list, or
chain it straight after any scraper Actor (LinkedIn, Google Maps, Apollo) and every
scraped row becomes a personal video with a hosted, trackable link — ready to drop into
a cold email or a LinkedIn DM. Agencies and SDR teams use one base recording per
sales outreach campaign; all of their prospects get their own copy, each with an
AI voice clone of the presenter rather than a synthetic narrator.

**You pay only for videos that are actually delivered.** Failed renders are free.
Imperfect (degraded) renders are free.

### What you get

- One MP4 per recipient: your recording, their first name spliced in with correct
  prosody (not a bare name — a full carrier phrase, e.g. "Hey Sarah"), and their
  website recorded and shown on screen next to you.
- A hosted, shareable link per video (`shareableUrl`) that stays live for 60 days.
- A dataset row per recipient with the video, status, and whether it was charged —
  native Output tab, no post-processing needed.

#### ▶ [Watch a real result (15s)](https://vimeo.com/1216802000/c76e4e43f5)

That video was produced by this Actor, start to finish, from one recording: the
recipient's own website is captured on screen while the presenter appears
lip-synced alongside it, then the video returns to full frame. It is one recipient's
copy — every other person on the list gets their own.

Curious what goes *in*? Here is the
[example base video](https://vimeo.com/1216794629/de65331660) it was made from — a single
plain take, no editing, nothing personalized yet.

### Video prospecting for cold email and LinkedIn

Chain this Actor after any scraper and the whole motion runs unattended: a LinkedIn,
Google Maps, or Apollo Actor produces the rows, this one turns each row into a personal
video, and the `shareableUrl` column drops straight into your sequencer. The prospect
opens a link and watches you say their name in front of their own homepage — from a
recording you made once.

That is the difference between personalization and a merge field. SDR teams use it for
first touch and for follow-up; agencies run one base recording per client campaign.

### How it compares to HeyGen, Tavus, and recording Looms by hand

- **Avatar generators (HeyGen, Tavus, Synthesia)** synthesize a presenter. This Actor
  does not generate a face — it splices a personalized greeting into *your real
  recording*, in your own cloned voice, and lip-syncs only that greeting. What the
  prospect watches is you.
- **Recording Looms by hand** is the thing this replaces. It works, and it stops working
  somewhere around twenty a day. One take here covers the whole list.
- **This Actor is not affiliated with any of the above.** The comparison is offered
  because it is the question every buyer asks first.

### How it works

1. **Record once.** A short video of yourself saying a greeting with a placeholder name
   — "Hey there, I loved what you're building" — uploaded anywhere reachable by URL.
   **[See an example base video (44s)](https://vimeo.com/1216794629/de65331660)** — one
   take, no editing, nothing personalized yet; that is all it takes.
   Full guide: [Recording your base video](./docs/recording-your-base-video.md), and
   [Hosting your video file](./docs/hosting-your-video.md) if it only exists on your
   computer.
2. **Give it a list.** Paste recipients as JSON, or chain this Actor after any scraper
   (see below) and map its columns. Full guide:
   [Recipient lists and chaining](./docs/recipient-lists-and-chaining.md).
3. **Get videos back.** Each row becomes a render: the greeting is re-synthesized as
   text-to-speech in a clone of your own voice, lip-synced to your face at the splice
   point, and composited with the website capture. Completed videos land in this run's
   dataset with a shareable link.

#### The one rule that matters when recording

Your placeholder must sit inside a **carrier micro-phrase** — say *"Hey there"*, which
becomes *"Hey Sarah"* — and must **never** be a bare name on its own. The surrounding
words give the splice the natural mouth movement and intonation of real speech; a bare
name recorded in isolation sounds pasted-in on every single video. The
[recording guide](./docs/recording-your-base-video.md) covers this and the other
failure modes before you spend anything.

### Who this is for

- **Founder-led sales.** Your face and your voice are the reason the video works; this
  keeps both while removing the part where you record two hundred takes.
- **SDRs and outbound teams** doing video prospecting at volume — personalized video at
  scale, from the lead list you already have, without a per-seat contract.
- **Agencies** running cold outreach video for several clients: one base recording per
  client, one run per campaign, billed only on what shipped.

Common shape: a scraper builds the list, this Actor turns each row into a cold outreach
video, and the shareable link goes into whatever channel you already send from.

### Chain it after a scraper

Point **Input dataset** at a previous run's dataset and set **Field mapping** to match
its columns. For example, chaining after
[HarvestAPI's LinkedIn Profile Scraper](https://apify.com/harvestapi/linkedin-profile-scraper),
whose output rows carry `firstName`, `linkedinUrl`, and a `currentPosition` array:

```json
{
  "firstName": "firstName",
  "company": "currentPosition.0.companyName",
  "url": "linkedinUrl"
}
```

Dot paths reach nested fields and array elements. Every scraped row becomes a
personalized video automatically. A bad row (missing name, a broken URL) costs that one
row, not the run — the rest of the list still renders.

### Pricing: pay only for delivered videos

**$1.25 per delivered video — $0.99 if you are on an Apify Business or Enterprise plan.**
The rate follows your own Apify subscription; nothing to apply for.

Apify's pricing table normalizes recurring events to a per-1,000 display, so this shows as
**"$1,250.00 / 1,000"** (or "$990.00 / 1,000" on Business+). That is the same per-video
price, not a bundle and not a minimum. You are never pre-buying a thousand of anything;
each delivered video is billed individually.

| Your Apify plan | Per delivered video |
|---|---|
| Free, Starter, Scale | $1.25 |
| Business, Enterprise | **$0.99** |

- **Failed and degraded videos are always free.** A "degraded" video still plays (it's
  watchable) but fell short of the full pipeline — Outvo does not charge for output it
  isn't confident in.
- **Skipped rows are free.** A row with a missing name or an invalid URL is reported as
  `skipped_invalid` and never submitted.
- **Daily capacity:** the marketplace has a shared daily rendering ceiling and a
  per-account daily limit. If the ceiling is reached mid-run, remaining rows are
  reported as `deferred_daily_cap` (never submitted, never charged) and the run still
  finishes successfully — re-run the deferred rows tomorrow.
- **Apify free-plan accounts draw from a smaller shared daily pool.** When that pool
  is used up, remaining rows defer the same way (free, `deferred_daily_cap`) with a
  note naming the free-plan pool; upgrade to a paid Apify plan to render without the
  free-plan limit, or re-run after midnight UTC.

#### A worked example

A 40-lead list where 3 rows have no usable website URL and 2 renders come back degraded:

| Rows | Outcome | Cost |
|---|---|---|
| 3 | `skipped_invalid` — missing URLs, never submitted | $0.00 |
| 2 | `degraded` — watchable but imperfect, never charged | $0.00 |
| 35 | `completed` — delivered with shareable links | 35 × $1.25 = **$43.75** |

Total for the run: **$43.75** on Free/Starter/Scale, or **$34.65** on Business+. Every dataset row states whether it was charged.

#### Refunds

Failed and degraded renders are never charged in the first place — that is the billing
invariant, not a refund policy. If a video was delivered as `completed` and you consider
it unsatisfactory, report it through the **Issues tab** on this Actor's page; each case
is reviewed individually and resolved within 1–2 business days.

Prefer to render against your own Outvo plan instead of paying per video here? Set
**Your own Outvo API key** (Limits & advanced) with a key from
[outvo.io](https://outvo.io) → Settings → API Keys.

### How long does a run take?

- **~2–4 minutes per video** is typical. Videos render with real TTS, lip-sync, and a
  live website recording — this is compute, not templating.
- **A 100-row list takes hours.** That is expected. The run does **not** need your
  browser open — close the tab and come back to a full dataset.
- **Do not lower the run timeout.** Long lists need the default headroom (≥ 21600
  seconds); a lowered timeout guarantees a timed-out — and therefore failed — run on
  any large list. Already-delivered rows are kept, but nothing in flight survives.

### Input reference

| Field | Description |
|---|---|
| `videoUrl` | Your recording's public URL. Required. |
| `placeholderWord` | The word in your recording that gets replaced with each recipient's name. Required. |
| `recipients` | Inline recipient list as JSON. Leave empty when chaining a dataset. |
| `inputDatasetId` | A previous Actor run's dataset to chain from. |
| `fieldMapping` | Maps Outvo fields to your dataset's column names. |
| `pipLayout` | Where the recipient's website overlay appears, or `none` for talking-head only. |
| `pipDurationSeconds` | How long the website overlay stays on screen. |
| `recordingMode` | `desktop` or `mobile` layout for the website recording. |
| `missingUrlBehavior` | `skip` (default) or `renderWithoutPip` for rows with no URL. |
| `maxVideos` | Safety cap for this run (1-200). Rows past the cap are returned as `deferred_max_videos` — never silently dropped. |
| `outvoApiKey` | Optional — bring your own Outvo API key instead of paying per video. |

Full descriptions and defaults are in the Input tab — they're written to match this
table exactly (congruent naming is part of what the Apify quality score rewards).

### Output reference

One dataset item per recipient row:

| Field | Meaning |
|---|---|
| `status` | `completed`, `degraded`, `failed`, `skipped_invalid`, `deferred_daily_cap`, `deferred_charge_limit`, `deferred_max_videos`, or `cancelled`. |
| `charged` | Whether this row was billed. Always `false` unless `status: "completed"`. |
| `firstName` | The recipient this row belongs to. |
| `company` | The recipient's company, when provided. |
| `url` | The recipient's website or profile URL used for the on-screen recording. |
| `shareableUrl` | The hosted, shareable video link. |
| `videoUrl` | Direct MP4 URL. |
| `thumbnailUrl` | Thumbnail image. |
| `renderId` | Outvo's internal render id, for support/troubleshooting. |
| `note` | Human-readable explanation for anything other than a clean `completed`. |
| `chargeError` | Why a `completed` render was **not** charged (e.g. the run's "Max total charge" was reached, or billing failed). `null` on charged rows. An uncharged completed row has its video links withheld — you are never billed for it, and never given an unbilled video. |

### What this Actor does not do

Worth knowing before you run it, so nothing here is a surprise on the far side of a
charge:

- **It does not send anything.** You get hosted links and MP4s back. Email, LinkedIn,
  wherever it goes — that stays in the tool you already send from.
- **It does not build your list.** Bring recipients as JSON, or chain a scraper Actor
  in front of it (below).
- **It does not synthesize an avatar.** No AI avatar, no talking photo generated from a
  still, no digital twin. It lip-syncs a real recording of your real face, which is
  exactly why it needs you to record one.
- **It does not translate or dub.** Greetings come back in the language you recorded in.
- **It does not edit video.** No trimming, no captions, no thumbnail generation.

### Limitations

- **Every dataset row ends in one of eight statuses.** `completed` (delivered and
  charged), `degraded` (watchable but below the full pipeline's bar — free),
  `failed` (no video — free), `skipped_invalid` (bad input row, never submitted —
  free), `deferred_daily_cap` (capacity reached, never submitted — free; re-run
  tomorrow), `deferred_charge_limit` (the run's "Max total charge" was reached —
  free, never submitted; raise the limit and re-run), `deferred_max_videos` (the run's
  "Max videos" ceiling was already reached — free, never submitted; raise it or re-run
  with the remaining rows), and `cancelled` (run aborted while this row was in flight —
  free). `charged` is never `true` unless `status`
  is `completed` — and in the rare case a `completed` render could not be charged,
  the row carries a `chargeError`, its video links are withheld, and you pay
  nothing.
- **Some websites cannot be recorded.** Pages behind a login wall or aggressive bot
  protection may fail to load in the recorder; those rows come back `failed` or
  `degraded`, and are free either way. Use `missingUrlBehavior` / `pipLayout: none`
  for talking-head-only videos when the URL isn't the point.
- **Language coverage:** greetings are generated in the language of your recording,
  auto-detected, across 29 supported languages (English, Spanish, Portuguese, French,
  German, Italian, Japanese, Chinese, Korean, Hindi, and more). A recording in an
  unsupported language fails validation rather than producing a wrong-language splice.
- **Shareable links expire after 60 days.** Download the MP4s (`videoUrl`) if you need
  them beyond that.
- **Daily capacity is shared.** A marketplace-wide daily ceiling and a per-account
  daily limit apply; rows beyond them defer, free, to the next day. Each single run is
  additionally capped by `maxVideos` (1–200, default 25).
- **Aborting a run** cancels everything in flight; only videos already delivered (and
  therefore already charged) are kept. Nothing in flight is billed.

### Works well with these Actors

Feed this Actor from any scraper whose rows carry a name and a URL:

- [LinkedIn Profile Scraper (HarvestAPI)](https://apify.com/harvestapi/linkedin-profile-scraper) —
  enriched profiles with `firstName`, `linkedinUrl`, and current position; the field
  mapping above is written for it.
- [Google Maps Scraper (Compass)](https://apify.com/compass/crawler-google-places) —
  local businesses with names and websites, ideal for local-service outreach.
- [Apollo.io Scraper (Code Crafter)](https://apify.com/code_crafter/apollo-io-scraper) —
  B2B contacts with names, companies, and domains.
- [Instagram Scraper (Apify)](https://apify.com/apify/instagram-scraper) — creator and
  brand profiles for influencer outreach.

Chain any of them with **Input dataset** + **Field mapping** — see
[Recipient lists and chaining](./docs/recipient-lists-and-chaining.md) for worked
mappings.

### FAQ & troubleshooting

**What counts against the daily cap?** The marketplace has a shared capacity ceiling
across every user of this Actor, plus a per-account daily limit. Rows deferred by
either show `status: "deferred_daily_cap"` and were never submitted or charged — just
re-run them.

**What does "degraded" mean, and why is it free?** A render that completed but fell
short somewhere in the pipeline (e.g. a website recording that didn't load cleanly). It's
still a real, watchable video — Outvo just doesn't charge for output it isn't confident
represents your best result.

**A recipient's website wouldn't record — what happens to that row?** It fails
independently; that one row is `failed` (and free), the rest of the run is unaffected.

**How does "Max total charge" interact with pricing?** It's an Apify run setting this
Actor actively respects: rows that would push the run past your limit are never
submitted and come back as `deferred_charge_limit`, free, with a note telling you to
raise the limit. Leave it unset for no extra ceiling beyond `maxVideos`, or set it to
cap total spend for a single run — a capped run still finishes successfully.

**What happens if I abort a run?** In-flight renders are cancelled; only videos already
delivered (and therefore already charged) are kept. Nothing in flight is billed.

**My video is a local file — where do I host it?**
[Hosting your video file](./docs/hosting-your-video.md) walks through Google Drive,
S3, and Dropbox, including the classic mistake (a Drive *share* link is an HTML page,
not an MP4).

### Support

- **Issues tab** on this Actor's page — the fastest route for anything about a specific
  run or video; include the `renderId` from the dataset row when it's about one video.
- Expect a response within **1–2 business days**.
- Failed and degraded renders are never charged; a delivered video you consider
  unsatisfactory is reviewed case by case through the same Issues tab, within the same
  1–2 business days.

### About Outvo

Outvo turns a base recording and a recipient list into individually personalized
outreach videos — TTS-matched greetings, lip-sync, and optional live website recordings.
This Actor is the zero-setup way to use it from inside Apify, chained straight off
whatever scraper already built your list — or bring your own API key from
[outvo.io](https://outvo.io) to render against your Outvo plan.

# Actor input Schema

## `videoUrl` (type: `string`):

Public or signed URL of your recorded MP4 (max 200 MB). Record yourself once saying a greeting with a placeholder name — e.g. 'Hey there, I loved what you're building.' Outvo clones the greeting for every recipient. The same URL is reused across runs without re-uploading.

## `placeholderWord` (type: `string`):

The word in your recording that gets replaced with each recipient's first name. If you said 'Hey there', enter `there`. The sample recording prefilled above says `avocado`, so leave it as-is to try the Actor before recording your own.

## `recipients` (type: `array`):

Recipients as JSON: `[{ "firstName": "Sarah", "company": "Acme", "url": "https://acme.com" }]`. Leave empty when chaining from another Actor with Input dataset below. `url` is each recipient's website or LinkedIn page, shown on screen next to your video.

## `inputDatasetId` (type: `string`):

Dataset from a previous Actor run (e.g. a LinkedIn or Google Maps scraper). Chain it here and every row becomes a personalized video — map the columns with Field mapping below.

## `fieldMapping` (type: `object`):

Which dataset columns feed the video: keys are Outvo fields (`firstName`, `company`, `url`, `title`, `language`, `pronunciation`), values are your dataset's column names. Dot paths supported (`contact.first_name`).

## `pipLayout` (type: `string`):

Where the recipient's website recording appears over your video. `none` = talking-head only (recipient `url` becomes optional).

## `pipDurationSeconds` (type: `integer`):

How long the website recording stays on screen.

## `recordingMode` (type: `string`):

Capture the recipient's website as desktop or mobile layout.

## `missingUrlBehavior` (type: `string`):

What to do with rows that have no `url` while a website overlay is enabled: skip them (never charged, reported in the output) or render them as talking-head-only videos.

## `maxVideos` (type: `integer`):

Safety cap for this run. You are only charged for videos actually delivered — failed or degraded videos are always free.

## `outvoApiKey` (type: `string`):

Optional. Paste your own Outvo API key (outvo.io → Settings → API Keys) to render against your Outvo plan instead of paying per video here.

## Actor input object example

```json
{
  "videoUrl": "https://hxjbyswpegngmcfnqrkt.supabase.co/storage/v1/object/public/videos/demo/apify-sample-base.mp4",
  "placeholderWord": "avocado",
  "fieldMapping": {
    "firstName": "firstName",
    "company": "companyName",
    "url": "websiteUrl"
  },
  "pipLayout": "bottom-right",
  "pipDurationSeconds": 8,
  "recordingMode": "desktop",
  "missingUrlBehavior": "skip",
  "maxVideos": 25
}
```

# Actor output Schema

## `videos` (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 = {
    "videoUrl": "https://hxjbyswpegngmcfnqrkt.supabase.co/storage/v1/object/public/videos/demo/apify-sample-base.mp4",
    "placeholderWord": "avocado",
    "fieldMapping": {
        "firstName": "firstName",
        "company": "companyName",
        "url": "websiteUrl"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("outvo/personalized-outreach-video-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 = {
    "videoUrl": "https://hxjbyswpegngmcfnqrkt.supabase.co/storage/v1/object/public/videos/demo/apify-sample-base.mp4",
    "placeholderWord": "avocado",
    "fieldMapping": {
        "firstName": "firstName",
        "company": "companyName",
        "url": "websiteUrl",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("outvo/personalized-outreach-video-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 '{
  "videoUrl": "https://hxjbyswpegngmcfnqrkt.supabase.co/storage/v1/object/public/videos/demo/apify-sample-base.mp4",
  "placeholderWord": "avocado",
  "fieldMapping": {
    "firstName": "firstName",
    "company": "companyName",
    "url": "websiteUrl"
  }
}' |
apify call outvo/personalized-outreach-video-generator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,outvo/personalized-outreach-video-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/GENyoLNwRaaq1lK5z/builds/O9WHTt2SORqEkI0Kk/openapi.json
