[ PROMPT_NODE_24857 ]
Photo Avatars
[ SKILL_DOCUMENTATION ]
# Photo Avatars (Talking Photos)
Photo avatars allow you to animate a static photo and make it speak. This is useful for creating personalized video content from portraits, headshots, or any suitable image.
## Overview
HeyGen offers several approaches to photo-based avatars:
| Type | Description | Quality |
|------|-------------|---------|
| Talking Photo | Basic photo animation | Good |
| Photo Avatar | Enhanced photo avatar with motion | Better |
| Avatar IV | Latest generation photo avatar | Best |
## Uploading a Photo for Talking Photo
### Step 1: Upload the Image
```typescript
// Upload photo as an asset
const assetId = await uploadFile("./portrait.jpg", "image/jpeg");
```
### Step 2: Create Talking Photo
```bash
curl -X POST "https://api.heygen.com/v2/talking_photo"
-H "X-Api-Key: $HEYGEN_API_KEY"
-H "Content-Type: application/json"
-d '{
"image_url": "https://files.heygen.ai/asset/your_asset_id"
}'
```
### TypeScript
```typescript
interface TalkingPhotoResponse {
error: null | string;
data: {
talking_photo_id: string;
};
}
async function createTalkingPhoto(imageUrl: string): Promise {
const response = await fetch("https://api.heygen.com/v2/talking_photo", {
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_url: imageUrl }),
});
const json: TalkingPhotoResponse = await response.json();
if (json.error) {
throw new Error(json.error);
}
return json.data.talking_photo_id;
}
```
## Using Talking Photo in Video
```typescript
const videoConfig = {
video_inputs: [
{
character: {
type: "talking_photo",
talking_photo_id: "your_talking_photo_id",
},
voice: {
type: "text",
input_text: "Hello! This is my talking photo speaking!",
voice_id: "1bd001e7e50f421d891986aad5158bc8",
},
},
],
dimension: { width: 1920, height: 1080 },
};
const videoId = await generateVideo(videoConfig);
```
## Avatar IV API
Avatar IV is HeyGen's latest photo avatar technology with improved quality and natural motion.
### Generate Avatar IV Video
```bash
curl -X POST "https://api.heygen.com/v2/video/av4/generate"
-H "X-Api-Key: $HEYGEN_API_KEY"
-H "Content-Type: application/json"
-d '{
"photo_s3_key": "path/to/uploaded/photo.jpg",
"script": "Hello! This is Avatar IV with enhanced quality.",
"voice_id": "1bd001e7e50f421d891986aad5158bc8",
"video_orientation": "portrait",
"video_title": "My Avatar IV Video"
}'
```
### TypeScript
```typescript
interface AvatarIVRequest {
photo_s3_key: string;
script: string;
voice_id: string;
video_orientation?: "portrait" | "landscape" | "square";
video_title?: string;
fit?: "cover" | "contain";
custom_motion_prompt?: string;
enhance_custom_motion_prompt?: boolean;
}
interface AvatarIVResponse {
error: null | string;
data: {
video_id: string;
};
}
async function generateAvatarIVVideo(config: AvatarIVRequest): Promise {
const response = await fetch(
"https://api.heygen.com/v2/video/av4/generate",
{
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify(config),
}
);
const json: AvatarIVResponse = await response.json();
if (json.error) {
throw new Error(json.error);
}
return json.data.video_id;
}
```
## Avatar IV Options
### Video Orientation
| Orientation | Dimensions | Use Case |
|-------------|------------|----------|
| `portrait` | 720x1280 | TikTok, Stories |
| `landscape` | 1280x720 | YouTube, Web |
| `square` | 720x720 | Instagram Feed |
### Fit Options
| Fit | Description |
|-----|-------------|
| `cover` | Fill the frame, may crop edges |
| `contain` | Fit entire image, may show background |
### Custom Motion Prompts
Add specific motion or expression:
```typescript
const config = {
photo_s3_key: "path/to/photo.jpg",
script: "Let me tell you about our product.",
voice_id: "voice_id",
custom_motion_prompt: "nodding head and smiling",
enhance_custom_motion_prompt: true,
};
```
## Photo Avatar Groups
Organize multiple photo looks:
### Create Photo Avatar Group
```typescript
interface PhotoAvatarGroupRequest {
image_key: string;
name: string;
generation_id?: string;
}
async function createPhotoAvatarGroup(
imageKey: string,
name: string
): Promise {
const response = await fetch(
"https://api.heygen.com/v2/photo_avatar/avatar_group/create",
{
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_key: imageKey, name }),
}
);
const json = await response.json();
if (json.error) {
throw new Error(json.error);
}
return json.data.id;
}
```
### Add Photos to Group
```typescript
async function addPhotosToGroup(
groupId: string,
imageKeys: string[],
name: string
): Promise {
const response = await fetch(
"https://api.heygen.com/v2/photo_avatar/avatar_group/add",
{
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
group_id: groupId,
image_keys: imageKeys,
name,
}),
}
);
const json = await response.json();
if (json.error) {
throw new Error(json.error);
}
}
```
## Photo Requirements
For best results, use photos that meet these criteria:
### Technical Requirements
| Aspect | Requirement |
|--------|-------------|
| Format | JPEG, PNG |
| Resolution | Minimum 512x512px |
| File size | Under 10MB |
| Face visibility | Clear, front-facing |
### Quality Guidelines
1. **Lighting** - Even, natural lighting on face
2. **Expression** - Neutral or slight smile
3. **Background** - Simple, uncluttered
4. **Face position** - Centered, not cut off
5. **Clarity** - Sharp, in focus
6. **Angle** - Straight-on or slight angle
## Generating AI Photo Avatars
Generate synthetic photo avatars from text descriptions.
> **IMPORTANT: All 8 fields are REQUIRED.** The API will reject requests missing any field.
> You cannot simply provide a text description - you MUST specify each enum field explicitly.
> When a user asks to "generate an AI avatar of a professional man", you need to ask for or select values for ALL fields below.
### Required Fields (ALL must be provided)
| Field | Type | Allowed Values |
|-------|------|----------------|
| `name` | string | Name for the generated avatar |
| `age` | enum | `"Young Adult"`, `"Early Middle Age"`, `"Late Middle Age"`, `"Senior"`, `"Unspecified"` |
| `gender` | enum | `"Woman"`, `"Man"`, `"Unspecified"` |
| `ethnicity` | enum | `"White"`, `"Black"`, `"Asian American"`, `"East Asian"`, `"South East Asian"`, `"South Asian"`, `"Middle Eastern"`, `"Pacific"`, `"Hispanic"`, `"Unspecified"` |
| `orientation` | enum | `"square"`, `"horizontal"`, `"vertical"` |
| `pose` | enum | `"half_body"`, `"close_up"`, `"full_body"` |
| `style` | enum | `"Realistic"`, `"Pixar"`, `"Cinematic"`, `"Vintage"`, `"Noir"`, `"Cyberpunk"`, `"Unspecified"` |
| `appearance` | string | Text prompt describing appearance (clothing, mood, lighting, etc). Max 1000 chars |
### curl Example
```bash
curl -X POST "https://api.heygen.com/v2/photo_avatar/photo/generate"
-H "X-Api-Key: $HEYGEN_API_KEY"
-H "Content-Type: application/json"
-d '{
"name": "Sarah Product Demo",
"age": "Young Adult",
"gender": "Woman",
"ethnicity": "White",
"orientation": "horizontal",
"pose": "half_body",
"style": "Realistic",
"appearance": "Professional woman with a friendly smile, wearing a navy blue blazer over a white blouse, soft studio lighting, clean neutral background"
}'
```
### TypeScript
```typescript
// All fields are REQUIRED - the API will reject requests with missing fields
interface GeneratePhotoAvatarRequest {
name: string; // Name for the avatar
age: "Young Adult" | "Early Middle Age" | "Late Middle Age" | "Senior" | "Unspecified";
gender: "Woman" | "Man" | "Unspecified";
ethnicity: "White" | "Black" | "Asian American" | "East Asian" | "South East Asian" | "South Asian" | "Middle Eastern" | "Pacific" | "Hispanic" | "Unspecified";
orientation: "square" | "horizontal" | "vertical";
pose: "half_body" | "close_up" | "full_body";
style: "Realistic" | "Pixar" | "Cinematic" | "Vintage" | "Noir" | "Cyberpunk" | "Unspecified";
appearance: string; // Max 1000 characters
callback_url?: string; // Optional: webhook for completion notification
callback_id?: string; // Optional: custom ID for tracking
}
interface GeneratePhotoAvatarResponse {
error: string | null;
data: {
generation_id: string;
};
}
async function generatePhotoAvatar(
config: GeneratePhotoAvatarRequest
): Promise {
const response = await fetch(
"https://api.heygen.com/v2/photo_avatar/photo/generate",
{
method: "POST",
headers: {
"X-Api-Key": process.env.HEYGEN_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify(config),
}
);
const json: GeneratePhotoAvatarResponse = await response.json();
if (json.error) {
throw new Error(`Photo avatar generation failed: ${json.error}`);
}
return json.data.generation_id;
}
```
### Example: Generate Professional Avatar
```typescript
const generationId = await generatePhotoAvatar({
name: "Tech Demo Presenter",
age: "Early Middle Age",
gender: "Man",
ethnicity: "East Asian",
orientation: "horizontal",
pose: "half_body",
style: "Realistic",
appearance: "Professional man in a modern office setting, wearing a dark gray suit with no tie, confident and approachable expression, soft natural lighting from a window, clean minimalist background"
});
console.log(`Generation started: ${generationId}`);
// Save generationId to poll for status later
```
### Check Generation Status
```typescript
interface PhotoGenerationStatus {
error: string | null;
data: {
status: "pending" | "processing" | "completed" | "failed";
image_url?: string; // Available when completed
image_key?: string; // S3 key for use in video generation
};
}
async function checkPhotoGeneration(generationId: string): Promise {
const response = await fetch(
`https://api.heygen.com/v2/photo_avatar/generation/${generationId}`,
{ headers: { "X-Api-Key": process.env.HEYGEN_API_KEY! } }
);
return response.json();
}
// Poll for completion
async function waitForPhotoGeneration(generationId: string): Promise {
const maxAttempts = 60;
const pollIntervalMs = 5000; // 5 seconds
for (let i = 0; i setTimeout(r, pollIntervalMs));
}
throw new Error("Photo generation timed out");
}
```
### Pre-Generation Checklist
Before calling the API, ensure you have values for ALL fields:
| # | Field | Question to Ask / Default |
|---|-------|---------------------------|
| 1 | `name` | What should we call this avatar? |
| 2 | `age` | Young Adult / Early Middle Age / Late Middle Age / Senior? |
| 3 | `gender` | Woman / Man? |
| 4 | `ethnicity` | Which ethnicity? (see enum values above) |
| 5 | `orientation` | horizontal (landscape) / vertical (portrait) / square? |
| 6 | `pose` | half_body (recommended) / close_up / full_body? |
| 7 | `style` | Realistic (recommended) / Cinematic / other? |
| 8 | `appearance` | Describe clothing, expression, lighting, background |
**If the user only provides a vague request** like "create a professional looking man", ask them to specify the missing fields OR make reasonable defaults (e.g., "Early Middle Age", "Realistic" style, "half_body" pose, "horizontal" orientation).
### Appearance Prompt Tips
The `appearance` field is a text prompt - be descriptive:
**Good prompts:**
- "Professional woman with shoulder-length brown hair, wearing a light blue button-down shirt, warm friendly smile, soft studio lighting, clean white background"
- "Young man with short black hair, casual tech startup style, wearing a dark hoodie, confident expression, modern office background with plants"
**Avoid:**
- Vague descriptions: "a nice person"
- Conflicting attributes
- Requesting specific real people
## Complete Workflow: Photo to Video
```typescript
async function createVideoFromPhoto(
photoPath: string,
script: string,
voiceId: string
): Promise {
// 1. Upload photo
console.log("Uploading photo...");
const assetId = await uploadFile(photoPath, "image/jpeg");
// 2. Create talking photo
console.log("Creating talking photo...");
const talkingPhotoId = await createTalkingPhoto(
`https://files.heygen.ai/asset/${assetId}`
);
// 3. Generate video
console.log("Generating video...");
const videoId = await generateVideo({
video_inputs: [
{
character: {
type: "talking_photo",
talking_photo_id: talkingPhotoId,
},
voice: {
type: "text",
input_text: script,
voice_id: voiceId,
},
},
],
dimension: { width: 1920, height: 1080 },
});
// 4. Wait for completion
console.log("Processing video...");
const videoUrl = await waitForVideo(videoId);
return videoUrl;
}
```
## Managing Photo Avatars
### Get Photo Avatar Details
```typescript
async function getPhotoAvatar(id: string) {
const response = await fetch(
`https://api.heygen.com/v2/photo_avatar/${id}`,
{ headers: { "X-Api-Key": process.env.HEYGEN_API_KEY! } }
);
return response.json();
}
```
### Delete Photo Avatar
```typescript
async function deletePhotoAvatar(id: string): Promise {
const response = await fetch(
`https://api.heygen.com/v2/photo_avatar/${id}`,
{
method: "DELETE",
headers: { "X-Api-Key": process.env.HEYGEN_API_KEY! },
}
);
if (!response.ok) {
throw new Error("Failed to delete photo avatar");
}
}
```
## Best Practices
1. **Use high-quality photos** - Better input = better output
2. **Front-facing portraits** - Work best for animation
3. **Neutral expressions** - Allow for more natural animation
4. **Test different photos** - Results vary by image
5. **Consider Avatar IV** - For highest quality results
6. **Organize with groups** - Keep photo avatars organized
## Limitations
- Photo quality significantly affects output
- Side-profile photos have limited support
- Full-body photos may not animate properly
- Some expressions may look unnatural
- Processing time varies by complexity
Source: claude-code-templates (MIT). See About Us for full credits.