Integration examples

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.

Webhook integration

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.

Integration workflow

bash
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.