---
title: ToolRouter — Tool Detail
description: Detailed documentation for a ToolRouter tool including skills, parameters, and examples.
url: https://toolrouter.com/tools/ugc-content
---
# UGC Content Generator

Create authentic UGC-style videos and photos from a product description. Researches audience, writes scripts, generates AI scenes and clips across 16 formats — from product showcase and unboxing to lifestyle stills and flat lays. For marketers, DTC brands, and agencies.

- **Version:** 0.08
- **Categories:** media, marketing, ai
- **Workflow:** research_audience → generate_creative → create_voice_profile → generate_frames → composite_product → generate_videos → assemble_final

## Skills (12)

### Research Audience (`research_audience`)

Step 1: Research audience pain points, language, triggers, and objections. Async (1-3 min). Do NOT skip. Pass full output to generate_creative.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `company_name` | string | Yes | Company or brand name |
| `product_name` | string | Yes | Product or feature name |
| `product_description` | string | Yes | Detailed description of the product and what it does |
| `target_audience` | string | No | Optional target audience description (e.g. "busy parents", "fitness beginners") |
| `processor` | string | No | Research depth: "core" (default, 1-3 min) or "pro" (deeper, 5-15 min) (lite, base, core, pro) [default: "core"] |
| `source_domains` | array | No | Optional domains to focus research on (e.g. ["reddit.com", "trustpilot.com"]) |

**Returns:** Structured audience research with pain points, problems, language patterns, emotional triggers, objections, and research summary

**Examples:**

*Research audience for an AI meal planning app:*

```json
{
  "company_name": "NutriPlan",
  "product_name": "MealAI",
  "product_description": "AI-powered meal planning app that creates personalized weekly meal plans based on dietary preferences, budget, and schedule"
}
```

### Write Hooks (`write_hooks`)

Generate UGC video hooks using the 3-variable framework (Angle + Aesthetic + Action). Uses RAG from the UGC playbook to apply proven hook patterns targeting 60%+ 3-second view rate. ⏱ Takes ~5 seconds. Step 2 of the UGC workflow — requires research output from step 1.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `company_name` | string | Yes | Company or brand name |
| `product_name` | string | Yes | Product or feature name |
| `product_description` | string | Yes | Detailed product description |
| `research` | object | Yes | Audience research output from research_audience step |
| `num_hooks` | number | No | Number of hooks to generate (default 3) [default: 3] |
| `content_archetype` | string | No | Content archetype to tailor hooks to (e.g. educational, entertainment) (lifestyle, inspirational, aspirational, educational, documentary, entertainment, fictional) |
| `persona` | object | No | Optional creator persona for hook voice/style |
| `llm_model` | string | No | OpenRouter model ID (default google/gemini-2.5-flash) |

**Returns:** Array of hooks, each with text, angle, aesthetic, action, emotional trigger, and target pain point

**Examples:**

*Generate 3 hooks from research:*

```json
{
  "company_name": "NutriPlan",
  "product_name": "MealAI",
  "product_description": "AI-powered meal planning app",
  "research": {
    "pain_points": [
      "spending too much time deciding what to cook"
    ],
    "emotional_triggers": [
      "overwhelm",
      "guilt"
    ]
  },
  "num_hooks": 3
}
```

### Write Scripts (`write_scripts`)

Generate authentic UGC scripts using But/Therefore zigzag structure. Creates script variations for each hook with mini-hooks at key drop-off timestamps. Targets ~20 second duration with conversational, non-salesy tone. ⏱ Takes ~5-10 seconds per hook. Step 3 — requires hooks from step 2.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `hooks` | array | Yes | Array of hooks from write_hooks step |
| `company_name` | string | Yes | Company or brand name |
| `product_name` | string | Yes | Product or feature name |
| `product_description` | string | Yes | Detailed product description |
| `research` | object | Yes | Audience research output from research_audience step |
| `scripts_per_hook` | number | No | Number of script variations per hook (default 2) [default: 2] |
| `content_archetype` | string | No | Content archetype to tailor scripts to (lifestyle, inspirational, aspirational, educational, documentary, entertainment, fictional) |
| `persona` | object | No | Optional creator persona |
| `llm_model` | string | No | OpenRouter model ID (default google/gemini-2.5-flash) |

**Returns:** Array of scripts, each with hook_id, variation, hook_text, script_body, CTA, duration estimate, and mini-hooks at drop-off timestamps

**Examples:**

*Generate 2 scripts per hook:*

