The Seedance 2 API is an asynchronous task API: POST creates a job and returns an ID; GET retrieves that job; a callback can report state changes; the finished video is downloaded only after succeeded. The integration fails when a correct API key, host, or model ID is copied from a different route.
Choose one complete route tuple before writing code. These contracts were checked on July 18, 2026:
| Route | Base URL and key scope | Model namespace | Create, query, and download contract |
|---|---|---|---|
| BytePlus ModelArk direct | https://ark.ap-southeast.bytepluses.com/api/v3; a ModelArk API key created inside the correct resource project | dreamina-seedance-2-0-260128, dreamina-seedance-2-0-fast-260128, or dreamina-seedance-2-0-mini-260615 | POST /contents/generations/tasks; GET /contents/generations/tasks/{id}; read content.video_url from the succeeded task |
| Volcengine Ark China direct | https://ark.cn-beijing.volces.com/api/v3; a key for the China-region Ark account | doubao-seedance-2-0-260128 or doubao-seedance-2-0-fast-260128; verify the account's current Mini ID instead of guessing it | The same create/query path shape; read the succeeded task's output |
| laozhang.ai gateway | https://api.laozhang.ai/seedance/api/v3; a token assigned to the SeeDance2 group | doubao-seedance-2-0-260128 or doubao-seedance-2-0-fast-260128 | Ark-shaped create/query paths; compatibility download at https://api.laozhang.ai/v1/videos/{id}/content |
Do not put a dreamina-* ID on the China or gateway host. Do not append BytePlus's host path to laozhang.ai. A route is the host, key scope, model namespace, and task contract together.
Choose the route before the API key
Use BytePlus ModelArk direct when the application needs first-party international account ownership, the current Standard/Fast/Mini catalog, or endpoint-level controls. Its Seedance 2 tutorial owns the three current dreamina-* IDs, while ModelArk key management explains resource-project keys and optional model, endpoint, and IP restrictions.
Use Volcengine Ark direct for China-region infrastructure and doubao-* contracts. The public reference can show an older example ID, so check the account's current model list before deployment. The Standard and Fast IDs above were checked against current first-party surfaces; the exact Mini suffix was not inferred.
Use the laozhang.ai Seedance relay when a single developer gateway and its documented relay are operationally simpler. Move to official direct access if the project needs a model variant, region, account control, or feature the relay does not expose. The current relay documentation does not support real-person face workflows; review the real-person input boundary before accepting those assets.
This is a route decision, not a price comparison. Availability still depends on the reader's account, region, activation, and balance.
Create and protect the API key
For BytePlus, create the key inside the ModelArk resource project that owns the intended model or endpoint. A key or activation from another project or region may not work against the AP host. Limit its scope where possible, keep it in a server-side environment variable, and never place it in browser JavaScript, a mobile binary, a public repository, or a callback URL.
bashexport ARK_API_KEY="replace-on-your-server" export SEEDANCE_BASE_URL="https://ark.ap-southeast.bytepluses.com/api/v3" export SEEDANCE_MODEL="dreamina-seedance-2-0-260128"
Treat those values as one configuration object. Reject a mismatch before it can create a paid task:
tsfunction assertSeedanceRoute(baseUrl: string, model: string, key: string) { if (!key) throw new Error("Missing server-side Seedance API key"); const bytePlus = baseUrl.includes("bytepluses.com"); const china = baseUrl.includes("volces.com"); const relay = baseUrl.includes("api.laozhang.ai/seedance/api/v3"); if (bytePlus && !model.startsWith("dreamina-seedance-")) { throw new Error("BytePlus requires a dreamina-* model ID"); } if ((china || relay) && !model.startsWith("doubao-seedance-")) { throw new Error("This route requires a doubao-* model ID"); } if (!bytePlus && !china && !relay) throw new Error("Unknown Seedance route"); }
The guard prevents a known configuration error. It cannot prove account activation or model access.
Submit the first Seedance 2 API call
This request is specifically for BytePlus Standard. It creates one async task and asks for a callback:
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 tabletop product shot, slow camera move, natural studio sound" }], "ratio": "16:9", "resolution": "720p", "duration": 5, "generate_audio": true, "callback_url": "https://api.example.com/webhooks/seedance", "priority": 0 }'
The response is a task ID, not a video. Create a local job and normalized request hash before or around submission, then persist the provider ID immediately. Bind that record to the route, model, user, prompt/media fingerprint, and output settings.
BytePlus currently documents priority as a Seedance 2-only integer from 0 through 9. A larger value moves queued work ahead of lower-priority work inside the same endpoint. It does not interrupt running tasks, cross endpoints, apply to offline flex inference, or guarantee faster generation.
The current BytePlus choices are:
dreamina-seedance-2-0-260128: quality-first;dreamina-seedance-2-0-fast-260128: speed/cost balance;dreamina-seedance-2-0-mini-260615: cost-performance.
These labels do not make the IDs portable to other routes. The BytePlus account also needs an active Seedance 2 resource package with available balance.
Query the task; do not create a replacement
Retrieve the task with the same base URL, key, and provider ID:
bashcurl \ "https://ark.ap-southeast.bytepluses.com/api/v3/contents/generations/tasks/$TASK_ID" \ -H "Authorization: Bearer $ARK_API_KEY"
| State | Application meaning | Safe next action |
|---|---|---|
queued | Accepted and waiting | Wait for callback or poll later; never create a replacement |
running | Generation started | Keep the same provider ID |
succeeded | Output available | Mark success and copy the output into durable storage |
failed | Provider ended the task with an error | Record and classify the error before a new user-authorized task |
expired | Execution window ended | Mark terminal and require an explicit new-generation decision |
Treat content.video_url as a delivery URL, not the permanent product URL. Copy it promptly to controlled storage and make that copy retryable without re-running generation.
Make callbacks idempotent and polling reparative
callback_url is optional. ModelArk sends a payload shaped like the retrieve-task response when status changes. For failed delivery of succeeded or failed, the current documentation says it retries three times when no successful confirmation arrives within five seconds.
Repeated terminal delivery means callback processing must be idempotent. A deploy or handler failure also means polling must remain available. Callback and polling are two paths into one durable job state machine.
The public reference checked for this article does not establish a webhook-signature scheme that is safe to claim universally. Do not invent one. Require HTTPS, cap payload size, accept only the expected content type, find the task ID in the local database, verify its route and job ownership, reject illegal state regressions, and return only after committing the transition.
tsconst terminal = new Set(["succeeded", "failed", "expired"]); async function applyProviderState(event: SeedanceTask) { const job = await jobs.findByProviderTaskId(event.id); if (!job) throw new Error("Unknown provider task"); await jobs.transaction(async (tx) => { const current = await tx.forUpdate(job.id); if (current.providerStatus === event.status) return; if (terminal.has(current.providerStatus)) return; await tx.update(job.id, { providerStatus: event.status, providerPayload: event }); if (event.status === "succeeded") await tx.enqueueOutputCopy(job.id); }); }
A repair poller queries non-terminal jobs whose callback deadline passed and applies the same transition function.
Enforce the duplicate-create stop rule
The dangerous case is a create timeout after the provider accepted the request but before the client received the task ID.
Stop rule: if a local job may have reached the provider, do not issue a second POST because the HTTP client timed out. Reconcile the request hash, provider ID, request log, and any callback. If an ID exists, query it. If acceptance remains unknown, surface “submission state unknown” for review instead of silently purchasing another generation.
| Operation | Retry policy |
|---|---|
| Create before any request left the process | Safe to try once |
| Create timed out with acceptance unknown | Stop and reconcile the existing job |
| Query a stored task ID | Retry transport failures with bounded backoff |
| Process callback | Replay through idempotent transitions |
| Copy succeeded output | Retry without creating a new video |
| Provider task failed | Classify validation, policy, account, or transient failure first |
Read route probes correctly
On July 18, 2026, the documented create paths were probed with POST {} and no credential. No task was created.
| Probe | Response | Correct interpretation |
|---|---|---|
| BytePlus AP create | HTTP 401 JSON | Reached the auth boundary; does not prove activation or generation |
| Volcengine China create | HTTP 401 JSON | China path is live and protected; account/model remain untested |
Correct laozhang.ai /seedance/api/v3/... | HTTP 401 JSON | Relay expects auth; a default-group token is not proven |
laozhang.ai path missing /seedance | HTTP 404 JSON | Wrong route |
Obsolete laozhang.ai /seedance/v3/... | HTTP 200 HTML | HTML is not an API success |
Check status and content type. None of these was an authenticated generation test, and this article does not claim a video was generated.
After authentication, diagnose the contract layer before changing the prompt: model not found means recheck the route's model namespace and the account's current model list; an unsupported-field error means compare the payload with that route's current request schema instead of assuming fields are portable across providers; any HTML response still points back to the URL layer.
Reference media and production boundaries
ByteDance's Seedance 2 page identifies text, image, audio, and video inputs. The selected API route's current reference—not the model page—owns exact fields and limits. In the Ark contract, media goes in content. Keep first/last-frame generation separate from multimodal references, and do not submit audio alone; audio requires at least one image or video.
For real-person assets, consent and route policy belong before the call. After the route works, use the Seedance 2 prompt guide for creative control rather than debugging configuration with elaborate prompts. If the remaining question is provider selection, use the separate Seedance 2 API provider comparison.
Before launch, version the route tuple, keep the key server-side, persist the task ID, make callbacks idempotent, retain polling repair, stop duplicate creates, reject HTML health-check responses, and recheck current endpoints and model IDs.
