On this page
- Short answer
- Call the Seedance API from code in six steps
- Request errors a video script should handle
- Seedance API price per clip (18 October 2026)
- What do I need before I start?
- Which fields does POST /v1/videos take?
- How do I call the Seedance API from Python?
- How do I call the Seedance API from Node.js?
- How do I generate many videos at once?
- Does the Seedance API support webhooks?
- How do I verify the webhook in Node.js?
- Which errors should my code handle?
- How much does each call cost?
- Frequently asked questions
Short answer
To use the Seedance API from Python or Node.js, send POST https://kreatorflow.ai/v1/videos with your key in "Authorization: Bearer", then poll GET /v1/jobs/{id} until the status is completed and stream GET /v1/jobs/{id}/output to a file. No SDK is needed: requests in Python and the built-in fetch in Node 18+ are enough. A 5-second Seedance 2.0 clip at 720p costs $0.55 (as of 18 October 2026).
Get started
Call the Seedance API from code in six steps
- 1
Get an API key
Create a key with the videos:create and jobs:read scopes in the developer console, top up from $20, and export it as KREATORFLOW_API_KEY on the machine that runs the script.
- 2
Install the HTTP client
Python: pip install requests. Node.js: nothing to install on Node 18 or later, which ships fetch.
- 3
Create a video job
POST /v1/videos with model, prompt, duration, aspect_ratio and resolution, plus an Idempotency-Key header. The answer is 202 with the job id and the quoted token cost.
- 4
Poll the job
GET /v1/jobs/{id} every 10 seconds or so until the status is completed, failed, canceled or blocked. On a 429, wait the seconds in Retry-After.
- 5
Download the MP4
When the status is completed, GET /v1/jobs/{id}/output with the same key and stream the body to a file, then copy it to your own storage.
- 6
Switch to webhooks in production
Pass webhook_url when you create the job and verify the X-KreatorFlow-Signature header in a small handler, so no worker sits in a polling loop.
Request errors a video script should handle
From the KreatorFlow API reference. Every one returns success: false with this code.
| Code | HTTP | What it means | What to do |
|---|---|---|---|
| invalid_api_key | 401 | The key is missing, wrong or revoked. | Send Authorization: Bearer <key>. Create a new key if it was revoked. |
| scope_missing | 403 | The key lacks the scope this endpoint needs. | Use a key with that scope. |
| prompt_required | 400 | No prompt (or text for speech) was sent. | Add a prompt. |
| prompt_too_long | 400 | The prompt is over the model's limit. | Shorten it; GET /v1/models lists maxPromptChars. |
| model_not_found | 400 | Unknown model alias. | Use an id from GET /v1/models. |
| duration_not_supported | 400 | The model doesn't offer that length. | Send one of the model's durations. |
| aspect_ratio_not_supported | 400 | The model doesn't offer that aspect ratio. | Send one of the model's aspect ratios. |
| resolution_not_supported | 400 | The model doesn't offer that resolution. | Send one of the model's resolutions. |
| insufficient_balance | 402 | Not enough tokens for the quoted cost. | Top up under Billing. data.top_up_url links there. |
| job_not_found | 404 | No job with that id for this key's account. | Check the id; jobs are private to the account. |
| rate_limited | 429 | Too many requests in the window. | Wait the seconds in the Retry-After header. |
| internal_error | 500 | Something failed on our side. | Retry with the same Idempotency-Key. |
| dispatch_disabled | 503 | Generation is paused for this environment. | Retry later. |
Seedance API price per clip (18 October 2026)
From the KreatorFlow developer API rate card: a 5-second clip without a video input.
| Model | Model id | Resolution | Per second | 5-second clip |
|---|---|---|---|---|
| Seedance 2.5 | kreator-video-seedance-2-5 | 720p | $0.207/s | $1.05 (21 tokens) |
| Seedance 2.5 | kreator-video-seedance-2-5 | 1080p | $0.444/s | $2.25 (45 tokens) |
| Seedance 2.0 | kreator-video-seedance-2 | 720p | $0.110/s | $0.55 (11 tokens) |
| Seedance 2.0 | kreator-video-seedance-2 | 1080p | $0.240/s | $1.20 (24 tokens) |
| Seedance 2.0 Fast | kreator-video-seedance-2-fast | 720p | $0.084/s | $0.45 (9 tokens) |
| Seedance 2.0 Fast | kreator-video-seedance-2-fast | 1080p | $0.183/s | $0.95 (19 tokens) |
| Seedance 2.0 Mini | kreator-video-seedance-2-mini | 720p | $0.067/s | $0.35 (7 tokens) |
| Seedance 2.0 Mini | kreator-video-seedance-2-mini | 1080p | $0.135/s | $0.70 (14 tokens) |
The create response always carries the exact quote for your request.
What do I need before I start?
A KreatorFlow developer account, an API key and a token balance. The API is plain HTTPS and JSON at https://kreatorflow.ai/v1, so there is no SDK to install and nothing to keep up to date. KreatorFlow is an official partner of BytePlus, a ByteDance company, and the models are ByteDance's Seedance family.
Keep the key on your server: read it from an environment variable, never commit it, and never send it to a browser or a mobile app. Your users talk to your backend; your backend talks to KreatorFlow.
Which fields does POST /v1/videos take?
model and prompt are required. duration (seconds), aspect_ratio (ratio also works), resolution and generate_audio shape the clip; seed fixes the randomness so you can compare prompt changes; webhook_url asks for a signed call when the job ends. Seedance 2.0 (kreator-video-seedance-2) takes 4, 5, 6, 8, 10, 12 and 15 seconds and 480p, 720p, 1080p and 4K. GET /v1/models lists every model id your key can use with its limits.
The answer comes back at once with HTTP 202: the job id and status, your balance and the quoted cost in tokens. The job then moves through created, queued, processing, completed, failed, canceled and blocked.
Create response
HTTP/1.1 202 Accepted { "success": true, "data": { "job": { "id": "3f1c0a9e-...", "mediaType": "video", "modelId": "kreator-video-seedance-2", "status": "queued", ... }, "balance_tokens": 389, "quoted_token_cost": 11, "estimated_token_cost": 11 } }
How do I call the Seedance API from Python?
Save this as kreatorflow_video.py and run python kreatorflow_video.py "your prompt". It creates a 5-second 16:9 clip at 720p, polls every 10 seconds, waits out a 429, stops after 30 minutes, and streams the finished MP4 to disk in 1 MB chunks so a large file never sits in memory.
Errors from the API raise KreatorFlowError with the HTTP status and the machine-readable code, so the calling code can branch on err.code (for example top up on insufficient_balance) instead of parsing messages.
Python 3 (requests)
# kreatorflow_video.py: create a Seedance video, wait for it, save the MP4. # pip install requests (Python 3.9+) import os import sys import time import uuid import requests BASE = "https://kreatorflow.ai/v1" KEY = os.environ["KREATORFLOW_API_KEY"] AUTH = {"Authorization": f"Bearer {KEY}"} FINAL = {"completed", "failed", "canceled", "blocked"} class KreatorFlowError(Exception): def __init__(self, status, code, message): super().__init__(f"{status} {code}: {message}") self.status, self.code = status, code def _json(res): body = res.json() if res.status_code >= 400 or not body.get("success"): raise KreatorFlowError(res.status_code, body.get("code"), body.get("error")) return body["data"] def create_video(prompt, *, model="kreator-video-seedance-2", duration=5, aspect_ratio="16:9", resolution="720p", webhook_url=None): payload = {"model": model, "prompt": prompt, "duration": duration, "aspect_ratio": aspect_ratio, "resolution": resolution} if webhook_url: payload["webhook_url"] = webhook_url # One Idempotency-Key per clip: resending the same request never creates a second job. headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())} data = _json(requests.post(f"{BASE}/videos", json=payload, headers=headers, timeout=60)) print(f"job {data['job']['id']} quoted at {data['quoted_token_cost']} tokens") return data["job"] def get_job(job_id): res = requests.get(f"{BASE}/jobs/{job_id}", headers=AUTH, timeout=30) if res.status_code == 429: time.sleep(int(res.headers.get("Retry-After", "5"))) return get_job(job_id) return _json(res)["job"] def wait_for_job(job, every=10, timeout=1800): deadline = time.time() + timeout while job["status"] not in FINAL: if time.time() > deadline: raise TimeoutError(f"job {job['id']} still {job['status']}") time.sleep(every) job = get_job(job["id"]) return job def download(job_id, path): with requests.get(f"{BASE}/jobs/{job_id}/output", headers=AUTH, stream=True, timeout=120) as res: res.raise_for_status() with open(path, "wb") as out: for chunk in res.iter_content(chunk_size=1 << 20): out.write(chunk) return path if __name__ == "__main__": prompt = " ".join(sys.argv[1:]) or "A paper boat drifting down a rainy street at dusk, low angle" try: job = wait_for_job(create_video(prompt)) except KreatorFlowError as err: sys.exit(f"request failed: {err}") if job["status"] == "completed": print("saved", download(job["id"], f"{job['id']}.mp4")) else: sys.exit(f"{job['status']}: {job.get('errorCode')} {job.get('errorMessage')}")
How do I call the Seedance API from Node.js?
The same client as an ES module. Save it as kreatorflow-video.mjs and run node kreatorflow-video.mjs "your prompt". It uses only built-ins: fetch, crypto.randomUUID for the Idempotency-Key, and a stream pipeline to write the MP4 without buffering it.
The exported functions (createVideo, getJob, waitForJob, download) drop straight into an Express route, a queue worker or a Next.js server action.
Node.js 18+ (fetch, ESM)
// kreatorflow-video.mjs: create a Seedance video, wait for it, save the MP4. // Node 18+ (fetch is built in). Run: node kreatorflow-video.mjs "your prompt" import { createWriteStream } from "node:fs"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; const BASE = "https://kreatorflow.ai/v1"; const AUTH = { Authorization: `Bearer ${process.env.KREATORFLOW_API_KEY}` }; const FINAL = new Set(["completed", "failed", "canceled", "blocked"]); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export class KreatorFlowError extends Error { constructor(status, code, message) { super(`${status} ${code}: ${message}`); this.status = status; this.code = code; } } async function json(res) { const body = await res.json(); if (!res.ok || !body.success) throw new KreatorFlowError(res.status, body.code, body.error); return body.data; } export async function createVideo(prompt, { model = "kreator-video-seedance-2", duration = 5, aspectRatio = "16:9", resolution = "720p", webhookUrl } = {}) { const res = await fetch(`${BASE}/videos`, { method: "POST", // One Idempotency-Key per clip: resending the same request never creates a second job. headers: { ...AUTH, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ model, prompt, duration, aspect_ratio: aspectRatio, resolution, ...(webhookUrl ? { webhook_url: webhookUrl } : {}) }), }); const data = await json(res); console.log(`job ${data.job.id} quoted at ${data.quoted_token_cost} tokens`); return data.job; } export async function getJob(id) { const res = await fetch(`${BASE}/jobs/${id}`, { headers: AUTH }); if (res.status === 429) { await sleep(Number(res.headers.get("Retry-After") ?? 5) * 1000); return getJob(id); } return (await json(res)).job; } export async function waitForJob(job, { every = 10_000, timeout = 30 * 60_000 } = {}) { const deadline = Date.now() + timeout; while (!FINAL.has(job.status)) { if (Date.now() > deadline) throw new Error(`job ${job.id} still ${job.status}`); await sleep(every); job = await getJob(job.id); } return job; } export async function download(id, path) { const res = await fetch(`${BASE}/jobs/${id}/output`, { headers: AUTH }); if (!res.ok) throw new KreatorFlowError(res.status, "download_failed", await res.text()); await pipeline(Readable.fromWeb(res.body), createWriteStream(path)); return path; } const prompt = process.argv.slice(2).join(" ") || "A paper boat drifting down a rainy street at dusk, low angle"; try { const job = await waitForJob(await createVideo(prompt)); if (job.status === "completed") console.log("saved", await download(job.id, `${job.id}.mp4`)); else console.error(job.status, job.errorCode, job.errorMessage); } catch (err) { console.error("request failed:", err.message); process.exitCode = 1; }
How do I generate many videos at once?
Submit every job first, then wait for them together: the batch then takes about as long as its slowest clip, not the sum of all of them. POST /v1/videos allows 20 requests per 60 seconds per account, so pace larger batches. Drafts on the Fast model cost less; render the keepers on Seedance 2.0 with the same prompt.
Python batch
# batch.py: submit several prompts at once, then collect every clip. from concurrent.futures import ThreadPoolExecutor from kreatorflow_video import create_video, download, wait_for_job prompts = [ "A red fox trotting through fresh snow at sunrise, side tracking shot", "Steam rising from a bowl of ramen, slow push in, warm light", "A skateboarder kickflips over a puddle at golden hour, low angle", ] jobs = [create_video(p, model="kreator-video-seedance-2-fast") for p in prompts] # submit all first with ThreadPoolExecutor(max_workers=len(jobs)) as pool: for job in pool.map(wait_for_job, jobs): if job["status"] == "completed": print("saved", download(job["id"], f"{job['id']}.mp4")) else: print(job["id"], job["status"], job.get("errorCode"))
Does the Seedance API support webhooks?
Yes. Add webhook_url to the create request and KreatorFlow sends a signed POST when the job reaches its final status, with type job.completed, job.failed, job.canceled or job.blocked and the job in data.job. The headers are X-KreatorFlow-Event-Id, X-KreatorFlow-Event-Type, X-KreatorFlow-Job-Id, X-KreatorFlow-Timestamp and X-KreatorFlow-Signature.
X-KreatorFlow-Signature looks like t=<unix time>,v1=<hex>. Recompute HMAC-SHA256 of "<t>.<raw body>" with the signing secret from the console's Webhooks section, compare in constant time, reject old timestamps, and store each event id so you handle every event once. Reply 2xx quickly and do the download in the background.
Python (Flask)
# webhook.py: receive the signed "job finished" call instead of polling. # pip install flask Run: flask --app webhook run --port 8000 import hashlib import hmac import os import time from flask import Flask, abort, request app = Flask(__name__) SECRET = os.environ["KREATORFLOW_WEBHOOK_SECRET"].encode() seen = set() # use your database in production @app.post("/kreatorflow/webhook") def kreatorflow_webhook(): raw = request.get_data(as_text=True) sig = request.headers.get("X-KreatorFlow-Signature", "") parts = dict(p.split("=", 1) for p in sig.split(",") if "=" in p) expected = hmac.new(SECRET, f'{parts.get("t", "")}.{raw}'.encode(), hashlib.sha256).hexdigest() fresh = abs(time.time() - int(parts.get("t", "0"))) < 300 if not (fresh and hmac.compare_digest(expected, parts.get("v1", ""))): abort(401) event = request.get_json() if event["id"] not in seen: seen.add(event["id"]) job = event["data"]["job"] print(event["type"], job["id"], job["status"]) # queue the download here return "ok", 200
How do I verify the webhook in Node.js?
Read the raw body before parsing it: the signature covers the exact bytes that were sent. This handler uses only node:http and node:crypto.
Node.js (node:http)
// webhook.mjs: receive the signed "job finished" call (no framework needed). import { createHmac, timingSafeEqual } from "node:crypto"; import { createServer } from "node:http"; const SECRET = process.env.KREATORFLOW_WEBHOOK_SECRET; const seen = new Set(); // use your database in production function verify(raw, header = "") { const parts = Object.fromEntries(header.split(",").map((part) => part.split("="))); const expected = createHmac("sha256", SECRET).update(`${parts.t}.${raw}`).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; return fresh && typeof parts.v1 === "string" && parts.v1.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)); } createServer(async (req, res) => { if (req.method !== "POST" || req.url !== "/kreatorflow/webhook") return res.writeHead(404).end(); let raw = ""; for await (const chunk of req) raw += chunk; if (!verify(raw, req.headers["x-kreatorflow-signature"])) return res.writeHead(401).end("bad signature"); const event = JSON.parse(raw); if (!seen.has(event.id)) { seen.add(event.id); console.log(event.type, event.data.job.id, event.data.job.status); // queue the download here } res.writeHead(200).end("ok"); }).listen(8000);
Which errors should my code handle?
Every error is JSON with success: false, a code and a readable error. Branch on code, never on the message text. A 4xx request error (the table below) means no job was created and nothing was charged: fix the request, then send it again. For internal_error, resending with the same Idempotency-Key is safe.
A job that starts and then fails ends with status failed, canceled or blocked, and carries errorCode and errorMessage. The tokens held for it are given back. Change the prompt or reference if the error points at it, then create a new job.
- Check success on every response, not only the HTTP status
- Stop and alert on invalid_api_key, scope_missing and insufficient_balance: resending will not help
- Wait for Retry-After on 429 before the next call
- Treat failed, canceled and blocked as final and log errorCode with the job id
How much does each call cost?
Video is billed per second of output at a rate set by model and resolution, and every create response carries the exact quote before the job runs. A 5-second Seedance 2.0 clip costs $0.55 at 720p and $1.20 at 1080p. The same clip on Seedance 2.0 Fast is $0.45 at 720p. One token is $0.05; top-ups start at $20. Prices as of 18 October 2026.
The cheapest Seedance API at 720p and above: Seedance 2.0 and 2.5 at 720p, 1080p, 4K cost less per video than on any of the 9 Seedance APIs we track (list prices from each provider's own page, checked 17 October 2026).
Checked 17 October 2026
The cheapest Seedance API at 720p and above
Seedance 2.0 and 2.5 at 720p, 1080p, 4K cost less per video than on any of the 9 Seedance APIs we track (list prices from each provider's own page, checked 17 October 2026).
Frequently asked questions
Is there an official Seedance Python SDK?
You do not need one. The KreatorFlow API is plain REST and JSON, so the requests library (or httpx) is enough; the client in this guide is under 100 lines.
Which Node.js version do I need?
Node 18 or later, which has fetch, crypto.randomUUID and web streams built in. Older versions need a fetch package such as undici.
How long does a Seedance video take to generate?
Usually a few minutes, longer for long or high-resolution clips. Poll every 10 seconds or so, or use a webhook; never hold a browser request open while it renders.
How do I avoid paying twice if a request times out?
Send an Idempotency-Key header with each create call and reuse it when you resend that same request. The API returns the job it already created, marked idempotent_replay: true.
Can I call the Seedance API from the browser?
No. The key is a secret. Call the API from your server or a serverless function and give your users the finished file or your own link to it.
How do I download the finished video?
GET /v1/jobs/{id}/output with the same Bearer key streams the file. Save it to your own storage: the output link needs your key and does not last forever.
Can I use image or video references from code?
Yes. Send public HTTPS URLs in reference_image_urls, reference_video_urls or reference_audio_urls, or upload files first with POST /v1/uploads and pass the upload ids. The quickstart covers the upload flow.



