Video generation API curl: debug the async lifecycle step by step
Before debugging a video client, reproduce its submission with a small curl request. That separates JSON and authentication problems from queue handling in your application. This guide uses Bash, curl, and jq. It creates a real generation when executed, so choose the prompt and parameters deliberately and keep the accepted response for subsequent observation.
1. Use a known parameter combination
Create video-request.json. Seedance Fast accepts integer durations from three through fifteen seconds, the listed aspect ratio is supported, and this example chooses 720p explicitly. Do not infer that these options apply to every video model; the gateway validates parameters through the selected adapter.
{
"model": "seedance-2.0-fast",
"prompt": "A slow camera move past a wooden sailboat in a quiet harbor",
"duration_sec": 5,
"aspect_ratio": "16:9",
"resolution": "720p"
}
curl --silent --show-error --fail-with-body \
--max-time 45 \
--output video-accepted.json \
https://genace.ai/api/v1/video/generations \
--header "Authorization: Bearer $GENACE_API_KEY" \
--header 'Content-Type: application/json' \
--data-binary @video-request.json
jq -e '.job_id and .model and .state' video-accepted.json
If the request fails, inspect error.code and error.message in that file. An invalid duration needs a request correction; repeating the same body does not fix it. An authentication error needs a credential check. Keep this submission step separate from the loop so restarting observation cannot accidentally launch another video.
2. Use a bounded observation loop
JOB_ID=$(jq -er '.job_id' video-accepted.json)
for ATTEMPT in $(seq 1 20); do
curl --silent --show-error --fail-with-body \
--max-time 45 \
--output video-status.json \
"https://genace.ai/api/v1/jobs/$JOB_ID?wait=30" \
--header "Authorization: Bearer $GENACE_API_KEY" || break
STATE=$(jq -er '.state' video-status.json) || break
printf '%s\n' "$STATE"
case "$STATE" in
succeeded|failed|canceled) break ;;
esac
done
jq '{id, state, error, outputs}' video-status.json
The loop performs at most twenty status calls. Its total duration also includes network time, so use a separate wall-clock deadline in a production worker. The server waits for up to the requested window and returns the latest state. A queued or running response means observation can continue with the same identifier.
3. Interpret the result before downloading
jq -er 'select(.state == "succeeded") | .outputs[]? |
select(.type == "video") | .url' video-status.json
This filter emits video URLs only after success. A null output list is normal before the job completes. Failed and canceled are terminal too, but they do not produce a usable result. Log the error object with the job ID when requesting support; remove your key and any confidential prompt details first.
Common mistakes
A curl timeout ends the local HTTP request. It does not cancel the remote generation. Resume GET observation using the original job ID. The submission route has no request idempotency contract, so retrying the POST because the terminal appears quiet can create another billable render.
Do not use polling frequency as a way to improve generation speed. The job endpoint has its own request limit, and repeated instant checks add traffic without changing your prompt or rendering parameters. A waiting status request is usually easier to reason about.
Verification checklist
- Save the accepted response before entering any polling loop.
- Confirm the status response identifies the job using
id. - Stop on all three terminal states and inspect
erroron failure. - Check output type before passing the URL to a video player.
For reusable code, follow the Node.js video client. For model-specific validation, use Seedance duration guidance and resolution options.
Continue with code and integration
Models covered on this page
Where these facts come from
- Editorial keyword hypothesis; search volume not measured
- codebase: src/app/api/v1/video/generations/route.ts
- codebase: src/ai/providers/seedance.ts
- codebase: src/app/api/v1/jobs/[id]/route.ts