```json
{
  "hooks": [
    {
      "id": "hook_1",
      "text": "I used to spend 2 hours deciding what to cook",
      "angle": "time-waste",
      "aesthetic": "selfie-rant",
      "action": "frustrated-face"
    }
  ],
  "company_name": "NutriPlan",
  "product_name": "MealAI",
  "product_description": "AI-powered meal planning app",
  "research": {
    "pain_points": [
      "decision fatigue around meals"
    ]
  },
  "scripts_per_hook": 2
}
```

### Generate Scenes (`generate_scenes`)

Generate visual scene descriptions for each script, optimized for AI image/video generation. Set "format" for format-specific direction. Takes ~5s per script. Step 4 — requires scripts from step 3.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `scripts` | array | Yes | Array of scripts from write_scripts step |
| `format` | string | No | UGC video format for format-specific scene direction. Omit for default talking-head style. (talking_to_camera, product_demo, product_showcase, grwm, unboxing, before_after, problem_solution, storytime, pov, voiceover_broll, reaction) |
| `persona` | object | No | Optional creator persona for visual consistency |
| `familiar_elements` | object | No | Repetitive brand elements for memorability across videos |
| `wardrobe_image_url` | string | No | Optional URL to a wardrobe/outfit reference image for clothing consistency across scenes. |
| `platform` | string | No | Target platform for framing guidance. Defaults to tiktok. (tiktok, instagram_reels, youtube_shorts, instagram_stories, twitter) |
| `llm_model` | string | No | OpenRouter model ID (default google/gemini-2.5-flash) |

**Returns:** Array of scenes, each with scene description, setting, lighting, wardrobe, camera angle, mood, and shot list with visual changes

**Examples:**

*Generate scenes for scripts:*

```json
{
  "scripts": [
    {
      "hook_id": "hook_1",
      "hook_text": "I used to spend 2 hours deciding what to cook",
      "script_body": "But then I found this app that plans my whole week in 30 seconds.",
      "cta": "Link in bio"
    }
  ]
}
```

### Generate Creative (Fast) (`generate_creative`)

Generate hooks, scripts, and scene descriptions in one call (~8s). Set "format" for format-specific output (e.g. product_demo, grwm, unboxing, lifestyle_still). Photo formats return compositions instead of hooks/scripts. Step 2 — pass FULL research output from step 1.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `company_name` | string | Yes | Company or brand name |
| `product_name` | string | Yes | Product or feature name |
| `product_description` | string | Yes | Detailed product description |
| `research` | object | Yes | Audience research output from research_audience step |
| `format` | string | No | UGC format. Most require persona_image_url. Product formats require product_images. See format docs for requirements. (talking_to_camera, product_demo, product_showcase, grwm, unboxing, before_after, problem_solution, storytime, pov, voiceover_broll, reaction, lifestyle_still, product_flat_lay, in_use_shot, aesthetic_moment, mirror_selfie) |
| `num_hooks` | number | No | Number of hooks to generate (video formats, default 3) [default: 3] |
| `num_compositions` | number | No | Number of photo compositions to generate (photo formats, default 3) [default: 3] |
| `scripts_per_hook` | number | No | Script variations per hook (default 1) [default: 1] |
| `aspect_ratio` | string | No | Custom aspect ratio (e.g. "9:16", "1:1", "4:5", "16:9"). Overrides platform and format defaults. |
| `creative_direction` | string | No | Optional script direction, e.g. "focus on Claude not generating images by default" or "clickbaity discovery angle". |
| `content_archetype` | string | No | Content archetype to tailor output to (lifestyle, inspirational, aspirational, educational, documentary, entertainment, fictional) |
| `persona` | object | No | Creator persona. With appearance details and a turnaround sheet image URL, generated content depicts this person. |
| `persona_image_url` | string | No | Headshot or turnaround URL for face consistency. Required for most formats (all except pov, voiceover_broll, lifestyle_still, aesthetic_moment). |
| `wardrobe_image_url` | string | No | URL to a wardrobe/outfit reference image. When provided, scenes emphasize this clothing in generated frames. |
| `product_images` | array | No | Product image URLs (screenshots, photos, packaging). Required for: product_demo, product_showcase, unboxing, reaction, product_flat_lay, in_use_shot. |
| `platform` | string | No | Target platform — determines aspect ratio, duration limits, hook window, and text placement. Defaults to tiktok. (tiktok, instagram_reels, youtube_shorts, instagram_stories, twitter) |
| `familiar_elements` | object | No | Repetitive brand elements for memorability |
| `llm_model` | string | No | OpenRouter model ID (default google/gemini-2.5-flash) |

**Returns:** Video: hooks, scripts, scenes arrays. Photo: compositions + scenes. Includes output_type, format_name, persona info, aspect_ratio.

