Integration examples
Create one task, wait for its result and download the file.
Create one task, wait for its result and download the file.
Use Connect and save a JSON body from Examples as request.json. Keep BASE_URL as an HTTPS origin and the key on your server. One state file belongs to one order. Rerunning a program with the saved ID resumes GET without another POST.
Add callback_url to request.json as shown in the
creation example. The cURL, TypeScript, Python and
Go submission examples use that JSON with the same method and path.
Create the task once and save its ID. Your HTTPS handler accepts a POST with
the task snapshot, persists it and returns
200 or 204 promptly. Process it after acknowledging: read the task with
an authenticated GET, check its status and download the result. Do not download
the video before acknowledging the webhook.
Keep a periodic GET every 60–120 seconds to recover missed notifications.
A repeated callback does not create another order and must not repeat your
application's actions. When callback_url is omitted, use the normal polling
workflow below.
umask 077
touch result.json content.headers && chmod 600 result.json content.headers || exit 1
[[ "$BASE_URL" =~ ^https://[A-Za-z0-9.-]+(:[0-9]{1,5})?$ &&
-n "$API_KEY" && -r request.json ]] || {
printf 'Set HTTPS BASE_URL, API_KEY and request.json\n' >&2
exit 1
}
OUTPUT_FILE=${VIDEO_OUTPUT:-seedance.mp4}
if [[ -f task-id.txt ]]; then
TASK_ID=$(< task-id.txt)
else
(
set -C
printf 'creating\n' > task-id.txt
) || {
printf 'State file already exists; inspect it before another POST\n' >&2
exit 1
}
if curl --fail --silent --show-error --max-time 120 \
"$BASE_URL/v1/videos" \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @request.json \
-o created.json; then
TASK_ID=$(jq -er '
.id | select(type == "string" and length > 0)
' created.json) || exit 1
printf '%s\n' "$TASK_ID" > task-id.txt
else
printf 'Outcome unknown; check console before another POST\n' >&2
exit 1
fi
fi
[[ "$TASK_ID" != creating && "$TASK_ID" =~ ^[A-Za-z0-9_-]+$ ]] || {
printf 'Invalid task ID\n' >&2
exit 1
}
deadline=$((SECONDS + 2700))
completed=0
while ((SECONDS < deadline)); do
if ! http_status=$(curl --silent --show-error --max-time 60 \
--write-out '%{http_code}' \
"$BASE_URL/v1/videos/$TASK_ID" \
-H "Authorization: Bearer $API_KEY" \
-o result.json); then
sleep 30
continue
fi
case "$http_status" in
200) ;;
429 | 5??)
sleep 30
continue
;;
*)
printf 'Status HTTP %s; ID preserved\n' "$http_status" >&2
exit 1
;;
esac
jq '{id, status, error}' result.json
status=$(jq -r '.status' result.json)
case "$status" in
completed)
completed=1
break
;;
failed)
printf 'Terminal status: %s\n' "$status" >&2
exit 1
;;
queued | in_progress) sleep 15 ;;
*)
printf 'Unknown status: %s\n' "$status" >&2
exit 1
;;
esac
done
if ((!completed)); then
printf 'Polling limit reached; resume with %s\n' "$TASK_ID" >&2
exit 1
fi
VIDEO_URL=$(jq -r '.content.video_url // empty' result.json)
if [[ -z "$VIDEO_URL" ]]; then
metadata=$(curl --silent --show-error --max-time 300 \
--dump-header content.headers --write-out '%{http_code} %{content_type}' \
"$BASE_URL/v1/videos/$TASK_ID/content" \
-H "Authorization: Bearer $API_KEY" -o seedance.download) || exit 1
http_status=${metadata%% *}
if [[ "$http_status" == 307 ]]; then
VIDEO_URL=$(awk 'tolower($1)=="location:" {print $2}' content.headers | tr -d '\r' | tail -1)
elif [[ "$http_status" == 200 ]]; then
mime=${metadata#* }
case "$mime" in
video/mp4* | video/quicktime* | application/octet-stream*)
mv seedance.download "$OUTPUT_FILE"
exit 0
;;
esac
fi
[[ "$http_status" == 307 ]] || {
printf 'Content HTTP %s; ID preserved\n' "$http_status" >&2
exit 1
}
fi
[[ "$VIDEO_URL" =~ ^https://[^/@?#]+ ]] \
&& [[ ! "$VIDEO_URL" =~ ^https://[^/?#]*@ ]] || {
printf 'Invalid signed HTTPS media URL\n' >&2
exit 1
}
# The signed URL grants temporary access. Never send the API Bearer to it.
metadata=$(curl --silent --show-error --max-time 300 \
--write-out '%{http_code} %{content_type}' \
"$VIDEO_URL" -o seedance.download) || exit 1
http_status=${metadata%% *}
mime=${metadata#* }
case "$http_status:$mime" in
2??:video/mp4* | 2??:video/quicktime* | 2??:application/octet-stream*)
mv seedance.download "$OUTPUT_FILE"
;;
*)
printf 'Unexpected HTTP response or file type\n' >&2
exit 1
;;
esac
Run the cURL steps in sequence, repeating only status GET every 10-15 seconds until a terminal status or a local 45-minute deadline. On a POST timeout before an ID arrives, check the console or support before another POST. For a new order use a new state filename. A local deadline does not cancel the remote task. For MOV output set VIDEO_OUTPUT=seedance.mov (all examples use this filename). Protect local state and result files because result JSON may contain a signed media URL.