---
title: Make a UGC ad from product photos with an AI actor
description: A vertical UGC-style ad, or AI creator ad, with an AI actor who talks about your product, your real product shots, captions and a hook.
---
You get a vertical UGC ad, also called a creator-style ad or AI spokesperson ad, where an AI actor talks to camera about your product. Your real product photos and clips sit between the talking shots, so the product on screen is the one you sell. The ad has a hook line, captions and a short call to action.

Use invideo for this when you want the actor, the voice, your product shots and the captions on one editable timeline: you swap the hook, the voice or a product shot and export again, without a new shoot.

Making ads for your own product is ordinary marketing work, and your agent does it. The agent never switches an ad on or spends ad budget. You upload the ad and run it in the ad platform.

## Quickest path

Set up your agent first with [invideo for AI agents](/agents). Then give it this prompt.

```text
Make a 20 second vertical UGC ad for my serum. Use the actor photo
./actor.jpg and my product photos in ./product. Write a script with
the hook "I stopped buying three products for this one", make the
voice, animate the actor, cut to my real product shots, add captions.
Tell me the price before you generate anything.
```

In a chat app, you drag the actor photo and product photos into the project in invideo. In a terminal, you name the folder and the agent uploads it.

## What you need

The actor and the product come from you. The agent does not invent either one.

- **An actor photo you have the rights to.** A front-facing portrait of a person who agreed to appear in AI ads, or a face made with an image model.
- **Product photos and clips.** The real product, sharp, from a few angles. A short phone clip of the product in a hand is good.
- **The claim and the hook.** What the product does, in your words, and two or three hook lines to test.
- **The shape.** 9:16 for TikTok, Reels and Shorts. 4:5 or 1:1 for feeds.
- **The voice.** A voice from the library, or your own voice clip.

## Steps

The agent works on a branch and tells you what changed at each step.

<Steps>
<Step title="Set up the project and bring the media in">
The agent makes a project with `new_project`, a 1080 by 1920 timeline with `new_timeline`, and a branch with `timeline_history`. It uploads the actor photo and product files with `start_upload` and `add_media`. Expected result: a sequence URL per file.
</Step>
<Step title="Write the script and make the voice">
The agent writes a short script: the hook, one or two lines on the product, the call to action. It shows you the script first. It runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick is `elevenlabs_text_to_speech.elevenlabs_tts_v4`. It picks a voice with `search_library` in `elevenlabs_text_to_speech_voices`, because a voice id works only with its own model. It calls `generate` with `dry_run: true` and tells you the price. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. Expected result: a sequence URL with the voiceover, which the agent reads with `read`.
</Step>
<Step title="Animate the actor">
The agent runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick is `heygen_photo_avatar`, and `kling_avatar.std` takes the same `image` and `audio`. It calls `generate` with the actor photo as `image`, the voice as `audio`, `aspect_ratio` 9:16 and `dry_run: true`, and tells you the price. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. Expected result: a thread URL and, when the take lands, a sequence URL with a talking video whose length follows the voice. The agent reads it with `read` and never calls `generate` again to check.
</Step>
<Step title="Keep the product real">
The agent cuts from the actor to your real product photos and clips with `place_media`. A product shot stays your photo or clip, so the label, color and shape are exact. Expected result: talking shots and real product shots, in script order.
</Step>
<Step title="Check the product by eye">
A generated shot can redraw the label or the shape. The agent runs `capture` on every generated shot that shows the product, and compares each frame with your photo. It replaces any shot where the product changed with your real photo. Expected result: the product looks the same in every frame.
</Step>
<Step title="Add the hook, captions and the disclosure">
The agent adds the hook as `add_text` in the first seconds. It prices `audio.transcript` on the voice's sequence URL with `dry_run`, starts it, and calls again after `retry_after_s` for the words. Then it sends `add_captions`. It adds a line such as "AI-generated actor" with `add_text` where the platform asks for one. Expected result: a hook at the top, captions that follow the voice, and the disclosure on screen.
</Step>
<Step title="Check and export">
The agent runs `capture` at the hook, at each cut and at the end, and `check_timeline`. It checks that captions and the hook sit clear of the app buttons. Then it runs `export` with `format` video. Expected result: an export card with status `completed`.
</Step>
</Steps>

The same job from a terminal:

```bash
invideo project new --title "Serum UGC ad"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo edit new <project-url> --name "UGC 20s" --width 1080 --height 1920
invideo asset upload <edit-url> ./actor.jpg ./product/*
invideo library search <project-url> \
  --library elevenlabs_text_to_speech_voices --query "warm young woman"
invideo gen models <project-url> --query "voiceover from a script"
invideo gen model <project-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 --dry-run \
  -i prompt=@script.txt -i voice_id=<voice-id>
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 \
  --max-credits <price> --idempotency-key serum-voice-1 \
  -i prompt=@script.txt -i voice_id=<voice-id>
invideo gen models <project-url> --query "talking actor from a photo"
invideo gen model <project-url> --model heygen_photo_avatar
invideo gen run <page-url> --model heygen_photo_avatar --dry-run \
  -i image=<actor-sequence-url> -i audio=<voice-sequence-url> \
  -i aspect_ratio=9:16
invideo gen run <page-url> --model heygen_photo_avatar \
  --max-credits <price> --idempotency-key serum-ugc-actor-take-1 \
  -i image=<actor-sequence-url> -i audio=<voice-sequence-url> \
  -i aspect_ratio=9:16
invideo status <thread-url> --wait
invideo media audio.transcript <voice-sequence-url> --dry-run
invideo media audio.transcript <voice-sequence-url> --max-credits <price>
invideo edit show <edit-url> --json
invideo edit change <edit-url> --timeline T1 --token <state_token> \
  --operations @ops.json
invideo edit capture <edit-url> --at 0.5,4,9,15,19 --out ./frames
invideo edit export <edit-url> --format video --quality 1080 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./out
```

