Official BytePlus partnerBytePlus, a ByteDance company
Seedance API reference
Everything a Seedance video job takes and returns on the KreatorFlow API: request fields, the job object, webhooks, uploads, rate limits and error codes. Prepaid tokens at $0.05, top-ups from $20.
- Base URL https://kreatorflow.ai/v1
- Bearer API key
- JSON
- Idempotency-Key
On this page
Authentication
Send your key as Authorization: Bearer KREATORFLOW_API_KEY on every request to https://kreatorflow.ai/v1. Keys are created in the API console and carry scopes, for example videos:create. Keep the key on your server, never in a browser or app.
Add an Idempotency-Key header to every create. If a request times out, send it again with the same key and you get the original job back instead of a second one.
POST /v1/videos
Creates a Seedance video job. The price is quoted and reserved before the job is accepted.
| Field | Type | Description |
|---|---|---|
| model | string | Model alias, for example kreator-video-seedance-2-5, kreator-video-pro (Seedance 2.0), kreator-video-fast or kreator-video-mini. Defaults to kreator-video-fast. |
| promptrequired | string | What should happen in the clip: subject, action, camera move, light and style. |
| duration | number | Length in seconds. Must be one of the model's durations (GET /v1/models lists them). |
| resolution | string | 480p, 720p, 1080p or 4k, where the model offers it. |
| aspect_ratio | string | For example 16:9, 9:16, 1:1, 4:3, 3:4 or 21:9. ratio is accepted as an alias. |
| generate_audio | boolean | Generate sound with the clip on models that support it. |
| seed | integer | Fix the seed to get a similar result from the same request. |
| camera_fixed | boolean | Keep the camera still. Leave it out when you send image or frame inputs. |
| watermark | boolean | Defaults to false. |
| first_frame_url / first_frame_upload_id | string | An image the clip starts on (public HTTPS URL or an upload id). |
| last_frame_url / last_frame_upload_id | string | An image the clip ends on. Send it with a first frame for a transition. |
| reference_image_urls / reference_image_upload_ids | string[] | Images that guide the look of a product, character, outfit or style. |
| reference_video_urls / reference_video_upload_ids | string[] | Clips that guide motion or camera work. |
| reference_audio_urls / reference_audio_upload_ids | string[] | Audio that guides voice, music or timing. |
| character_ids | string[] | Reusable characters registered once with POST /v1/characters. |
| webhook_url | string | HTTPS URL that receives a signed event when the job ends. callback_url is accepted as an alias. |
curl https://kreatorflow.ai/v1/videos \
-H "Authorization: Bearer $KREATORFLOW_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: perfume-teaser-001" \
-d '{
"model": "kreator-video-seedance-2-5",
"prompt": "slow dolly in on a glass perfume bottle on wet black stone, soft studio light, light rain",
"duration": 5,
"aspect_ratio": "9:16",
"resolution": "720p"
}'
# then poll until status is completed, failed, canceled or blocked
curl https://kreatorflow.ai/v1/jobs/JOB_ID \
-H "Authorization: Bearer $KREATORFLOW_API_KEY"import os, time, requests # pip install requests
API = "https://kreatorflow.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['KREATORFLOW_API_KEY']}"}
job = requests.post(
f"{API}/videos",
headers={**HEADERS, "Idempotency-Key": "perfume-teaser-001"},
json={
"model": "kreator-video-seedance-2-5",
"prompt": "slow dolly in on a glass perfume bottle on wet black stone, soft studio light, light rain",
"duration": 5,
"aspect_ratio": "9:16",
"resolution": "720p",
},
timeout=60,
).json()["data"]["job"]
while job["status"] not in ("completed", "failed", "canceled", "blocked"):
time.sleep(3)
job = requests.get(f"{API}/jobs/{job['id']}", headers=HEADERS, timeout=60).json()["data"]["job"]
print(job["status"], job["providerOutputUrl"])const API = "https://kreatorflow.ai/v1";
const headers = { Authorization: `Bearer ${process.env.KREATORFLOW_API_KEY}` };
let { data } = await fetch(`${API}/videos`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": "perfume-teaser-001" },
body: JSON.stringify({
model: "kreator-video-seedance-2-5",
prompt: "slow dolly in on a glass perfume bottle on wet black stone, soft studio light, light rain",
duration: 5,
aspect_ratio: "9:16",
resolution: "720p",
}),
}).then((response) => response.json());
let job = data.job;
while (!["completed", "failed", "canceled", "blocked"].includes(job.status)) {
await new Promise((resolve) => setTimeout(resolve, 3000));
({ data } = await fetch(`${API}/jobs/${job.id}`, { headers }).then((response) => response.json()));
job = data.job;
}
console.log(job.status, job.providerOutputUrl);Limits differ by model. The Seedance 2.5 API, Seedance 2.0 API, Seedance 2.0 Fast API and Seedance 2.0 Mini API pages list the durations, resolutions, reference limits and price of each.
Response and the job object
A create returns the job, the quoted cost and your remaining balance.
{
"success": true,
"data": {
"job": {
"id": "3f0c0e0a-0000-4000-8000-000000000000",
"mediaType": "video",
"modelId": "kreator-video-fast",
"status": "processing",
"quotedTokenCost": 9,
"finalTokenCost": 0,
"providerOutputUrl": null,
"errorCode": null,
"errorMessage": null,
"createdAt": "2026-10-17T12:00:00.000Z",
"completedAt": null
},
"quoted_token_cost": 9,
"balance_tokens": 391
}
}| Field | Type | Description |
|---|---|---|
| id | string | Job id. Use it with GET /v1/jobs/{id}. |
| status | string | One of created, queued, processing, completed, failed, canceled, blocked. completed, failed, canceled and blocked are final. |
| modelId | string | The model alias the job ran on. |
| quotedTokenCost | number | Tokens reserved when the job was accepted (1 token = $0.05). |
| finalTokenCost | number | Tokens charged. 0 for a job that failed, so nothing is charged for failed work. |
| providerOutputUrl | string | null | Download link for the finished file. It needs your API key and expires, so copy the file to your own storage. |
| errorCode / errorMessage | string | null | Why a job failed (see the error codes below). |
| createdAt / completedAt | string | ISO timestamps. |
GET /v1/jobs/{id}
Poll a job until its status is final, or use a webhook instead.
{
"success": true,
"data": {
"job": {
"id": "3f0c0e0a-0000-4000-8000-000000000000",
"status": "completed",
"quotedTokenCost": 9,
"finalTokenCost": 9,
"providerOutputUrl": "https://kreatorflow.ai/v1/jobs/3f0c0e0a-0000-4000-8000-000000000000/output",
"providerOutputExpiresAt": "2026-10-24T12:00:00.000Z",
"errorCode": null,
"completedAt": "2026-10-17T12:01:10.000Z"
}
}
}Poll every 10 to 15 seconds for video. GET /v1/jobs/{id}/output downloads the file with the same key; the link expires, so copy each finished file to your own storage (see storing and delivering AI video).
Webhooks
Pass webhook_url and KreatorFlow posts job.completed, job.failed, job.canceled or job.blocked when the job ends.
{
"object": "kreatorflow.developer_webhook_event",
"id": "evt_<job_id>_completed",
"type": "job.completed",
"created": "2026-10-17T12:01:10.000Z",
"data": {
"job": {
"id": "<job_id>",
"status": "completed",
"providerOutputUrl": "https://kreatorflow.ai/v1/jobs/<job_id>/output"
}
}
}Each event carries X-KreatorFlow-Event-Id, X-KreatorFlow-Event-Type, X-KreatorFlow-Job-Id, X-KreatorFlow-Timestamp and X-KreatorFlow-Signature. Check the signature with the secret from the Webhooks tab, and use the event id to ignore an event you have already handled. Answer with a 2xx quickly and do the work afterwards.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received (a string, before JSON.parse)
export function verifyKreatorFlowWebhook(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(",").map((part) => part.split("=")));
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // reject replays older than 5 min
return fresh && expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}import hashlib, hmac, time
def verify_kreatorflow_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
parts = dict(part.split("=", 1) for part in signature_header.split(","))
signed = f"{parts['t']}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300 # reject replays older than 5 min
return fresh and hmac.compare_digest(expected, parts.get("v1", ""))The full walkthrough is in AI video API webhooks.
Uploads and references
References can be public HTTPS URLs or files you upload. To upload: POST /v1/uploads with the file name, type and size, PUT the bytes to the returned direct_upload_url with the returned headers, then POST /v1/uploads/complete. Pass the upload_id in any reference field. Size limits: images 50 MB, video 200 MB, audio 15 MB.
References must be media you own or have permission to use. A photo of a real person is declined as a reference; register an owned character with POST /v1/characters instead.
Rate limits
Per developer account. A 429 response carries a Retry-After header with the seconds to wait.
| POST /v1/uploads | 60 per minute |
|---|---|
| PUT /v1/uploads | 60 per minute |
| DELETE /v1/uploads/{upload_id} | 60 per minute |
| POST /v1/videos | 20 per minute |
| GET /v1/jobs/{job_id} | 120 per minute |
| GET/HEAD /v1/jobs/{job_id}/output | 300 per minute |
Error codes
Request errors fail the HTTP call itself. Job errors end a job as failed, and its reserved tokens are released.
Request errors
| Code | Status | Meaning | 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. |
| insufficient_balance | 402 | Not enough tokens for the quoted cost. | Top up under Billing. data.top_up_url links there. |
| account_not_active | 403 | The developer account is suspended. | Contact support. |
| scope_missing | 403 | The key lacks the scope this endpoint needs. | Use a key with that scope. |
| job_not_found | 404 | No job with that id for this key's account. | Check the id; jobs are private to the account. |
| character_not_found | 404 | The character id or slug doesn't exist. | List characters with GET /v1/characters. |
| upload_not_found | 404 | The upload_id doesn't exist in this workspace. | Create the upload again. |
| reference_upload_not_found | 404 | A reference upload_id doesn't exist in this workspace. | Re-upload the file and use the new upload_id. |
| video_edit_source_not_found | 404 | The source_video_upload_id doesn't exist. | Upload the clip with upload_type VIDEO. |
| upload_in_use | 409 | A queued or processing job still uses this upload. | Delete it after the job finishes. |
| character_pending | 409 | The character is still being reviewed. Nothing was reserved. | Wait retry_after seconds, then retry. |
| upload_too_large | 413 | The file is over the size limit for its type. | Images 50 MB, video 200 MB, audio 15 MB. |
| character_registration_failed | 422 | The character image did not pass review. | Register the character again with another image. |
| rate_limited | 429 | Too many requests in the window. | Wait the seconds in the Retry-After header. |
| 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. |
| reference_combination_not_supported | 400 | These references can't be combined, e.g. start/end frames with reference images. | Send one kind per request. |
| character_prompt_unsafe | 400 | A character prompt asks to match a real person. | Describe appearance, wardrobe, pose and style instead. |
| person_match_prompt_unsafe | 400 | A reference prompt asks to match a real person's face or likeness. | Register an owned character and describe the look instead. |
| upload_invalid | 400 | The upload request is missing a field or has a bad type. | Send file_name, mime_type, file_size and upload_type. |
| video_edit_model_not_supported | 400 | Video edits need a Seedance 2.0 family model (2.0, Fast or Mini). | Use kreator-video-pro, kreator-video-fast or kreator-video-mini. |
| video_edit_source_required | 400 | A video edit needs a source clip. | Send source_video_upload_id or video. |
| voice_required | 400 | Speech needs a voice. | Use an id from GET /v1/voices. |
| 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. |
Job errors
| Code | Status | Meaning | What to do |
|---|---|---|---|
| reference_rejected | job | A reference was declined (often a real person's photo). | Register the face as a character, or use another image. |
| reference_asset_pending | job | A new reference is still being reviewed. | Wait errorData.retry_after seconds, resubmit with the same upload_id and a new Idempotency-Key. |
| reference_asset_failed | job | The reference did not pass review. | Use a different file. |
| generation_rejected | job | The request was declined (prompt, reference or options). | Change the prompt, reference or options, then retry. |
| generation_timeout | job | The job did not finish in time. Reserved tokens are released. | Retry with a new Idempotency-Key. |
| generation_unavailable | job | The model is temporarily unavailable. | Retry later or pick another model. |
| generation_failed | job | The generation failed. | Retry; change the prompt or references if it repeats. |