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
Last updated
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.
The whole API is also described in an OpenAPI 3.1 spec (https://kreatorflow.ai/openapi.json): every public /v1 endpoint, request field, error code and the webhook signature, for Postman, code generators and AI agents.
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 |
| POST /v1/videos/edit and /v1/videos/extend (shared with POST /v1/videos) | 20 per minute |
| GET /v1/jobs/{job_id} | 120 per minute |
| GET/HEAD /v1/jobs/{job_id}/output | 300 per minute |
Volume pricing for the Seedance API
The more you top up, the less Seedance video costs. Your tier comes from the token top-ups you paid for in the last 30 days. It lowers the per-second price of Seedance video and raises your video request limit, automatically, with no contract and no sales call. If your top-ups drop, the tier moves back down.
| Tier | Paid top-ups, last 30 days | Seedance video price | Video requests / minute | Reached in one top-up |
|---|---|---|---|---|
| Standard | Any | List price | 20 | – |
| Scale | $1,000+ | up to 5% off | 60 | Volume pack ($1,250) |
| Growth | $2,500+ | up to 7.5% off | 120 | Volume+ pack ($2,500) |
| Enterprise | $5,000+ | up to 10% off | 300 | Enterprise pack ($5,000) |
Every job's quote shows your price before it runs; a few low-priced settings get a smaller discount. Smaller top-ups add up toward a tier too. Price comparisons on this site use list prices unless a price is marked as at volume. Need more volume? Custom pricing for committed monthly spend: email harry@kreatorflow.ai.
Effective Seedance price per second by tier
What one second of Seedance video costs on each tier, from the same rate card and tier rules the API bills with.
| Model, resolution | Standard (list) | Scale (5% off) | Growth (7.5% off) | Enterprise (10% off) |
|---|---|---|---|---|
| Seedance 2.5, 720p | $0.207/s | $0.1967/s | $0.1915/s | $0.1863/s |
| Seedance 2.5, 1080p | $0.444/s | $0.4218/s | $0.4107/s | $0.3996/s |
| Seedance 2.0, 720p | $0.110/s | $0.1045/s | $0.1018/s | $0.099/s |
| Seedance 2.0, 1080p | $0.240/s | $0.228/s | $0.222/s | $0.216/s |
| Seedance 2 Fast, 720p | $0.0844/s | $0.0802/s | $0.0781/s | $0.076/s |
| Seedance 2 Fast, 1080p | $0.183/s | $0.1739/s | $0.1693/s | $0.1647/s |
| Seedance 2.0 Mini, 720p | $0.0674/s | $0.0641/s | $0.0624/s | $0.0607/s |
| Seedance 2.0 Mini, 1080p | $0.135/s | $0.1283/s | $0.1249/s | $0.1215/s |
Billed per delivered second; each clip's total is rounded up to a whole token ($0.05). "see quote" means that setting gets a smaller discount on that tier, and the job's quote shows the exact price before it runs.
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_developer | 400 | The upload_id is a file uploaded on the website, not through the API. | Upload this file with /v1/uploads first. |
| 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. |
| 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. |
| paid_account_required | 403 | Lip Sync needs a paid account. Nothing was charged. | Top up with a card under Billing. |
| paid_check_unavailable | 503 | The account's payment status could not be read just now. Nothing was charged. | Send the same request again in a moment. |
| audio_reference_too_long | 400 | The audio references add up to more than the model accepts in total (15 s on Seedance 2.0, 30 s on 2.5). Nothing was charged. | Trim or remove audio so all audio references combined fit. |
| image_upload_not_found | 404 | The image_upload_id isn't in this workspace. | Upload the photo with upload_type IMAGE and use the new upload_id. |
| audio_upload_not_found | 404 | The audio_upload_id isn't in this workspace (or audio_job_id isn't a completed /v1/audio job). | Upload the track with upload_type AUDIO, or use a completed audio job id. |
| audio_too_short | 400 | The voice track is under 4 seconds. Nothing was charged. | Send 4 seconds to 5 minutes of audio. |
| audio_too_long | 400 | The voice track is over 5 minutes. Nothing was charged. | Trim it to 5 minutes or less. |
| audio_unreadable | 400 | The audio couldn't be read. Nothing was charged. | Use an MP3 or WAV under 15 MB. |
| image_rejected | 422 | The photo didn't pass the safety check. Nothing was charged. | Use a different photo. |
| 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 did not pass screening. | For a real person you have consent to use, register the face as a character (reviewed once, then reused), 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. |