Seedance errors come in two layers. One is an HTTP request failure, such as 401, 403, 400, or 429. The other is a request that succeeds but whose task eventually ends in failed or expired. Assuming a video task succeeded because the submission endpoint returned 200 is the most common misjudgment when integrating a video API. The correct troubleshooting order is: check the HTTP status code first, then the task status and error field, and only then consider changing the prompt or the model.
This article breaks the problem down by error type, giving causes, handling order, and reusable error-handling code. Endpoint paths, status names, and quota rules may change between versions, so rely on the on-site and official documentation in practice.
Note: Seedance, Doubao, and Volcano Engine are trademarks of ByteDance and its affiliates. This article is for technical explanation only and implies no affiliation or endorsement. The paths and fields in the examples are illustrative; verify them against the documentation before calling.
1. Separate request failures from task failures
| Symptom | Layer | Common cause | Handling |
|---|---|---|---|
| 401 | HTTP | Missing, malformed, or expired key | Fix authentication |
| 403 | HTTP | Insufficient permission, model not enabled, regional restriction | Check permissions and enablement status |
| 400 | HTTP | Parameter out of range, wrong model name, inaccessible asset | Correct parameters against the documentation |
| 429 | HTTP | Concurrency, rate, or quota limit | Back off, reduce concurrency |
| 5xx | HTTP | Upstream overload or temporary fault | Back off, switch channels |
| failed | Task | Moderation rejection, asset problem, generation error | Read the error, fix the asset |
| expired | Task | Timed out or result not retrieved | Recreate the task, check deadlines |
| Download failure | Result | URL expired, authentication, network | Re-fetch or copy to storage |
Logs should record at least the timestamp, HTTP status code, error code, model name, task_id, business order number, and elapsed time. Never log full keys, and do not print signed result URLs verbatim.
2. 401 and 403: check headers and keys first
A 401 means identity verification failed and usually has nothing to do with model capability or balance. Check in order:
- Whether
AuthorizationisBearer <key>; Volcano Ark uses the API key as the bearer token. - Whether the key contains spaces, line breaks, or stray quotes, and whether the environment variable actually loaded.
- Whether the base URL and the key belong to the same platform and the same region.
- Whether the key was deleted, rotated, or disabled, or lacks project permissions.
- Whether a reverse proxy overwrites or drops the
Authorizationheader.
A 403 is more often a permission issue: the model is not enabled, the account lacks access to that model, or a regional or compliance restriction applies. With a 401, a minimal request using a new key quickly isolates the cause; with a 403, check enablement status and account permissions in the console, because code changes usually will not help.
3. 429: rate limits, concurrency, and quotas
A 429 means requests exceeded a rate, concurrency, or quota limit. Common triggers:
- Several services share one key and compete for the same quota.
- Batch jobs all start on the hour, creating a traffic spike.
- Retries have no backoff, so failures beget more requests and create a retry storm.
- Long tasks occupy all concurrency slots, so short tasks cannot get through.
The principles are exponential backoff with random jitter, lower concurrency, separate keys per project, and staggered submission. Do not loop retries without backoff; continuing to submit at high frequency during a 429 turns rate limiting into a sustained outage. If the business genuinely needs high concurrency, confirm the quota ceiling in advance and prepare a backup channel.
4. 400: parameter errors account for most cases
A 400 can usually be located directly from the error message. Check:
- Model name and version: copy them from the documentation; do not assemble suffixes from memory.
- Duration: ranges differ by model. For example, 2.5 is 4–30 seconds, 2.0 is 4–15 seconds, and 1.0 is 2–12 seconds.
- Resolution and aspect ratio: confirm whether the model supports 4K, 21:9, or adaptive ratios.
- Reference asset counts: 2.5 allows up to 30 images, 10 video clips, and 10 audio clips; 2.0 has lower limits.
- Image or video URLs: they must be reachable by the platform, not internal addresses or links that require a login.
- Edit or continuation tasks: some modes only allow automatic duration and reject a manually entered number of seconds.
- Audio input: some versions do not support audio-only input.
The fastest approach is to reproduce the problem with the documentation's example request body, then add your parameters back one at a time. Test after each addition to pinpoint the exact field.
5. Tasks that end in failed or expired
A successful submission only means the task entered the queue. Common statuses in query results include queued, running, succeeded, failed, and expired. For failed, read the error field: a content moderation rejection, non-compliant input assets, an unreadable reference image, and a generation error all require different handling.
expired usually means the task did not finish or its result was not collected before the execution deadline. According to the documentation, execution_expires_after is configurable and defaults to 48 hours; task IDs are retained for 7 days. Polling after the deadline will not revive the task; it must be recreated. When using callbacks, note that a callback not acknowledged within 5 seconds is retried 3 times, so the callback endpoint must be idempotent.
Do not try to get around a moderation rejection by swapping a few words repeatedly. First confirm asset authorization, likeness rights, brand elements, and content policy, then decide whether to resubmit.
6. Timeouts and polling
Timeouts need to be separated into three layers: client connection timeout, per-query timeout, and the overall task deadline. The submission endpoint usually returns a task_id quickly; the real time is spent in the queue and in rendering. Set the HTTP client timeout too short and a normal submission is misjudged as a failure; set it too long and it ties up the thread pool.
Start polling at 5-second intervals, back off gradually to 15–30 seconds, and set an overall deadline. When callbacks are available, prefer them and use polling as a fallback. Do not resubmit immediately after a timeout; first check by business order number or idempotency key whether a task is already running.
7. Result download failures
A successful task does not mean the file is in hand. video_url is often a signed temporary address with an expiry. Storing it in a database and downloading hours later commonly produces a 403 or 404. Handling:
- Stream the file to your own object storage immediately after success.
- Retry downloads separately; do not recreate the generation task.
- Validate Content-Type, file size, and playability.
- Do not print full signed URLs in logs.
8. Example: error handling with backoff and idempotency
The code below separates submission, polling, and download, and retries only transient errors. Paths and fields are governed by the on-site documentation.
import os, time, random, requests
BASE = os.environ["SEEDANCE_BASE_URL"].rstrip("/")
HEADERS = {
"Authorization": f"Bearer {os.environ['SEEDANCE_API_KEY']}",
"Content-Type": "application/json",
}
def submit(payload, max_retries=5):
# Before calling, look up the task table by business order number and reuse any existing task_id
for attempt in range(max_retries):
try:
r = requests.post(
f"{BASE}/contents/generations/tasks",
headers=HEADERS, json=payload, timeout=(5, 30),
)
except requests.Timeout:
time.sleep(min(2 ** attempt, 20) + random.random())
continue
if r.status_code == 429 or r.status_code >= 500:
time.sleep(min(2 ** attempt, 20) + random.random())
continue
if r.status_code >= 400:
raise RuntimeError(f"{r.status_code}: {r.text[:500]}")
return r.json()["id"]
raise RuntimeError("submit retries exhausted")
def wait(task_id, deadline_seconds=900):
deadline = time.time() + deadline_seconds
delay = 5
while time.time() < deadline:
r = requests.get(
f"{BASE}/contents/generations/tasks/{task_id}",
headers=HEADERS, timeout=(5, 30),
)
if r.status_code == 429 or r.status_code >= 500:
time.sleep(delay)
delay = min(delay * 1.5, 30)
continue
r.raise_for_status()
data = r.json()
if data["status"] == "succeeded":
return data["content"]["video_url"]
if data["status"] in ("failed", "expired"):
raise RuntimeError(f"{data['status']}: {data.get('error')}")
time.sleep(delay)
delay = min(delay * 1.5, 30)
raise TimeoutError(task_id)
def download(url, path):
with requests.get(url, stream=True, timeout=(5, 120)) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1024 * 256):
f.write(chunk)
The code deliberately does not retry 400, 401, or 403. Parameter and authentication errors will not succeed no matter how many times they are retried; retrying only amplifies logs and costs.
9. Retries, idempotency, and a support checklist
Retries are appropriate only for transient errors: 429, 5xx, and connection timeouts. Do not automatically retry 400, 401, 403, or moderation rejections. The key to idempotency is not the retry count but the business order number: look up the task table before submitting, store the task_id after submitting, and reuse the existing task for duplicate requests.
Before contacting your provider, organize the information according to this checklist:
- Record the time, HTTP status code, error code, and full error message, with sensitive data redacted.
- Record the model name, duration, resolution, aspect ratio, and number of reference assets.
- Record the task_id, business order number, submission count, and polling count.
- Keep one minimal reproduction request with user privacy and keys removed.
- Distinguish request failures from tasks that ended in
failedorexpired. - Check whether asset URLs are publicly reachable and whether responses are slow.
- Confirm quotas, concurrency, and account permissions to rule out 429 and 403.
- Attach the request ID and reproduction time before submitting the case.
FAQ
The submission endpoint returned 200, so why did the task fail?
A 200 only means the task was created. Video generation is asynchronous; the final state is in the status and error fields of the query result.
Does a 429 mean the balance is insufficient?
Not necessarily. A 429 usually means a rate, concurrency, or quota limit, while balance problems may return a different error. Check concurrency, shared keys, and retry policy first.
Can I retry immediately after a task fails?
Read the error first. Moderation rejections and non-compliant assets usually fail again if retried unchanged; network or upstream faults can be retried with backoff. Check idempotency by business order number before retrying.
How often should I poll?
Start at 5-second intervals, back off gradually to 15–30 seconds, and set an overall timeout. Prefer callbacks when available and use polling as a fallback.
What should I do if the result URL will not open?
Most often the validity period has passed or the signature is invalid. Copy results to object storage immediately after success; if the URL has already expired, query the task again or recreate it rather than repeatedly requesting the old link.
Next steps
Create a test key in the ZSQH AI Gateway, rehearse 401, 429, parameter errors, and task failures once each, and confirm that alerts and retry policies work. Model availability and billing methods are governed by model pricing.
Related reading:
