Seedance 2 API를 처음 호출할 때 가장 먼저 정할 것은 prompt가 아니라 경로(route) 입니다. BytePlus 국제 경로, 중국 Volcengine 경로, LaoZhang relay는 모두 비동기 작업을 만들지만 호스트, API 키가 유효한 범위, 모델 ID namespace, endpoint가 다릅니다. 네 요소를 한 줄로 고정하지 않으면 정상 키로도 401이나 404가 나거나, 브라우저용 HTML을 API 응답으로 오인할 수 있습니다.
2026년 7월 18일 기준 BytePlus 직접 호출의 최소 tuple은 다음과 같습니다.
texthost = https://ark.ap-southeast.bytepluses.com key scope = ModelArk resource project + 허용 model/custom endpoint (+ 선택 IP allowlist) model = dreamina-seedance-2-0-260128 create = POST /api/v3/contents/generations/tasks retrieve = GET /api/v3/contents/generations/tasks/{task_id} delivery = callback_url + repair polling
API 키는 BytePlus ModelArk 콘솔의 resource project에서 만들고 서버 환경 변수에 보관합니다. 브라우저 코드, 모바일 앱, 공개 저장소에 넣지 마세요. 아래 호출은 현재 BytePlus API reference의 국제 AP 경로를 사용합니다.
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 calm product shot, one slow camera move, clean studio light" }], "ratio": "16:9", "resolution": "720p", "duration": 5, "generate_audio": false, "callback_url": "https://example.com/webhooks/seedance" }'
성공적인 create 응답은 완성된 MP4가 아니라 작업 id입니다. 이 글 작성 과정에서는 자격 증명 없이 경로만 점검했고 위 endpoint가 HTTP 401 JSON으로 인증 경계에 도달하는 것만 확인했습니다. 실제 키로 작업을 생성하거나 모델 접근 권한, 잔액, 생성 성공을 검증하지는 않았습니다.
내 호출에 맞는 route tuple부터 고정한다
같은 “Seedance 2” 이름을 보더라도 한 행의 값만 사용하세요. 모델 ID 하나만 바꾸는 방식으로 provider를 전환하면 안 됩니다.
| 경로 | Host와 키 범위 | 모델 ID namespace | 비동기 endpoint |
|---|---|---|---|
| BytePlus 국제 직접 | ark.ap-southeast.bytepluses.com; ModelArk resource project 키, model/custom endpoint와 IP 제한 가능 | dreamina-seedance-*: Standard dreamina-seedance-2-0-260128, Fast dreamina-seedance-2-0-fast-260128, Mini dreamina-seedance-2-0-mini-260615 | POST/GET /api/v3/contents/generations/tasks[/{id}] |
| Volcengine 중국 직접 | ark.cn-beijing.volces.com; 중국 리전 Ark 계정, 키와 모델 활성화가 같은 리전에 있어야 함 | doubao-seedance-*: Standard doubao-seedance-2-0-260128, Fast doubao-seedance-2-0-fast-260128; Mini suffix는 계정의 현재 모델 목록에서 확인 | POST/GET /api/v3/contents/generations/tasks[/{id}] |
| LaoZhang relay | base https://api.laozhang.ai/seedance/api/v3; token이 SeeDance2 group에 배정되어야 함 | plain doubao-seedance-2-0-260128 또는 doubao-seedance-2-0-fast-260128 | POST/GET /contents/generations/tasks[/{id}]; compatibility download는 https://api.laozhang.ai/v1/videos/{id}/content |
BytePlus의 현재 Seedance 2.0 tutorial은 Standard, Fast, Mini 세 경로를 구분합니다. Standard는 품질 우선, Fast는 속도와 비용의 균형, Mini는 cost-performance 선택지입니다. 이름만 보고 계정에서 사용할 수 있다고 가정하지 말고, 실제 model list와 활성화 상태를 확인한 뒤 배포하세요.
401, 404, HTML이면 prompt를 고치지 않는다
인증 전에 할 수 있는 안전한 probe는 “요청이 어느 종류의 경계에 도착했는가”를 보는 것입니다. 2026년 7월 18일 자격 증명 없이 빈 JSON {}를 POST했을 때 확인한 범위는 다음과 같습니다.
| 결과 | 이번 확인에서 뜻하는 것 | 다음 행동 |
|---|---|---|
BytePlus AP 401 JSON | 올바른 API 경로가 인증 경계까지 도달 | 키의 project/region, 모델 활성화, 잔액을 별도로 확인 |
Volcengine CN 401 JSON | 중국 경로의 인증 경계까지 도달 | 중국 리전 키와 doubao-* 모델을 확인 |
LaoZhang /seedance/api/v3/... 401 JSON | relay 경로가 맞고 token이 필요 | token의 SeeDance2 group을 확인 |
LaoZhang /api/v3/... 404 JSON | Seedance relay prefix가 빠짐 | /seedance/api/v3 base로 수정 |
구식 /seedance/v3/... 200 HTML | API JSON이 아니라 웹 문서/페이지에 도달 | status만 보지 말고 Content-Type과 JSON body를 검사 |
401은 모델 사용 가능이나 생성 성공의 증거가 아닙니다. 반대로 200도 HTML이면 API 성공이 아닙니다. 최초 probe에서 status, Content-Type, 응답 body의 JSON 여부를 함께 기록하세요.
모델 ID와 key scope를 같은 설정 단위로 묶는다
환경 변수는 API_KEY 하나보다 route tuple 전체를 표현해야 운영 중 혼합을 막기 쉽습니다.
tsconst seedanceRoute = { provider: "byteplus-ap", baseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3", apiKey: process.env.BYTEPLUS_ARK_API_KEY, model: "dreamina-seedance-2-0-260128", createPath: "/contents/generations/tasks", };
키 생성 화면에서 model/custom endpoint 제한이나 IP allowlist를 썼다면 해당 제한도 배포 설정에 문서화하세요. 다른 region에서 만든 키나 다른 group의 gateway token을 host만 바꿔 재사용할 수 있다고 가정하면 안 됩니다.
BytePlus endpoint 기반 online inference에서는 Seedance 2.0 create 요청에 priority를 0부터 9까지 넣을 수 있습니다. 큰 값은 같은 endpoint에서 아직 queued인 낮은 우선순위 작업보다 앞서게 할 뿐입니다. 실행 중인 작업을 중단하지 않고, 다른 endpoint의 queue를 넘지 않으며, 생성 자체가 더 빨라진다는 보장도 아닙니다. offline flex inference에 적용되는 필드로 설명해서도 안 됩니다.
작업 ID를 먼저 저장하고 callback을 처리한다
create 직전 로컬 job과 request hash를 만들고, create 응답의 provider task ID를 즉시 저장합니다. hash에는 provider, model ID, 정규화한 prompt, reference asset ID, 출력 설정, 사용자/job ID를 포함할 수 있습니다.
tstype LocalJob = { id: string; route: "byteplus-ap" | "volcengine-cn" | "laozhang-relay"; requestHash: string; providerTaskId?: string; status: "submitting" | "queued" | "running" | "succeeded" | "failed" | "expired"; outputCopiedAt?: string; };
BytePlus callback 계약은 callback_url을 선택 사항으로 두고, 상태가 바뀔 때 retrieve-task 응답 형태의 payload를 POST한다고 설명합니다. 문서화된 상태는 queued, running, succeeded, failed, expired입니다.
사실 경계가 중요합니다. succeeded 또는 failed 전달이 실패하고 5초 안에 성공 확인을 받지 못한 경우, 문서는 세 번 재시도한다고 설명합니다. 이는 모든 상태가 항상 세 번 전달된다는 뜻도, callback이 정확히 한 번만 온다는 뜻도 아닙니다. 공개 문서에서 이 글에 넣을 수 있는 webhook signature 계약은 확인되지 않았으므로 서명을 만들어내지 마세요.
대신 handler는 HTTPS, 허용 method/content type, body 크기, 알려진 task ID와 route 소유권을 확인하고 같은 이벤트를 여러 번 받아도 결과가 같도록 만듭니다. provider가 나중에 공식 signature 방법을 문서화하면 그 방법을 추가합니다.
tsasync function applySeedanceUpdate(event: ProviderTask) { const job = await jobs.findByProviderTaskId(event.id); if (!job) return { accepted: false, reason: "unknown_task" }; // terminal 상태를 queued/running으로 되돌리는 regression은 무시한다. if (isIllegalRegression(job.status, event.status)) { return { accepted: true, changed: false }; } await jobs.compareAndSet(job.id, normalize(event)); if (event.status === "succeeded") await enqueueOutputCopy(job.id); return { accepted: true, changed: true }; }
callback handler는 빨리 응답하고 무거운 다운로드는 queue로 넘기세요. 별도 repair worker는 살아 있는 job을 backoff로 조회해 배포 중 누락, 지연, 순서가 뒤바뀐 callback을 수습합니다.
create timeout은 두 번째 생성 허가가 아니다
| 실패 지점 | 안전한 판단 | 금지할 행동 |
|---|---|---|
| create 연결 timeout, task ID 미저장 | request hash로 로컬 job 확인; provider가 조회 수단을 제공하면 기존 task를 먼저 탐색 | timeout 직후 새 create 호출 |
| task ID 저장 후 callback 없음 | 같은 ID를 polling하고 callback endpoint 로그 확인 | prompt를 그대로 다시 제출 |
401/403 | host, key scope/group, 활성화와 권한 수정 | key를 client에 노출하거나 다른 host에 무작정 전송 |
404 또는 HTML | base/path와 Content-Type 수정 | 모델 ID나 prompt만 바꾸기 |
429 | 해당 provider의 현재 retry 지침과 backoff 적용 | 고정 간격 병렬 재시도 |
terminal failed/expired | 오류를 분류하고 사용자 의도에 따라 새 job 여부 결정 | 기존 job을 성공으로 덮어쓰기 |
provider task가 없음을 확인할 방법이 없다면 자동 재제출보다 사람이 판단할 수 있는 unknown_submit_outcome 상태가 안전합니다. 중복 비용과 중복 콘텐츠가 조용히 생기는 것보다 낫습니다.
결과는 “완료”가 아니라 “보관”까지 추적한다
succeeded payload에서 output URL을 얻어도 job은 아직 완전히 끝난 것이 아닙니다. 다운로드 응답 status와 media type을 확인하고 자체 object storage에 복사한 뒤, checksum 또는 object metadata와 outputCopiedAt을 기록하세요. 이번 검증에서는 output URL의 고정 보존 시간을 다시 확인하지 못했으므로 특정 시간 뒤 삭제된다고 단정하지 않습니다. provider URL을 장기 링크로 취급하지 않는 것이 안전합니다.
reference를 보낼 때는 BytePlus의 content role과 gateway의 schema를 섞지 마세요. BytePlus 직접 경로는 first_frame, last_frame, reference_image, reference_video, reference_audio 같은 역할을 정의합니다. 실제 인물 입력 경계는 별도 Seedance 2 실제 인물 API 안내에서 확인하고, prompt/reference 설계는 Seedance 2 prompt guide를 사용하세요.
세 경로 중 언제 바꿀까
- BytePlus 직접: 국제 계정 소유권, Standard/Fast/Mini catalog, endpoint 수준 제어가 필요할 때 적합합니다.
- Volcengine 직접: 중국 리전 인프라와
doubao-*계약이 운영 요건일 때 선택합니다. - LaoZhang relay: 하나의 developer gateway와 현재 문서화된 relay path가 onboarding/운영을 단순화할 때 유용합니다. 현재 Seedance relay 문서에서 base,
SeeDance2group, 모델과 제한을 다시 확인하세요.
LaoZhang 경로는 현재 문서상 실제 인물 face workflow를 지원하지 않습니다. 계정/region 통제, Mini, 특정 input 기능, endpoint control 또는 relay에 없는 기능이 필요하면 추천을 중단하고 BytePlus/Volcengine 공식 직접 경로를 사용하세요. 이 선택은 가격이나 uptime 우위를 보장하지 않습니다. provider를 더 비교해야 한다면 Seedance 2 API provider 비교로 이동하되, 구현 코드는 다시 선택한 한 route의 공식 문서에서 시작해야 합니다.
배포 전 마지막 확인은 간단합니다: host와 key scope/group이 맞는가, model ID가 현재 account list에 있는가, create가 task ID를 남기는가, callback이 중복되어도 안전한가, polling이 누락을 복구하는가, 결과가 자체 storage에 복사되는가. 하나라도 답할 수 없으면 paid create를 멈추세요.
