
seedance-2.5/text-to-video turns a text prompt into a short video clip — no starting image or reference footage required. This guide has a copy-pasteable curl and Python example against the real hiapi task API, the exact input schema, and the errors you'll actually hit in production.
sk-...) from the API Keys dashboard.requests installed (pip install requests).text-to-video needs only a prompt string, unlike image-to-video or reference-to-video variants that require input media URLs.Every generation model on hiapi runs through the same unified endpoint, POST /v1/tasks. seedance-2.5/text-to-video is called exactly like every other model — same auth header, same async task lifecycle — only model and input change. The model id is seedance-2.5/text-to-video, with the /text-to-video suffix; it is not an optional modality tag, and it's a different id from seedance-2.5/image-to-video.
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.5/text-to-video",
"input": {
"prompt": "a paper airplane gliding through a sunlit office, dust motes drifting in the light",
"duration": 4,
"resolution": "720p",
"aspect_ratio": "16:9"
}
}'
A successful call returns a task id immediately — generation itself happens asynchronously:
{"code":200,"data":{"taskId":"tk-hiapi-01XXXXXXXXXXXXXXXXXXXXXXXX"},"message":"success"}
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01XXXXXXXXXXXXXXXXXXXXXXXX \
-H "Authorization: Bearer sk-<your-api-key>"
While the clip is rendering, status is "handling". Once it finishes:
{
"code": 200,
"data": {
"taskId": "tk-hiapi-01XXXXXXXXXXXXXXXXXXXXXXXX",
"model": "seedance-2.5/text-to-video",
"status": "success",
"storage": "temp",
"created": 1786327257,
"completed": 1786327501,
"output": [
{"artifactId": "72583", "type": "video", "url": "https://temp.hiapi.ai/.../result.mp4", "expireAt": 1786932195}
]
},
"message": "success"
}
output[0].url is a temporary, expiring link — expireAt is a Unix timestamp. Download or re-host the clip right away; don't store the hot link.
import time
import requests
API_KEY = "sk-<your-api-key>"
BASE = "https://api.hiapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def create_task(prompt, duration=4, resolution="720p", aspect_ratio="16:9"):
payload = {
"model": "seedance-2.5/text-to-video",
"input": {
"prompt": prompt,
"duration": duration,
"resolution": resolution,
"aspect_ratio": aspect_ratio,
},
}
resp = requests.post(f"{BASE}/tasks", headers=HEADERS, json=payload, timeout=30)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_for_result(task_id, interval=5, timeout=600):
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(f"{BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
data = resp.json()["data"]
if data["status"] == "success":
return data["output"][0]["url"]
if data["status"] == "failed":
raise RuntimeError(f"task {task_id} failed: {data}")
time.sleep(interval)
raise TimeoutError(f"task {task_id} did not finish in {timeout}s")
task_id = create_task(
prompt="a paper airplane gliding through a sunlit office, dust motes drifting in the light",
)
video_url = wait_for_result(task_id)
print(video_url)
input is validated strictly (additionalProperties: false) — unknown fields are rejected before any generation starts.
prompt (string).duration (integer, 4–30 seconds), resolution ("480p" or "720p"), aspect_ratio (one of 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive).{
"model": "seedance-2.5/text-to-video",
"input": {
"prompt": "a slow drone shot rising over a foggy pine forest at dawn",
"duration": 8,
"resolution": "480p",
"aspect_ratio": "9:16"
}
}
Omit duration, resolution, and aspect_ratio and the model falls back to its defaults — pass them explicitly when your product needs a predictable shape (a 9:16 clip for a mobile feed, for example).
seed, negative_prompt, ratio (use aspect_ratio), fps, and any *_urls field (image_urls, reference_video_urls, and similar belong to the image-to-video and reference-to-video variants, not this one) all get rejected outright. If you're porting code from seedance-2.5/reference-to-video, drop every reference-media field first.
Cost scales with output duration and resolution — 720p renders bill at a higher per-second rate than 480p, and pricing differs across the seedance-2.5 family's endpoints (text-to-video, image-to-video, reference-to-video are each priced separately). Check the current per-second rate on pricing before batching requests or estimating a monthly bill, since video-tier costs are meaningfully higher than image-tier costs.
{
"model": "seedance-2.5/text-to-video",
"input": { "...": "..." },
"callback": { "url": "https://your-server.example.com/hiapi/callback", "when": "final" }
}
callback sits next to input, not inside it. when currently only accepts "final" — one POST when the task reaches a terminal state (success or failed), not incremental progress. See the async task API overview for the full callback contract, including signature verification and retry behavior.
POST /v1/tasks doesn't take a client-supplied idempotency key — every call creates a new task and, for a paid model like this one, a new charge. If a request times out on your end, check whether you already captured a taskId from that attempt before retrying, rather than resubmitting blindly.
An invalid or under-permissioned key fails synchronously, before any task is created:
HTTP 401
{"error":{"code":"permission_denied","message":"This API key cannot use the selected model. Please check permissions or use another key. If the issue persists, contact support with request ID: <id>","request_id":"<id>","type":"hiapi_error"}}
permission_denied means the key exists but isn't authorized for seedance-2.5/text-to-video specifically — check model access in the API Keys dashboard before assuming the request body is wrong. Note this error shape (error.code/error.message) is different from a schema validation failure, which returns a flat {"code":400,"error_code":"INVALID_REQUEST","message":"..."} instead.
Do I need a starting image or reference video?
No. seedance-2.5/text-to-video only requires a prompt string. Starting from an image is a different model, seedance-2.5/image-to-video.
What's the maximum clip length?
duration accepts 4–30 seconds.
What aspect ratios are supported?
16:9, 4:3, 1:1, 3:4, 9:16, 21:9, or adaptive.
Why did my request fail with a schema error even though the field name looked right?
The schema is strict and rejects unknown fields outright — common mistakes are sending seed, ratio instead of aspect_ratio, or any *_urls field, which belongs to the image-to-video and reference-to-video variants, not this one.
Can I get incremental progress updates instead of polling?
No — callback.when only supports "final" today, so you get one webhook POST when the task finishes, not progress ticks. Poll GET /v1/tasks/:id if you need interim status.
Why did I get a 401 with a key I know is valid?
permission_denied means the key isn't scoped for this model, not that the key itself is invalid. Check the key's model permissions in the dashboard.
Does resolution affect price? Yes — 720p costs more per second than 480p. Check the pricing page for current rates before choosing a default resolution for a high-volume feature.