Where the values come from: `<project-url>` from `invideo project new`, and `<edit-url>` from `invideo edit new`. `<name>` is the name you choose in `invideo agent register`. `<actor-sequence-url>` is the `url` that `invideo asset upload` returns for the actor photo. `<page-url>` is a notebook page from `invideo notebook show <project-url>`. `<voice-id>` comes from the library search. `<voice-sequence-url>` is the sequence URL the voice run returns when it lands. Each `<price>` is its dry run's `estimated_credits`. `<thread-url>` is the `url` the paid actor run returns. `<state_token>` comes from `invideo edit show`. `ops.json` is a bare list of edit ops. `<card-url>` is `.sequences[0]` of what the export returns. `invideo status` does not download; `invideo notebook show` saves the file as `export.mp4`.

The key MCP call makes the talking actor. It is a dry run, so it only checks the inputs and returns the price. `<page-url>` comes from `read` on the project URL, and the two sequence URLs come from `add_media` and the voice generation.

```json
{
  "url": "<page-url>",
  "model": "heygen_photo_avatar",
  "dry_run": true,
  "inputs": {
    "image": "<actor-sequence-url>",
    "audio": "<voice-sequence-url>",
    "aspect_ratio": "9:16",
    "expressiveness": "medium"
  }
}
```

The agent then sends the same call with `dry_run` false, `max_credits` set to the price and `idempotency_key` `serum-ugc-actor-take-1`.

To change the voice later without a new actor take, the agent calls `generate` with `sync_lipsync_v3.video`, the actor video as `video` and the new voice as `audio`: a dry run first, then `max_credits` and an `idempotency_key`. It sets `sync_mode` to `remap` when the new voice is a different length.

## Cost and time

Edits and exports are free. Generation and transcripts cost credits. Paid stock costs a small credit charge, or one of your workspace's iStock licenses. Your agent runs a dry run first and tells you the price before it spends anything.

- **Free:** uploads, cuts, your product shots, text, captions you place, the checks and the export.
- **Costs credits:** the voice, the actor model, any reference-to-video shot, the lip re-sync and the transcript. Actor models charge by the second of audio.

One ad takes under an hour, most of it on the script and the takes you compare. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems are a product that changed in a generated shot, or a mouth out of sync.

| Symptom | Fix |
| --- | --- |
| `get_model` or `generate` refuses the model as not found | Models retire. Run `find_models` with the job in words, use the best match, and tell the person which model you used. |
| Exit code 6, or `status` is `held` | A safety review, not a refusal. Wait, then run `invideo status <url> --wait` again. Do not resend. If it ends `failed`, change the input the review flagged. |
| The label or shape changed in a generated shot | Use your real photo or clip for that shot. Generated shots are for the actor, not the pack. |
| You want the actor holding the product | Generate it with `seedance_2_5.r2v` and your product photos in `reference_images`: `find_models`, `get_model`, a dry run, then `generate` with `max_credits` and an `idempotency_key`. Check the label by eye. |
| The mouth drifts from the words | Run `sync_lipsync_v3.video` on the take with the same voice. |
| The actor model refuses the photo | Use a sharp, front-facing photo of one face. Read the refusal `message`. |
| Status `needs_confirmation` | Only you can confirm it, in invideo. |
| The download folder is empty | `invideo status` does not download. Run `invideo notebook show <card-url> --out ./out`. |
| Avoid: the agent resends `generate` to check on a take | Instead: it reads the thread URL, or calls `status`. |
| Avoid: a face of a real person without their consent | Instead: use a person who agreed, or a made-up face. |

## Prompts to try

Each prompt is a real variation of the job.

```text
Make three versions of my UGC ad, each with a different hook from
hooks.txt. Same actor, same product shots. One branch per hook.
```

```text
Make a 15 second unboxing-style ad: my clip ./unbox.mp4 first, then the
actor ./actor.jpg says why they switched. Bold white captions, 9:16.
```

```text
Swap the voice on my finished UGC ad to a younger male voice, keep the
actor and re-sync the lips. Tell me the price first.
```

## When to use something else

For an ad with a real creator, book one on a creator marketplace and bring the clip in with `add_media`. invideo then cuts, captions and exports it.

The rules for labeling AI actors differ by platform and by country. invideo adds the words you choose, but it does not check an ad against a platform's policy. Read [Label AI content so your ad runs on TikTok, Meta and Google](/how-to/marketing/ai-disclosure-for-ads) before you run the ad.

## Related

- [Label AI content so your ad runs on TikTok, Meta and Google](/how-to/marketing/ai-disclosure-for-ads)
- [Test hooks on one ad, with branches](/how-to/marketing/test-hooks-on-branches)
- [Turn a product photo into a video without redrawing the product](/how-to/marketing/product-photo-to-video)
- [Cut a customer testimonial or raw creator footage into an ad, or the case study set](/how-to/marketing/testimonial-to-ad)
- [Make performance ads for Meta, TikTok and YouTube, named and iterated from results](/how-to/marketing/performance-ads)
- [Check your video before you say it is done](/how-to/check-your-video)