A standalone, production-quality starter kit for overseas developers who want to build on supported Seedance text-to-video and image-to-video workflows through the Vancine API.
This repository contains only developer-facing integration assets: copyable examples, importable collections, and an automation workflow. It is intentionally small, language-agnostic at the protocol level, and designed to be verified offline.
Vancine provides access to supported Seedance workflows through its documented asynchronous API. Create an account, generate an API key, and check the live pricing page for current model prices.
To run the requests, import the collection file from this repository:
postman/Vancine-Seedance.postman_collection.json.
See Postman below for the import steps.
The imported collection ships with an empty api_key on purpose. Fill it in
through your own private or local variable after importing. Never save a real
key into a public or shared Collection.
A general product landing page tells you that an API exists. This repository is meant to help you make a working request as quickly as possible:
- Submit a Seedance video task and get a
task_id. - Poll the task status with bounded retries.
- Handle success, failure, and timeout explicitly.
- Retrieve the result URL.
If you can run one of the examples end to end, the integration path works.
- Vancine Seedance page: https://vancine.com/seedance-api?utm_source=github&utm_medium=developer_resource&utm_campaign=seedance_starter_kit&utm_content=readme
- Documentation: https://vancine.com/docs#video
- Pricing: https://vancine.com/pricing
- API status: https://vancine.com/api/status
Seedance requests use an asynchronous submit-and-poll workflow. Each request requires an API key sent as a bearer token.
POST /v1/video/generations
-> { "id": "...", "task_id": "...", "status": "queued",
"model": "...", "created_at": 1781863047 }
GET /v1/video/generations/{task_id}
-> { "code": "success", "message": "",
"data": { "task_id": "...", "status": "...",
"result_url": "...", "progress": "100%" } }
data.status: QUEUED -> IN_PROGRESS -> SUCCESS | FAILURE
success result: data.result_url failure reason: data.fail_reason
The submit call returns the task id at the top level. You then poll the status
endpoint on a fixed interval until data.status reaches a terminal state
(SUCCESS or FAILURE). Under normal conditions a video task completes within
seconds to a few minutes depending on the model and payload, but your code must
treat polling as bounded: a configurable interval, a maximum number of
attempts or maximum wait time, and a clear exit for failure and timeout.
Requests are authenticated with an API key generated from the Vancine console:
Authorization: Bearer $VANCINE_API_KEYREADME.md Product + integration guide
.env.example Environment variable template
.gitignore Default ignores
LICENSE MIT License (examples)
SECURITY.md Reporting and handling guidance
examples/
curl/
generate.sh Submit + poll from the shell
node/
package.json Node example (ESM, native fetch)
generate.mjs Submit + poll, importable functions
generate.test.mjs Offline unit tests (node:test)
python/
requirements.txt Runtime dependency (requests)
generate.py Submit + poll, importable functions
test_generate.py Offline unit tests (unittest)
postman/
Vancine-Seedance.postman_collection.json Importable Seedance requests
n8n/
vancine-seedance-workflow.json Importable async workflow
scripts/
validate-assets.mjs Structure + link + secret checks
Every example reads the API key from the environment. Copy the template first:
cp .env.example .env
# then edit .env and set VANCINE_API_KEYTo run the examples with the values from .env, load the file with set -a
(so every assignment is auto-exported to the environment) before running any
example, all from the repository root:
set -a
source .env
set +a| Variable | Required | Description |
|---|---|---|
VANCINE_API_KEY |
yes | API key from the Vancine console |
VANCINE_BASE_URL |
no | Override base URL (defaults to https://vancine.com) |
SEEDANCE_MODEL |
no | Model id (defaults to Doubao-Seedance-2.5) |
POLL_INTERVAL_SECONDS |
no | Seconds between polls (default 5) |
POLL_MAX_ATTEMPTS |
no | Maximum poll attempts (default 120) |
chmod +x examples/curl/generate.sh
./examples/curl/generate.shPrerequisites: curl, jq. The script is written for bash, checks both
dependencies, exits cleanly on any non-2xx response, and never prints the
full API key.
cd examples/node
npm install
node cli.mjsRequirements: Node.js 18+. The example uses native fetch and has no runtime
dependencies beyond the Node standard library.
The code is split into two files on purpose:
examples/node/generate.mjsholds all param-driven, unit-testable logic (parsing, polling, network calls that takeapiKeyas a parameter). It never reads the environment, so it can be imported in tests without side effects.examples/node/cli.mjsis the runnable entrypoint: it readsVANCINE_API_KEYfrom the environment and calls intogenerate.mjs.
Functions are written to be imported and tested without network access:
import {
submitTask,
extractTaskId,
extractStatus,
extractResultUrl,
generateVideo,
} from "./generate.mjs";Run the offline unit tests (these exercise generate.mjs only):
node --testcd examples/python
pip install -r requirements.txt
python generate.pyRequirements: Python 3.10+, requests. Parsing and polling logic is split into
importable functions that can be tested without network access:
from generate import submit_task, fetch_task, extract_task_id, extract_status, extract_result_urlRun the offline unit tests:
python -m unittest test_generate.py -vThe collection is in
postman/Vancine-Seedance.postman_collection.json
and uses the v2.1 format.
It defines collection variables so you do not have to repeat shared values:
| Variable | Default | Purpose |
|---|---|---|
base_url |
https://vancine.com |
API host |
api_key |
(empty — set this first) | API credential |
model |
Doubao-Seedance-2.5 |
Example model |
task_id |
(empty) | Set automatically |
The collection contains two requests:
- Submit Seedance Video —
POST {{base_url}}/v1/video/generations - Get Video Task Status —
GET {{base_url}}/v1/video/generations/{{task_id}}
- Open Postman.
- Choose Import in the top left.
- Select the
Vancine-Seedance.postman_collection.jsonfile. - Open the collection, open the Variables tab, and set
api_keyto your key. - Send Submit Seedance Video first.
After a successful submit, the test script on the submit request saves the task
id into the task_id collection variable. The saved value comes from the first
present field in this order: task_id, id, data.task_id, data.id. You can
then send Get Video Task Status to poll the result. Its test script reads
data.status first (the current contract) and falls back to a top-level
status. The saved poll examples show the current SUCCESS and FAILURE
envelopes. Set a bounded polling loop in your own test runner rather than
relying on a single manual send.
The collection ships with an empty api_key and contains no credential.
The workflow is in
n8n/vancine-seedance-workflow.json and
uses standard n8n nodes: Manual Trigger, Set/Edit Fields, two HTTP Request
nodes, Wait, IF, Merge, and a Code node (for state merging only — no network).
It expresses the same submit-and-poll lifecycle:
setFields
-> submitTask (HTTP Request + Header Auth)
-> saveTaskId (Set: extract task_id, carry config)
-> waitInterval
-> pollTask (HTTP Request + same Header Auth)
-> mergeState (Code: merge response + carried state, increment poll_index;
reads data.status / data.result_url / data.fail_reason first)
-> SUCCESS -> output task_id + result URL
-> FAILURE -> stop with error (shows saved task_id + data.fail_reason)
-> timeout -> stop (poll_index >= maxPolls)
-> otherwise -> continue polling (bounded loop back to waitInterval)
Both submitTask and pollTask are real HTTP Request nodes that use generic Header Auth. You must assign the same Header Auth credential to both nodes after import — the Code node never makes network requests.
Loop state (task_id, poll_index, maxPolls, base_url) is carried on
execution-scoped items via a Merge node (append mode) that combines the HTTP
poll response with a parallel state-carrying Set node. This avoids n8n's stale
$('NodeName') cross-node references inside loops and does not rely on global
workflow staticData, so concurrent executions never overwrite each other's
state.
A base_url field (default https://vancine.com) in the Set node is used by
both HTTP Request nodes. No change is needed for production use.
Like the other examples, maxPolls defaults to 120 at a 5-second interval —
about 10 minutes maximum — and stays user-configurable while the polling loop
remains strictly bounded (poll_index >= maxPolls stops it; there is no
infinite loop).
Verification status.
The workflow file currently in this repository was executed in real n8n 2.30.4 (pinned image
n8nio/n8n:2.30.4, version confirmed by the n8n CLI inside the running container) throughtests/n8n-integration-test.py. Every workflow request went to a local mock that accepts only the fictitious credentialAuthorization: Bearer dummy_local_key; no production Vancine endpoint was called, and no real API key exists anywhere in the setup. These are the results of this revision, not carried over from an earlier one.Current shape — the
{code, message, data}envelope withdata.status,data.result_urlanddata.fail_reason:
SUCCESS: 1POST+ 2GET, executionsuccess, output carries the matchingtask_idand the exactresult_url.FAILURE: 1POST+ 1GET, executionerror, the Stop-And-Error node fires and its message names thetask_idanddata.fail_reason; no furtherGETis issued.timeoutatmaxPolls=3: 1POST+ exactly 3GET, the timeout Stop-And-Error node terminates the run, and there is no 4thGET.Compatibility shape — the older top-level
completed/failedform withmetadata.urlanderror.message:
completed: 1POST+ 2GET, executionsuccess, result URL extracted frommetadata.url.failed: 1POST+ 1GET, executionerror, message carries thetask_idand the compatibility error text, with no extraGETafterwards.Concurrency — two overlapping executions issued 2
POSTand exactly 6GETin total, produced two distincttask_idvalues, polled only their own task three times each, and both reached their expected terminal state, so loop state, result URLs andpoll_indexnever cross-contaminate.Every workflow API request — to
/v1/video/generationsand to its task-status endpoint — carriedAuthorization: Bearer dummy_local_key, a fictitious local test value and not a real key. The harness also sends one reachability probe to the mock on a non-API path without that header; it is not a product workflow request and is excluded from every per-scenario request count. Each scenario's terminal state was read back from n8n's own execution records rather than inferred from the mock log alone.Import was additionally verified with the n8n CLI into a fresh, empty user directory (
Successfully imported 1 workflow).Scope of these claims: n8n 2.30.4 only — no other n8n version has been exercised. Nothing here claims certification, approval, or endorsement by n8n.
The same expressions are also covered offline by
tests/n8n-workflow-logic.test.mjs, andtests/mock-server.mjsdefaults to the current envelope so the cURL, Node.js and Python examples run end to end against it locally.
- In n8n, open Workflows -> Import from File.
- Select
vancine-seedance-workflow.json. - Create one HTTP Header Auth credential:
- Name:
Authorization - Value:
Bearer <YOUR_VANCINE_API_KEY>
- Name:
- Assign that same credential to both HTTP Request nodes
(
submitTaskandpollTask). - The
base_urlfield defaults tohttps://vancine.com— no change needed. - Update
model,prompt, andsizeon the Set node as needed, then run.
The current Vancine response contract is the generic task envelope: the poll
call answers { code, message, data } and the task fields live inside
data. Some deployments and upstream providers instead expose the same facts
top level (status, metadata.url, error.message). These examples read the
current data.* fields first and fall back to the older shapes, so they
keep working while the response format is normalized. The older shapes are
compatibility input only — never treat them as the current contract.
The submit receipt carries the id at the top level; the poll response
carries it as data.task_id. One shared extractor serves both, and it resolves
the first present value in this order:
- top-level
task_id— current submit receipt (and older poll responses) - top-level
id— current submit receipt (and older poll responses) data.task_id— current poll envelopedata.id— compatibility
The n8n mergeState node handles the poll response only, so there it prefers
data.task_id first.
The values below are treated as terminal. data.status is the current field.
Current (data.status) |
Compatibility (top-level status) |
Meaning |
|---|---|---|
QUEUED |
queued |
Waiting |
IN_PROGRESS |
in_progress, IN_PROGRESS |
Running |
SUCCESS |
completed |
Done |
FAILURE |
failed |
Failed |
Examples treat SUCCESS (and the compatibility completed) as success, and
FAILURE (and the compatibility failed) as a failure.
Resolved from the first present value in this order:
data.result_url— current contractmetadata.url— compatibilitydata.data.content.video_url— compatibility
Resolved from the first present value in this order:
data.fail_reason— current contractdata.error.message— compatibilityerror.message— compatibility
| Situation | HTTP | Typical body | What to do |
|---|---|---|---|
| Authentication failed | 401 | error.message about the key or endpoint |
Verify the key and that you POST to /v1/video/generations, not a chat or TTS path |
| Insufficient balance | 402 / 400 | error.message about balance or quota |
Check the live pricing page and your account balance in the console |
| Invalid model or payload | 400 | error.message about model, prompt, size, etc. |
Check the model id and required parameters |
| Task failure | 200 | data.status: "FAILURE" with data.fail_reason (older shapes use error.message) |
Read the reason and adjust the prompt or inputs |
| Polling timeout | — | no terminal state within the bounded window | Stop polling; report the task_id for debugging |
| Non-2xx poll response | 4xx/5xx | transport-level error | Stop; do not keep looping |
Rules the examples follow:
- A bounded polling loop with a configurable interval and a maximum attempt count. No infinite loop.
- Clear, distinct handling for submission error, authentication error, insufficient balance, task failure, and timeout.
- The full API key is never printed to logs or output.
- Always pass the API key through an environment variable. Never hard-code it.
- This repository ships with example prompts only; no real credential is committed. See SECURITY.md for how to report a vulnerability or a committed secret.
- Rotate the key from the Vancine console if you believe it is exposed.
This starter kit documents access to supported Seedance workflows. It does not guarantee:
- cheapest pricing
- unlimited or unrestricted usage
- guaranteed uptime
- guaranteed output for every prompt
- any safety-filter bypass
Vancine does not bypass model safety requirements. Model capabilities, input requirements, availability, and safety behavior follow their documented requirements. Model pricing and limits can change; the live pricing page and API documentation remain authoritative.
The example code in this repository is released under the MIT License.
The Seedance name and its underlying models are trademarks of their respective owners. This starter kit is an independent integration example and is not an official product of, or partnership with, those providers.