**Examples:**

*Generate 3 hooks with scripts and scenes:*

```json
{
  "company_name": "NutriPlan",
  "product_name": "MealAI",
  "product_description": "AI-powered meal planning app",
  "research": {
    "pain_points": [
      "decision fatigue around meals"
    ],
    "emotional_triggers": [
      "overwhelm",
      "guilt"
    ]
  },
  "num_hooks": 3
}
```

### Create Voice Profile (`create_voice_profile`)

Create a reusable voice profile for consistent voice across clips. Provide a voice_sample_url or auto-match from persona + accent. Returns a voice_id for generate_videos. Call after generate_creative.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `voice_sample_url` | string | No | URL to a voice sample audio file (mp3/wav/mp4, 5-30s). If provided, skips auto-generation. |
| `persona` | object | No | Persona for auto voice matching. At least one of voice_sample_url or persona is required. |
| `accent` | string | No | Desired accent (e.g. "british", "american", "australian"). Used for auto voice matching. |
| `sample_text` | string | No | Text to speak for the auto-generated voice sample. Defaults to the first hook text. |
| `scripts` | array | No | Scripts array — scripts[0].hook_text used as default sample text if sample_text not provided. |

**Returns:** Object with voice_id (reusable Kling voice profile ID), source ("user_sample" or "auto_generated"), and optionally elevenlabs_voice_id and elevenlabs_voice_name

**Examples:**

*Auto-generate voice profile from persona:*

```json
{
  "persona": {
    "name": "Chloe",
    "description": "Mixed race woman in her mid-20s from London",
    "tone": "casual and friendly"
  },
  "accent": "british",
  "scripts": [
    {
      "hook_text": "I used to spend 2 hours deciding what to cook"
    }
  ]
}
```

*Use a provided voice sample:*

```json
{
  "voice_sample_url": "https://example.com/my-voice-sample.mp3"
}
```

### Generate First Frames (`generate_frames`)

Step 3: Generate first-frame images from scenes. ~30s async. Pass existing_frames to skip frames that already have image_url. Do NOT re-call if you have frames — pass them as existing_frames instead. persona_image_url for face consistency.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `scenes` | array | Yes | Array of scenes from generate_scenes step |
| `existing_frames` | array | No | Optional: frames from a previous generate_frames call. Frames with image_url will be skipped. Pass this to avoid regenerating frames you already have. |
| `persona_description` | string | No | Visual description of the persona/creator for image generation |
| `persona_image_url` | string | No | Reference image URL for persona. Always pass it — used for image-to-image conditioning to keep the face consistent. |
| `wardrobe_image_url` | string | No | Optional URL to a wardrobe/outfit reference image. When provided, each generated frame will get an edit pass to apply the exact clothing. |
| `platform` | string | No | Target platform — determines default aspect ratio. Defaults to tiktok (9:16). (tiktok, instagram_reels, youtube_shorts, instagram_stories, twitter) |
| `model` | string | No | fal.ai image model ID [default: "fal-ai/nano-banana-2"] |
| `aspect_ratio` | string | No | Image aspect ratio (overrides platform default if set) [default: "9:16"] |

**Returns:** Array of frames with image URLs and scene descriptions, plus asset paths for auto-upload

**Examples:**

*Generate first frames from scenes:*

```json
{
  "scenes": [
    {
      "script_index": 0,
      "scene_description": "Young woman in kitchen, looking frustrated at open fridge, selfie camera angle"
    }
  ]
}
```

### Generate Videos (`generate_videos`)

Step 5: Generate video clips from first frames. Takes 2-10 MINUTES — do NOT retrigger. Long scripts auto-split with continuation keyframes. Use check_video for pending clips.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `frames` | array | Yes | Array of frames from generate_frames step (objects with image_url, scene_description, script_index) |
| `scripts` | array | Yes | Array of scripts from write_scripts step (full script objects with script_body, hook_text, cta) |
| `scenes` | array | Yes | Array of scenes from generate_scenes step (scene objects with shot_list, setting, mood) |
| `persona_description` | string | No | Visual/behavioral description of the persona for video generation |
| `model` | string | No | Video model key (default kling-3.0, resolved via model registry) [default: "kling-3.0"] |
| `duration` | number | No | Target video duration in seconds. Defaults to model maximum. Automatically snapped to the nearest valid duration for the model. |
| `voice_id` | string | No | Kling voice_id from create_voice_profile. When provided, all clips use this voice with synced lip movement. |

**Returns:** Array of video clips with video URLs, script indices, segment indices, and duration. Long scripts are automatically split into multiple clips with consistent continuation keyframes.

