Generate animated face videos from portrait images and voice audio. Submit an image and audio file to receive a video with the face animated to match the speech.
This API applies only to the China (Beijing) region. Use an API key from the China (Beijing) region.
How it works
The EMO API uses a two-step asynchronous workflow:
-
Create a task -- Submit a portrait image and audio file. Receive a
task_idimmediately. -
Query the result -- Poll the task status with the
task_id. When the status reachesSUCCEEDED, download the generated video.
Video generation takes several minutes. Task IDs are valid for 24 hours after creation.
Performance showcase
| Sample input | Sample output |
|---|---|
Portrait: Voice audio: (sample audio) |
Sample output video Action style strength: style_level set to active. |
For more examples, see Performance showcase.
Ensure uploaded images and audio files are legally compliant and you have the necessary permissions for their use.
Prerequisites
-
Activate the model service, create an API key, and export it as an environment variable.
-
Process the input image with the EMO image detection API to get face area (
face_bbox) and dynamic area (ext_bbox) coordinates. Both are required parameters.
Step 1: Create a task
Submit the portrait image and audio file to create a video generation task. Receive a task_id for Step 2.
Endpoint
POST https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis
Task IDs are valid for 24 hours after creation.
Request headers
| Header | Type | Required | Description |
|---|---|---|---|
X-DashScope-Async |
string | Required | Asynchronous processing mode. Set to enable. HTTP requests support only asynchronous processing. Without this header, requests fail with "current user api does not support synchronous calls". |
Authorization |
string | Required | Authentication credential in format Bearer {API_KEY} using a Model Studio API key. Example: Bearer sk-xxxx. |
Content-Type |
string | Required | Request content type. Set to application/json. |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
model |
string | Required | Model name. Set to emo-v1. |
input |
object | Required | Input data containing the image, audio, and bounding box coordinates. See Input parameters. |
parameters |
object | Optional | Generation settings. See Parameters object. |
Input parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
image_url |
string | Required | Portrait image URL. The model crops the image using ext_bbox. The cropped area's aspect ratio determines the output video resolution. See Image requirements. |
audio_url |
string | Required | Voice audio file URL for EMO model inference. See Audio requirements. |
face_bbox |
array | Required | Face area bounding box coordinates in format [x1, y1, x2, y2] (top-left and bottom-right corners). Obtain these from the face_bbox field in the EMO image detection API response. Example: [302, 286, 610, 593]. |
ext_bbox |
array | Required | Dynamic area bounding box coordinates in format [x1, y1, x2, y2] (top-left and bottom-right corners). Aspect ratio must be 1:1 or 3:4. Obtain these from the ext_bbox field in the EMO image detection API response. Example: [71, 9, 840, 778]. |
The coordinate origin (0,0) is at the top-left corner of the image. The x-axis extends right and the y-axis extends downward.
Parameters object
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
style_level |
string | Optional | normal |
Controls the character's motion amplitude. Allowed values: normal (moderate motion), calm (calm motion), active (active motion). |
Image requirements
-
The aspect ratio of
ext_bboxdetermines the output video dimensions:-
1:1 aspect ratio produces a 512 x 512 profile picture video.
-
3:4 aspect ratio produces a 512 x 704 half-body portrait video.
-
-
Minimum side length: 400 pixels.
-
Maximum side length: 7,000 pixels.
-
Supported formats: JPG, JPEG, PNG, BMP, and WebP.
-
The image must be an HTTP or HTTPS URL. Local file paths are not supported.
Audio requirements
-
Audio must contain clear human voice. For best results, remove background noise and music.
-
Maximum file size: 15 MB.
-
Maximum duration: 60 seconds.
-
Supported formats: WAV and MP3.
-
The audio must be an HTTP or HTTPS URL. Local file paths are not supported.
Request examples
curl --location 'https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis' \
--header 'X-DashScope-Async: enable' \
--header "Authorization: Bearer $DASHSCOPE_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"model": "emo-v1",
"input": {
"image_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251225/onmomb/emo.png",
"audio_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250825/aejgyj/input_audio.mp3",
"face_bbox":[302,286,610,593],
"ext_bbox":[71,9,840,778]
},
"parameters": {
"style_level": "normal"
}
}'import requests
import os
# 1. Get the API key from an environment variable.
api_key = os.getenv("DASHSCOPE_API_KEY")
# 2. Prepare the request.
url = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis'
headers = {
'X-DashScope-Async': 'enable',
'Authorization': f'Bearer {api_key}',
}
payload = {
"model": "emo-v1",
"input": {
"image_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251225/onmomb/emo.png",
"audio_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250825/aejgyj/input_audio.mp3",
"face_bbox": [302, 286, 610, 593],
"ext_bbox": [71, 9, 840, 778]
},
"parameters": {
"style_level": "normal"
}
}
# 3. Send the POST request.
response = requests.post(url, headers=headers, json=payload)
# 4. Print the response.
print(response.json())import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
/**
* Requirements:
* - Java 8 or later.
* - The DASHSCOPE_API_KEY environment variable must be set at runtime.
**/
public class DashScopeApiDemo {
public static void main(String[] args) throws IOException {
// 1. Get the API key from an environment variable.
String apiKey = System.getenv("DASHSCOPE_API_KEY");
// 2. Prepare the request.
String urlString = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis";
String payload = "{"
+ "\"model\": \"emo-v1\","
+ "\"input\": {"
+ "\"image_url\": \"https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251225/onmomb/emo.png\","
+ "\"audio_url\": \"https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250825/aejgyj/input_audio.mp3\","
+ "\"face_bbox\": [302, 286, 610, 593],"
+ "\"ext_bbox\": [71, 9, 840, 778]"
+ "},"
+ "\"parameters\": {"
+ "\"style_level\": \"normal\""
+ "}"
+ "}";
// 3. Send the POST request.
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization", "Bearer " + apiKey);
connection.setRequestProperty("X-DashScope-Async", "enable");
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = payload.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 4. Get and print the server response.
int statusCode = connection.getResponseCode();
System.out.println("Status Code: " + statusCode);
InputStream inputStream = (statusCode >= 200 && statusCode < 300)
? connection.getInputStream()
: connection.getErrorStream();
String responseBody;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
responseBody = reader.lines().collect(Collectors.joining("\n"));
}
System.out.println("Response Body: " + responseBody);
connection.disconnect();
}
}// The node-fetch package is required.
// npm install node-fetch@2
const fetch = require('node-fetch');
// 1. Get the API key from an environment variable.
const apiKey = process.env.DASHSCOPE_API_KEY;
// 2. Prepare the request.
const url = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis';
const headers = {
'X-DashScope-Async': 'enable',
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
};
const payload = {
"model": "emo-v1",
"input": {
"image_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251225/onmomb/emo.png",
"audio_url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250825/aejgyj/input_audio.mp3",
"face_bbox": [302, 286, 610, 593],
"ext_bbox": [71, 9, 840, 778]
},
"parameters": {
"style_level": "normal"
}
};
// 3. Send a POST request and process the response.
fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
// 4. Print the JSON data returned by the server.
console.log(data);
});Response parameters
| Parameter | Type | Description |
|---|---|---|
output |
object | Task output information. |
output.task_id |
string | ID of the asynchronous task. Use this ID in Step 2 to query the result. Example: a8532587-fa8c-4ef8-82be-xxxxxx. |
output.task_status |
string | Task status after submission. Value: PENDING. |
request_id |
string | Unique request ID for tracing and troubleshooting. |
code |
string | Error code. Returned only when the request fails. See Error codes. |
message |
string | Error message. Returned only when the request fails. See Error codes. |
Response examples
Sample success response
{
"output": {
"task_id": "a8532587-fa8c-4ef8-82be-xxxxxx",
"task_status": "PENDING"
},
"request_id": "7574ee8f-38a3-4b1e-9280-11c33ab46e51"
}
Sample error response
{
"code": "InvalidParameter",
"message": "The specified parameter is not valid.",
"request_id": "4909100c-7b5a-9f92-bfe5-xxxxxx"
}
Step 2: Query the result
Use the task_id from Step 1 to poll the task status and retrieve the generated video.
Endpoint
GET https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}
Replace {task_id} with the task ID from Step 1.
Important notes
-
task_idvalidity: Task IDs are valid for 24 hours after creation. After expiration, API returns statusUNKNOWN. -
Task status flow:
PENDING→RUNNING→SUCCEEDEDorFAILED. -
Polling: Video generation takes several minutes. Query API limit: 20 QPS. Poll every 15 seconds or longer.
-
video_urlvalidity: Video URLs are valid for 24 hours after task success. Download and transfer to permanent storage (e.g., What is OSS?) immediately.
Request headers
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Required | Authentication credential in format Bearer {API_KEY} using a Model Studio API key. Example: Bearer sk-xxxx. |
URL path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id |
string | Required | Task ID from Step 1. Example: a8532587-fa8c-4ef8-82be-xxxxxx. |
Request examples
curl -X GET \
--header "Authorization: Bearer $DASHSCOPE_API_KEY" \
https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}import requests
import os
# 1. Get the API key from the environment variable.
api_key = os.getenv("DASHSCOPE_API_KEY")
# 2. Replace this with the actual task ID.
task_id = "a8532587-fa8c-4ef8-82be-xxxxxx"
# 3. Prepare the request.
url = f"https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}"
headers = {
'Authorization': f'Bearer {api_key}'
}
# 4. Send a GET request.
response = requests.get(url, headers=headers)
# 5. Print the response.
print(response.json())import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
/**
* Requirements:
* - Java 8 or later.
* - The DASHSCOPE_API_KEY environment variable must be set at runtime.
**/
public class TaskStatusChecker {
public static void main(String[] args) throws IOException {
// 1. Get the API key from the environment variable.
String apiKey = System.getenv("DASHSCOPE_API_KEY");
// 2. Replace this with the actual task ID.
String taskId = "a8532587-fa8c-4ef8-82be-xxxxxx";
// 3. Prepare the request.
String urlString = "https://dashscope.aliyuncs.com/api/v1/tasks/" + taskId;
// 4. Create a connection and send a GET request.
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + apiKey);
// 5. Get and print the server response.
int statusCode = connection.getResponseCode();
System.out.println("Status Code: " + statusCode);
InputStream inputStream = (statusCode >= 200 && statusCode < 300)
? connection.getInputStream()
: connection.getErrorStream();
String responseBody;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
responseBody = reader.lines().collect(Collectors.joining("\n"));
}
System.out.println("Response Body: " + responseBody);
connection.disconnect();
}
}// The node-fetch package is required.
// npm install node-fetch@2
const fetch = require('node-fetch');
// 1. Get the API key from the environment variable.
const apiKey = process.env.DASHSCOPE_API_KEY;
// 2. Replace this with the actual task ID.
const taskId = "a8532587-fa8c-4ef8-82be-xxxxxx";
// 3. Prepare the request.
const url = `https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`;
const headers = {
'Authorization': `Bearer ${apiKey}`
};
// 4. Send a GET request.
fetch(url, {
headers: headers
})
.then(response => response.json())
.then(data => {
// 5. Print the response.
console.log(data);
});Response parameters
| Parameter | Type | Description |
|---|---|---|
request_id |
string | Unique request ID for tracing and troubleshooting. |
output |
object | Task output information. |
output.task_id |
string | ID of the queried task. Example: a8532587-fa8c-4ef8-82be-xxxxxx. |
output.task_status |
string | Current task status. See Response examples. |
output.submit_time |
string | Time when the task was submitted (UTC+8). Example: 2025-09-11 14:33:38.716. |
output.scheduled_time |
string | Time when the task was scheduled to start (UTC+8). Example: 2025-09-11 14:33:53.089. |
output.end_time |
string | Time when the task finished (UTC+8). Example: 2025-09-11 14:35:51.541. |
output.results |
object | Task execution result. Present when task_status is SUCCEEDED. |
output.results.video_url |
string | Generated video URL, valid for 24 hours after task completion. Download and save promptly. Example: http://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/xxx.mp4?Expires=xxxx. |
output.code |
string | Error code. Present when task_status is FAILED. See Error codes. |
output.message |
string | Error message. Present when task_status is FAILED. See Error codes. |
usage |
object | Resource usage information. Present when task_status is SUCCEEDED. |
usage.video_duration |
float | Duration of the generated video, in seconds. Example: 13.93. |
usage.video_ratio |
string | Aspect ratio of the generated video. Value: 1:1 or 3:4. |
Task status values
| Status | Description |
|---|---|
PENDING |
Task is queued, waiting to be processed. |
RUNNING |
Task is being processed. |
SUCCEEDED |
Task completed successfully. video_url is available in the response. |
FAILED |
Task failed. Check output.code and output.message for details. |
CANCELED |
Task was canceled. |
UNKNOWN |
Task does not exist or status cannot be determined. Returned when the task_id has expired (after 24 hours). |
Response examples
Sample success response
{
"request_id": "8190395f-ca1b-4703-9656-xxxxxx",
"output": {
"task_id": "a8532587-fa8c-4ef8-82be-xxxxxx",
"task_status": "SUCCEEDED",
"submit_time": "2025-09-11 14:33:38.716",
"scheduled_time": "2025-09-11 14:33:53.089",
"end_time": "2025-09-11 14:35:51.541",
"results": {
"video_url": "http://dashscope-result-sh.oss-cn-shanghai.aliyuncs.com/xxx.mp4?Expires=xxxx"
}
},
"usage": {
"video_duration": 13.93,
"video_ratio": "1:1"
}
}
Sample error response
{
"output": {
"task_id": "a8532587-fa8c-4ef8-82be-xxxxxx",
"task_status": "FAILED",
"code": "InvalidURL",
"message": "Required URL is missing or invalid, please check the request URL."
},
"request_id": "4d687387-580a-4b49-a1f8-4691289e09a3"
}
Billing and rate limits
Pricing
emo-v1 uses pay-as-you-go billing based on generated video duration.
| Aspect ratio | Resolution | Unit price |
|---|---|---|
| 1:1 | 512 x 512 | USD 0.011469 per second |
| 3:4 | 512 x 704 | USD 0.022937 per second |
Cost examples:
-
A 10-second video at 1:1 aspect ratio costs approximately USD 0.11.
-
A 30-second video at 3:4 aspect ratio costs approximately USD 0.69.
Rate limits
| Limit type | Value |
|---|---|
| Task submission QPS | 5 |
| Concurrent tasks | 1 (excess tasks queued) |
| Task query QPS | 20 |
Error codes
For common error codes and status codes, see Error messages.
Voice audio: (sample audio)