Docs / Examples / Image-to-Video
Image-to-Video
Animate a still image into a short clip. The image goes into metadata.content[] as a reference_image, and the prompt cites it as @image1.
#Workflow
- Get a URL for your image: upload it via
POST /v1/files, or use any public URL you already host - Submit
POST /v1/video/generationswith the image inmetadata.content[]and a prompt citing@image1 - Poll
GET /v1/video/generations/{task_id}untilsucceeded, or setmetadata.callback_urland wait for the webhook - Download the clip from the
urlfield
#cURL
terminalbash
# 1) Submit
curl https://relaydance.com/v1/video/generations \
-H "Authorization: Bearer $RELAYDANCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-1-5-pro-with-audio",
"prompt": "@image1 comes alive: gentle camera pull-back, hair moving in a soft breeze",
"seconds": "5",
"metadata": {
"ratio": "16:9",
"resolution": "720p",
"content": [
{
"type": "image_url",
"image_url": { "url": "https://example.com/portrait.jpg" },
"role": "reference_image"
}
]
}
}'
# -> { "task_id": "...", "status": "submitted" }
# 2) Poll (replace TASK_ID)
curl https://relaydance.com/v1/video/generations/TASK_ID \
-H "Authorization: Bearer $RELAYDANCE_API_KEY"#Python (upload + submit + poll)
image_to_video.pypython
import requests, os, time
BASE = "https://relaydance.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['RELAYDANCE_API_KEY']}"}
# 1) Upload the local image (skip if you already have a public URL)
upload = requests.post(
f"{BASE}/files",
headers=HEADERS,
files={"file": open("portrait.jpg", "rb")},
)
image_url = upload.json()["url"]
# 2) Submit the video task
submit = requests.post(
f"{BASE}/video/generations",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"model": "seedance-1-5-pro-with-audio",
"prompt": "@image1 comes alive: gentle camera pull-back, soft natural light",
"seconds": "5",
"metadata": {
"ratio": "16:9",
"resolution": "720p",
"content": [
{
"type": "image_url",
"image_url": {"url": image_url},
"role": "reference_image",
}
],
},
},
)
task_id = submit.json()["task_id"]
# 3) Poll until the clip is ready
while True:
task = requests.get(f"{BASE}/video/generations/{task_id}", headers=HEADERS).json()
if task["status"] == "succeeded":
print("Video:", task["url"])
break
if task["status"] == "failed":
raise RuntimeError(task)
time.sleep(5)#Node.js / TypeScript
imageToVideo.tstypescript
const BASE = "https://relaydance.com/v1";
const KEY = process.env.RELAYDANCE_API_KEY;
// 1) Submit with a hosted image URL
const submit = await fetch(`${BASE}/video/generations`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
model: "seedance-1-5-pro-with-audio",
prompt: "@image1 comes alive: gentle camera pull-back, soft natural light",
seconds: "5",
metadata: {
ratio: "16:9",
resolution: "720p",
content: [
{
type: "image_url",
image_url: { url: "https://example.com/portrait.jpg" },
role: "reference_image",
},
],
},
}),
});
const { task_id } = await submit.json();
// 2) Poll
while (true) {
const r = await fetch(`${BASE}/video/generations/${task_id}`, {
headers: { Authorization: `Bearer ${KEY}` },
});
const task = await r.json();
if (task.status === "succeeded") { console.log("Video:", task.url); break; }
if (task.status === "failed") throw new Error(JSON.stringify(task));
await new Promise((s) => setTimeout(s, 5000));
}Prefer webhooks in production: add
callback_url inside metadata and the final task JSON is POSTed to you, no polling loop needed.#Model choices
| Model | Why pick it |
|---|---|
seedance-1-5-pro-with-audio | Image-to-video with a generated soundtrack |
seedance-1-5-pro-no-audio | Same family, silent output |
doubao-seedance-2-0-1080p | Highest fidelity, multi-reference fusion |
happyhorse-1.0-i2v | Dedicated image-to-video model |
Up to 9 reference images per request, cited as
@image1 to @imageN in content[] order. Reference images are mutually exclusive with first/last-frame mode. See Video Generation for the full schema.