**Examples:**

*Generate videos from frames:*

```json
{
  "frames": [
    {
      "image_url": "https://example.com/frame.png",
      "scene_description": "Kitchen selfie scene",
      "script_index": 0
    }
  ],
  "scripts": [
    {
      "hook_text": "I used to spend 2 hours deciding what to cook",
      "script_body": "But then I found this app that plans my whole week in 30 seconds. I just tell it what I like and it gives me a full plan with grocery list.",
      "cta": "Link in bio if you want to try it"
    }
  ],
  "scenes": [
    {
      "shot_list": [
        {
          "timestamp_seconds": 0,
          "description": "Frustrated face at fridge",
          "visual_change": "Close-up to medium shot"
        }
      ],
      "setting": "Modern kitchen",
      "mood": "Relatable frustration to excitement"
    }
  ]
}
```

### Check Video Status (`check_video`)

Check on a pending video that was still generating when generate_videos returned. Pass the fal_request_id and fal_model_id from the pending_videos array. Returns the video URL if ready, or current status if still generating. You can check back any time — the video stays on fal.ai servers.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `fal_request_id` | string | Yes | The fal.ai request ID from pending_videos output |
| `fal_model_id` | string | No | The fal.ai model ID (e.g. fal-ai/kling-video/v3/pro/image-to-video) |

**Returns:** Video URL if completed, or current status (queued/running/failed) with instructions to check again

**Examples:**

*Check on a pending video:*

```json
{
  "fal_request_id": "abc123-def456",
  "fal_model_id": "fal-ai/kling-video/v3/pro/image-to-video"
}
```

### Regenerate Frame (`regenerate_frame`)

Re-generate a single frame without re-running the full pipeline. Accepts optional revision notes for targeted edits. Use when a specific frame needs adjustment — much faster than regenerating all frames.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `frame_index` | number | Yes | Index of the frame to regenerate (0-based, must be within scenes array bounds) |
| `scenes` | array | Yes | Full scenes array from generate_creative or generate_scenes output |
| `persona_description` | string | No | Visual description of the persona for image generation |
| `persona_image_url` | string | No | Reference image URL for persona consistency |
| `wardrobe_image_url` | string | No | Optional wardrobe reference image URL |
| `revision_notes` | string | No | What to change about this frame (e.g. "make her smile more", "change to outdoor setting") |
| `model` | string | No | fal.ai image model ID [default: "fal-ai/nano-banana-2"] |
| `aspect_ratio` | string | No | Image aspect ratio (overrides platform default) |
| `platform` | string | No | Target platform for default aspect ratio (tiktok, instagram_reels, youtube_shorts, instagram_stories, twitter) |

**Returns:** Single frame object with image URL and scene description, plus asset path

**Examples:**

*Regenerate frame 2 with revision notes:*

```json
{
  "frame_index": 2,
  "scenes": [
    {
      "script_index": 0,
      "scene_description": "Kitchen selfie scene"
    },
    {
      "script_index": 1,
      "scene_description": "Living room couch scene"
    },
    {
      "script_index": 2,
      "scene_description": "Outdoor park scene"
    }
  ],
  "revision_notes": "Make her smile more, change to outdoor setting with better lighting"
}
```

### Composite Product (`composite_product`)

REQUIRED for product_showcase format. Composites real product images (screenshots, photos) into generated frames via inpainting. Call after generate_frames, before generate_videos. Pass composited frames to generate_videos.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `frames` | array | Yes | Array of frames from generate_frames (objects with image_url, scene_description, script_index) |
| `product_images` | array | Yes | Array of product image URLs — turnaround sheets, hero shots, packaging photos |
| `product_name` | string | Yes | Product name for the LLM placement prompt |
| `product_description` | string | No | Brief product description to guide natural placement |
| `placement_hints` | array | No | Optional per-frame placement hints (e.g. "holding in left hand", "visible on desk"). One per frame. |
| `strength` | number | No | How much to change the frame (0.0 = no change, 1.0 = full replacement). Default 0.5. [default: 0.5] |

**Returns:** Object with "frames" array (composited frames with product naturally placed in each scene) — pass directly to generate_videos

**Examples:**

*Composite a water bottle into UGC frames:*

```json
{
  "frames": [
    {
      "image_url": "https://example.com/frame.png",
      "scene_description": "Kitchen selfie scene",
      "script_index": 0
    }
  ],
  "product_images": [
    "https://example.com/bottle-front.png",
    "https://example.com/bottle-side.png"
  ],
  "product_name": "HydroFlask Pro",
  "product_description": "Insulated stainless steel water bottle, matte black",
  "placement_hints": [
    "holding casually in right hand"
  ]
}
```

