Seedance 2.0 API는 첫 응답에서 완성된 영상을 주는 동기 API가 아닙니다. 작업을 생성하고, provider task ID를 저장하고, polling 또는 callback으로 상태를 추적한 다음, succeeded 상태가 되면 content.video_url을 자체 저장소로 복사하는 구조입니다.
BytePlus ModelArk 국제 경로의 생성 endpoint는 https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks입니다. Seedance 2.0 모델 ID는 dreamina-seedance-2-0-260128와 dreamina-seedance-2-0-fast-260128입니다. 중국 지역 Volcengine Ark를 쓰는 경우 endpoint와 doubao-* model ID가 다르므로 한 설정 안에서 섞지 않아야 합니다.

작업은 한 번만 제출한다
생성을 시작하는 지점은 create call 하나로 제한합니다. 제출 전에 route, model, prompt, reference media, ratio, resolution, duration, audio, callback URL을 검증하세요. 응답을 받으면 provider task ID를 자체 job ID, request hash, user ID, 출력 설정과 함께 저장합니다.
bashcurl -X POST "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks" \ -H "Authorization: Bearer $ARK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "dreamina-seedance-2-0-260128", "content": [ { "type": "text", "text": "A quiet product demo shot, slow camera move, clean studio light" } ], "ratio": "16:9", "resolution": "720p", "duration": 5, "generate_audio": false, "return_last_frame": true, "callback_url": "https://example.com/webhooks/seedance" }'
create 요청이 timeout 됐다고 바로 다시 제출하면 중복 생성이 생길 수 있습니다. 먼저 request hash로 로컬 job을 조회하고, provider task ID가 이미 있으면 그 ID를 계속 polling해야 합니다.
polling 또는 callback으로 상태를 처리한다
Seedance 상태는 진행률이 아니라 작업 생명주기입니다.
| 상태 | 의미 | 처리 |
|---|---|---|
queued | 대기열에 등록됨 | backoff 후 대기 |
running | 생성 중 | 계속 polling |
succeeded | 결과 준비됨 | content.video_url 복사 |
failed | provider 실패 | code와 message 저장 |
expired | 실행 시간 만료 | 재제출 가능 여부 판단 |
cancelled | 취소됨 | polling 중단 |
bashcurl -X GET \ "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks/$TASK_ID" \ -H "Authorization: Bearer $ARK_API_KEY"
callback은 빠른 갱신에 좋지만, 복구용 polling은 계속 필요합니다. 배포 중단, callback 검증 오류, 네트워크 문제로 webhook이 누락될 수 있기 때문입니다.
결과를 다운로드해 자체 저장소에 보관한다
성공 응답의 content.video_url은 임시 MP4 URL입니다. 공식 응답 계약에 따르면 생성된 비디오는 24시간 후 정리되므로, 사용자에게 보여줄 영구 링크로 쓰지 말고 즉시 object storage에 복사해야 합니다.
tsasync function saveSeedanceVideo(job, task) { const url = task.content?.video_url; if (!url) throw new Error("Seedance succeeded without video_url"); const res = await fetch(url); if (!res.ok) throw new Error(`download failed: ${res.status}`); await storage.putObject({ key: `seedance/${job.id}/output.mp4`, body: res.body, contentType: "video/mp4", }); }
provider payload도 함께 저장하세요. model, status, seed, ratio, resolution, duration, generate_audio, usage.total_tokens는 고객 지원, 비용 정산, 재현 조사에 필요합니다.

참조 자료는 content에 넣는다
Seedance 2.0의 reference media는 단순 첨부 파일이 아닙니다. content 배열이 생성 모드를 결정합니다. 첫 프레임은 first_frame, 첫/마지막 프레임은 first_frame과 last_frame, 멀티모달 참조는 reference_image, reference_video, reference_audio를 사용합니다.
json{ "model": "dreamina-seedance-2-0-260128", "content": [ { "type": "text", "text": "Use [image 1] as the product reference and keep the motion minimal." }, { "type": "image_url", "image_url": { "url": "https://cdn.example.com/reference-product.webp" }, "role": "reference_image" } ], "ratio": "16:9", "duration": 5, "generate_audio": false }
제출 전 파일 형식, 크기, 길이, 권한을 검증하세요. 오디오는 단독으로 제출하지 말고 이미지 또는 비디오 reference와 함께 사용해야 합니다. 실제 인물이 포함된 워크플로라면 동의와 권리 확인을 API 호출 전에 처리해야 합니다.
재시도는 idempotent하게 만든다
가장 비싼 버그는 같은 job에 대해 유료 생성을 두 번 만드는 것입니다. route, model ID, 정규화된 prompt, reference URL 또는 asset ID, 출력 파라미터, user ID로 request hash를 만들고, 같은 hash의 job이 있으면 새 create call 대신 기존 job을 반환합니다.

| 작업 | 재시도 규칙 |
|---|---|
| task ID 저장 전 create | job과 hash 확인 후 재시도 |
| task ID 저장 후 create | 다시 제출하지 않고 polling |
| polling | backoff로 재시도 |
| callback | 상태 업데이트를 idempotent하게 처리 |
| download | 임시 URL 만료 전까지 재시도 |
| failed task | 오류 분류 후 판단 |
FAQ
Seedance 2.0 API는 동기 API인가요?
아닙니다. create는 task ID를 반환하고, MP4는 나중에 content.video_url로 제공됩니다.
polling과 callback 중 무엇을 써야 하나요?
둘 다 쓰는 것이 안전합니다. callback은 빠른 갱신, polling은 누락 복구에 필요합니다.
video_url을 장기 링크로 써도 되나요?
안 됩니다. 임시 URL이며 생성 비디오는 24시간 후 정리되므로 자체 저장소에 복사해야 합니다.
create 요청이 timeout 되면요?
request hash로 기존 job을 먼저 찾으세요. provider task ID가 있으면 polling을 계속하고, ID가 없을 때만 같은 job으로 create를 재시도합니다.
