As of August 4, 2026, ByteDance has published the Seedance 2.5 product page, but BytePlus's current public video API and model-pricing catalog still list the Seedance 2.0 series. The 2.5 product is real; a first-party ModelArk 2.5 model ID is not yet verifiable in the public catalog. The safe first step is to pin one provider-owned route instead of guessing an ID from a search result or wrapper.

Separate the Seedance 2.5 release from API availability
The official Seedance 2.5 page describes clips up to 30 seconds, two extensions, stronger reference and editing behavior, white-model control, and green-screen editing. It does not publish an API host, credential flow, model ID, request schema, or account rollout.
The BytePlus create-task reference, updated July 31, 2026, still documents public capabilities as Seedance 2.0 series. Its current model and pricing table lists three 2.0 IDs and no 2.5 entry. A string such as dreamina-seedance-2-5-260628 may appear on third-party pages, but that does not make it a published BytePlus ID.
| Surface | What it establishes | Safe integration decision |
|---|---|---|
| ByteDance Seed product page | Product name and creative capabilities | Use it for capability planning, not API parameters |
| BytePlus or Volcengine model catalog | Activated IDs, key scope, host, and task contract | Enable 2.5 only when the account or first-party catalog actually lists it |
| Third-party 2.5 API | A provider may expose its own alias and endpoint | Keep its host, key, ID, payload, billing, and policy together |
If production must ship now, use a currently cataloged Seedance 2.0 fallback. If the workload specifically requires 2.5, wait for the first-party account catalog or choose a provider that documents the complete route and its limits.
Choose one complete route tuple before writing code. The following are current Seedance 2.0 fallback contracts, not proof of a first-party 2.5 rollout:
| 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 and poll one task in Python
This example targets an Ark-shaped asynchronous contract. Set all three environment variables from the same provider's current documentation or console. SEEDANCE_MODEL can be a cataloged 2.0 fallback or a provider-owned 2.5 ID that you have independently verified.
pythonimport os import time import requests base_url = os.environ["SEEDANCE_BASE_URL"].rstrip("/") api_key = os.environ["SEEDANCE_API_KEY"] model = os.environ["SEEDANCE_MODEL"] headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} payload = { "model": model, "content": [{"type": "text", "text": "A ceramic cup rotates slowly on a clean studio table"}], "ratio": "16:9", "resolution": "720p", "duration": 5, "generate_audio": True, } response = requests.post(f"{base_url}/contents/generations/tasks", headers=headers, json=payload, timeout=30) response.raise_for_status() task_id = response.json()["id"] # Persist before doing anything else. while True: response = requests.get(f"{base_url}/contents/generations/tasks/{task_id}", headers=headers, timeout=30) response.raise_for_status() task = response.json() if task["status"] in {"succeeded", "failed", "expired"}: print(task) break time.sleep(5)
If the create request times out before returning an ID, do not silently submit a second job. Mark the local job as having an unknown submission outcome and reconcile it through callbacks, request logs, or provider support.
Run the same contract with Node.js
Node.js 18 and later include fetch. Keep the API key in a server environment; this code is not for browser-side JavaScript.
jsconst baseUrl = process.env.SEEDANCE_BASE_URL.replace(/\/$/, ""); const apiKey = process.env.SEEDANCE_API_KEY; const model = process.env.SEEDANCE_MODEL; async function request(path, init = {}) { const response = await fetch(`${baseUrl}${path}`, { ...init, headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", ...init.headers }, }); const contentType = response.headers.get("content-type") || ""; if (!contentType.includes("application/json")) throw new Error(`Expected JSON, received ${contentType}`); const data = await response.json(); if (!response.ok) throw new Error(`${response.status}: ${JSON.stringify(data)}`); return data; } const created = await request("/contents/generations/tasks", { method: "POST", body: JSON.stringify({ model, content: [{ type: "text", text: "A ceramic cup rotates slowly on a clean studio table" }], ratio: "16:9", resolution: "720p", duration: 5, generate_audio: true }), }); const taskId = created.id; // Persist this before polling. for (;;) { const task = await request(`/contents/generations/tasks/${taskId}`); if (["succeeded", "failed", "expired"].includes(task.status)) { console.log(task); break; } await new Promise((resolve) => setTimeout(resolve, 5000)); }

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.