### Assemble Final Video (`assemble_final`)

Stitch video clips into one final video with text overlays and transitions. Last step after generate_videos. Takes ~30-120s. Async — poll with get_job_result.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `videos` | array | Yes | Array of videos from generate_videos (objects with video_url, script_index, duration_seconds) |
| `scripts` | array | Yes | Scripts array for text overlays (hook_text on first clip, cta on last clip) |
| `platform` | string | No | Target platform — determines video format and text placement. Defaults to tiktok. (tiktok, instagram_reels, youtube_shorts, instagram_stories, twitter) |
| `transition` | string | No | Transition effect between clips (fade, slide, flip, clock-wipe, none) [default: "fade"] |
| `transition_duration` | number | No | Transition duration in seconds (0-2) [default: 0.3] |
| `captions` | boolean | No | Whether to overlay hook text and CTA on the video [default: true] |
| `style` | object | No | Optional style overrides for the final video |

**Returns:** Final assembled video file (auto-uploaded via asset system)

**Examples:**

*Assemble TikTok video from clips:*

```json
{
  "videos": [
    {
      "video_url": "https://example.com/clip1.mp4",
      "script_index": 0,
      "duration_seconds": 10
    },
    {
      "video_url": "https://example.com/clip2.mp4",
      "script_index": 1,
      "duration_seconds": 10
    }
  ],
  "scripts": [
    {
      "hook_text": "I used to spend 2 hours deciding what to cook",
      "cta": "Link in bio"
    },
    {
      "hook_text": "But then I found this app",
      "cta": "Link in bio"
    }
  ],
  "platform": "tiktok"
}
```

## Quick Start

```bash
# Via CLI
TOOLROUTER_API_KEY=tr_live_xxx npx -y toolrouter-mcp call ugc-content research_audience --company_name NutriPlan --product_name MealAI --product_description AI-powered meal planning app that creates personalized weekly meal plans based on dietary preferences, budget, and schedule

# Via REST API
curl -H "Authorization: Bearer tr_live_xxx" \
     -d '{"tool":"ugc-content","skill":"research_audience","input":{"company_name":"NutriPlan","product_name":"MealAI","product_description":"AI-powered meal planning app that creates personalized weekly meal plans based on dietary preferences, budget, and schedule"}}' \
     https://api.toolrouter.com/v1/tools/call
```

## Frequently Asked Questions

### What is the fastest way to get a finished UGC ad?

Use the full pipeline when you want speed with structure: research the audience, generate creative, make frames and videos, then assemble the final cut. The tool also has a single-step creative path if you want a quicker draft.

### How do I keep the same face or outfit across clips?

Pass a persona plus `persona_image_url` for face consistency, and use `wardrobe_image_url` when you want the outfit to stay the same from scene to scene.

### Can I use a real product image in the generated scene?

Yes. `composite_product` is built for placing a real product into generated frames, which is useful when the ad needs to show the actual package or device.

### What if one frame or clip looks off?

You do not have to rerun everything. `regenerate_frame` can fix a single scene, and `check_video` lets you inspect clips that are still rendering.

---

## Get Started with ToolRouter

**Already connected?** Call any tool with `use_tool({ tool, skill, input })`. Browse tools with `discover` or visit https://toolrouter.com/llms-full.txt

**New to ToolRouter?** Connect with one command (no API key needed — auto-provisions on first use):

```bash
# MCP (Claude Code, Codex CLI)
claude mcp add toolrouter -- npx -y toolrouter-mcp

# MCP (Cursor, Windsurf, Cline, VS Code, Gemini CLI, Claude Desktop)
{"mcpServers":{"toolrouter":{"command":"npx","args":["-y","toolrouter-mcp"]}}}

# Claude Chat / Claude Cowork / Claude Mobile — add as a connector:
# Settings → Connectors → Add custom connector → URL: https://api.toolrouter.com/mcp

# ChatGPT — add as an app:
# Settings → Apps → Advanced settings → enable Developer mode → Create app → MCP Server URL: https://api.toolrouter.com/mcp

# CLI (works instantly, no restart needed)
npx -y toolrouter-mcp tools                    # browse all tools
npx -y toolrouter-mcp search "seo"             # search by keyword
npx -y toolrouter-mcp call seo analyze_page --url https://example.com
```

Free tools work immediately. For paid tools, visit https://toolrouter.com/claim to add credits.

All platforms: https://toolrouter.com/connect | Full catalog: https://toolrouter.com/llms-full.txt | Docs: https://toolrouter.com/docs