Flow
- CreateAssetGroup to create an asset group and get
Result.Id. - CreateAsset to import an asset from a URL and get
Result.Id. - GetAsset until
Status=Active. - Create a video generation task that references the asset as
asset://<asset ID>, and getid. - Get the video generation task until
status=succeeded, then downloadcontent.video_url.
Complete example
Python 3.9+, standard library only.seedance_demo.py
import hashlib
import hmac
import json
import os
import time
from datetime import datetime, timezone
from urllib.error import HTTPError
from urllib.request import Request, urlopen
API_KEY = os.environ["APIGO_API_KEY"]
HOST = "api-vip.apigo.ai"
MODEL = "dreamina-seedance-2-0-260128"
IMAGE_URL = "https://ark-project.tos-cn-beijing.volces.com/doc_image/r2v_tea_pic1.jpg"
def send(request):
try:
with urlopen(request, timeout=60) as resp:
return json.loads(resp.read())
except HTTPError as e:
raise SystemExit(f"HTTP {e.code}: {e.read().decode('utf-8', 'replace')}")
def asset_api(action, body):
"""Asset library management API: HMAC-SHA256 signing."""
payload = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
x_date = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
day = x_date[:8]
query = f"Action={action}&Version=2024-01-01"
body_hash = hashlib.sha256(payload).hexdigest()
signed_headers = "content-type;host;x-content-sha256;x-date"
canonical_request = "\n".join([
"POST", "/", query,
f"content-type:application/json\nhost:{HOST}\n"
f"x-content-sha256:{body_hash}\nx-date:{x_date}\n",
signed_headers, body_hash,
])
scope = f"{day}/ap-southeast-1/ark/request"
string_to_sign = "\n".join([
"HMAC-SHA256", x_date, scope,
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
])
signing_key = API_KEY.encode("utf-8")
for part in (day, "ap-southeast-1", "ark", "request"):
signing_key = hmac.new(signing_key, part.encode("utf-8"), hashlib.sha256).digest()
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
return send(Request(f"https://{HOST}/?{query}", data=payload, method="POST", headers={
"Content-Type": "application/json",
"X-Date": x_date,
"X-Content-Sha256": body_hash,
"Authorization": f"HMAC-SHA256 Credential={API_KEY}/{scope}, "
f"SignedHeaders={signed_headers}, Signature={signature}",
}))["Result"]
def task_api(method, path, body=None):
"""Video generation API: Bearer authentication."""
data = json.dumps(body).encode("utf-8") if body is not None else None
return send(Request(f"https://{HOST}{path}", data=data, method=method, headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}))
# 1. Create an asset group
group_id = asset_api("CreateAssetGroup", {"Name": "demo-assets", "GroupType": "AIGC"})["Id"]
print("group:", group_id)
# 2. Import an image asset
asset_id = asset_api("CreateAsset", {
"GroupId": group_id,
"URL": IMAGE_URL,
"AssetType": "Image",
"Name": "tea-reference",
})["Id"]
print("asset:", asset_id)
# 3. Wait until the asset is ready
while True:
asset = asset_api("GetAsset", {"Id": asset_id})
print("asset status:", asset["Status"])
if asset["Status"] == "Active":
break
if asset["Status"] == "Failed":
raise SystemExit(f"Asset processing failed: {asset.get('Error')}")
time.sleep(5)
# 4. Create a video generation task
task_id = task_api("POST", "/api/v3/contents/generations/tasks", {
"model": MODEL,
"content": [
{"type": "text", "text": "Steam slowly rises from the cup in image 1 as the camera pushes in slightly."},
{"type": "image_url", "role": "reference_image", "image_url": {"url": f"asset://{asset_id}"}},
],
"duration": 5,
"resolution": "720p",
"ratio": "16:9",
"generate_audio": False,
"watermark": False,
})["id"]
print("task:", task_id)
# 5. Poll for the result
while True:
task = task_api("GET", f"/api/v3/contents/generations/tasks/{task_id}")
print("task status:", task["status"])
if task["status"] not in ("queued", "running"):
break
time.sleep(10)
if task["status"] != "succeeded":
raise SystemExit(f"Task ended: {task['status']} {task.get('error')}")
with urlopen(task["content"]["video_url"], timeout=300) as resp, open("output.mp4", "wb") as f:
f.write(resp.read())
print("saved: output.mp4")
export APIGO_API_KEY="your API key"
python3 seedance_demo.py
cURL example
The asset library management API requires a signature and cannot be called with hand-written cURL. See Authentication and conventions for signing. The video generation API uses Bearer and can be called directly:curl https://api-vip.apigo.ai/api/v3/contents/generations/tasks \
-H "Authorization: Bearer $APIGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "dreamina-seedance-2-0-260128",
"content": [
{"type": "text", "text": "Steam slowly rises from the cup in image 1 as the camera pushes in slightly."},
{"type": "image_url", "role": "reference_image", "image_url": {"url": "asset://<asset ID>"}}
],
"duration": 5,
"resolution": "720p",
"ratio": "16:9",
"generate_audio": false,
"watermark": false
}'
curl https://api-vip.apigo.ai/api/v3/contents/generations/tasks/<task ID> \
-H "Authorization: Bearer $APIGO_API_KEY"
Other reference media combinations
- Image + audio
- Video (with original sound)
Audio must be combined with an image or video. Set
generate_audio to true when using reference audio.{
"model": "dreamina-seedance-2-0-260128",
"content": [
{"type": "text", "text": "Use image 1 as the visual reference and audio 1 as the soundtrack. Do not add dialogue."},
{"type": "image_url", "role": "reference_image", "image_url": {"url": "asset://<image asset ID>"}},
{"type": "audio_url", "role": "reference_audio", "audio_url": {"url": "asset://<audio asset ID>"}}
],
"duration": 5,
"ratio": "16:9",
"generate_audio": true,
"watermark": false
}
The reference video must contain audible sound. Set
generate_audio to true.{
"model": "dreamina-seedance-2-0-260128",
"content": [
{"type": "text", "text": "Use video 1 as the visual and sound reference, keeping the subject, scene and original sound."},
{"type": "video_url", "role": "reference_video", "video_url": {"url": "asset://<video asset ID>"}}
],
"duration": 5,
"ratio": "16:9",
"generate_audio": true,
"watermark": false
}
