Lewati ke konten
Seedancee

Seedance API: endpoints, docs and early access

Seedancee's REST API lets you create Seedance generations, poll jobs and receive signed webhooks from your own code, using the same models, credits and refund rules as the workspace. API access is part of the Pro plan, and the API is in early access: the endpoints below are live and answer for Pro workspaces (and workspaces admitted to early access) once the API is switched on. Other requests get a clear 403: api_disabled while the API is off, plan_required when the workspace is not on Pro.

Request API access

Tell us your use case and expected monthly volume. API access comes with Pro; early access for other workspaces is granted in batches.

Request API access

Overview

The Seedancee API is a thin, stable layer over the same generation pipeline the dashboard uses. You authenticate with an API key, submit a generation request that is validated against the model registry (an unsupported resolution, duration or reference count is rejected before any credits are reserved), then poll the job or receive a signed webhook when it finishes. Finished videos are copied to Seedancee storage and returned as signed URLs, so you never depend on provider links that expire after 14 days. The machine-readable OpenAPI 3.1 spec is at /openapi.json.

  • Models: every active Seedance model (2.5, 2.0, 2.0 Fast, 2.0 Mini, 1.5 Pro) with capabilities and credit rates served from the registry.
  • Modes: text-to-video, image-to-video (first and last frame), reference-to-video, and edit/extend where the model supports them.
  • Credits: the same per-generation pricing as the workspace, drawn from the same balance, with automatic release on failure or cancellation.
  • Plan rules: API generations follow the same plan rules as the workspace. Pro, the plan that includes the API, allows every model, resolution and duration the model supports, plus reference videos, edit and extend.
  • Uploads: presigned uploads for reference images, videos, audio and first/last frames.
  • Delivery: polling, or webhooks signed with HMAC-SHA256 and retried on failure.

Authentication and scopes

Create keys in the dashboard under Developer API; a workspace can hold up to 10 active keys. A key is shown once, stored only as a SHA-256 hash, can be named and revoked individually (a revoked key cannot be restored), and acts for your workspace. Send it as a bearer token. Missing, invalid, revoked or expired keys return 401; while the API is switched off every call returns 403 api_disabled; a workspace that is not on Pro (and not in early access) returns 403 plan_required; a key without the scope an endpoint needs returns 403 insufficient_scope. If the workspace leaves Pro, its keys stop working but stay listed so you can revoke them.

Headers
Authorization: Bearer sdk_live_...
Content-Type: application/json
Idempotency-Key: order-1234   # optional, on POST /videos/generations
API key scopes (new keys get all five)
ScopeAllows
models:readGET /api/v1/models
uploads:writePOST /api/v1/uploads and /uploads/{id}/complete, GET /uploads/{id}
generations:writeCreate and cancel generations, send webhook test events
generations:readGet and list generations
credits:readGET /api/v1/credits

Endpoints

Seedancee API v1 endpoints (base URL https://seedancee.com/api/v1)
MethodPathPurpose
GET/modelsActive models with snake_case capabilities and credit rates
POST/uploadsPresigned upload for an image, video or audio reference
POST/uploads/{id}/completeConfirm the upload so it can be used as a reference (images are usually ready at once; videos and audio are verified first)
GET/uploads/{id}Upload status while the file is verified: poll until ready or failed
POST/videos/generationsCreate a generation (validates, reserves credits, submits the job)
GET/videos/generations/{id}Status, signed output URLs and the credit settlement
GET/videos/generationsList generations (limit, starting_after)
POST/videos/generations/{id}/cancelCancel a job that has not reached the provider yet; credits are released
GET/creditsSpendable balance and credits reserved by running jobs
POST/webhooks/testSend a signed ping to your webhook endpoints or a URL

Create a generation

Model ids are Seedancee's stable ids (seedance-2.5, seedance-2.0, seedance-2.0-fast, seedance-2.0-mini, seedance-1.5-pro), never provider ids. References are upload ids from POST /uploads or public https URLs, grouped by role: first_frame, last_frame, images, videos and audio. Defaults: mode text-to-video, duration 5 (or "auto" where the model supports it), aspect_ratio 16:9, resolution 720p, generate_audio true, output_format mp4. metadata takes up to 16 string pairs and is echoed back on the job and in webhooks. Send an Idempotency-Key header so a retried request never creates a second job or charge.

POST /api/v1/videos/generations
{
  "model": "seedance-2.5",
  "mode": "image-to-video",
  "prompt": "Slow push-in toward the window; furniture unchanged. Sound: quiet room tone.",
  "duration": 8,
  "aspect_ratio": "16:9",
  "resolution": "720p",
  "generate_audio": true,
  "references": { "first_frame": "<upload id>" },
  "webhook_url": "https://example.com/hooks/seedancee",
  "metadata": { "order_id": "1234" }
}
Response (202 Accepted)
{
  "id": "0b8f6c1e-5d7a-4c33-9f55-2f0d0b1a9c10",
  "object": "video.generation",
  "status": "queued",
  "model": "seedance-2.5",
  "credits_reserved": 600,
  "pricing_version": "2026-09-measured-v2",
  "created_at": "2026-09-30T10:12:00.000Z"
}

Query a job

GET /api/v1/videos/generations/{id}
{
  "id": "0b8f6c1e-5d7a-4c33-9f55-2f0d0b1a9c10",
  "object": "video.generation",
  "status": "succeeded",
  "model": "seedance-2.5",
  "duration": 8,
  "resolution": "720p",
  "credits_reserved": 600,
  "credits_captured": 600,
  "outputs": [
    { "url": "https://…signed URL, valid 1 hour…", "format": "mp4", "duration": 8, "resolution": "720p" }
  ],
  "error": null,
  "metadata": { "order_id": "1234" },
  "completed_at": "2026-09-30T10:15:41.000Z"
}

Status values: queued, processing, succeeded, failed, cancelled. credits_captured is null while the job runs, the final charge on success, and 0 on failure or cancellation (the reservation is released). Output URLs are signed for one hour; fetch the job again for a fresh URL. When you set return_last_frame, last_frame_url links the video's final frame as an image, ready to use as the first frame of the next shot. You only ever see generations of your own workspace.

Two kinds of failure are not always final: if the provider does not confirm a submission (error code submit_unconfirmed) or does not finish in time (timeout), the reservation is released, but the video may still arrive (we accept the provider's late answer, and keep checking timed-out jobs for 24 hours). If it arrives, the job becomes succeeded, is charged then, and a generation.succeeded webhook follows. Do not resubmit such a job right away, or it may render and be charged twice. The create call itself can answer 202 with status failed, credits_reserved 0 and error.code submit_unconfirmed; no generation.failed webhook is sent in that case.

Webhooks

Add up to 10 webhook endpoints in the dashboard (each gets its own whsec_ signing secret, shown once), or pass webhook_url per request, which is signed with your workspace's per-request secret. Events: generation.succeeded, generation.failed and generation.cancelled. The body is { id, type, created, data: { object } } where object is the same JSON as the query endpoint; id stays the same across retries, so use it to deduplicate. Respond with 2xx within 10 seconds. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours; redirects are not followed; only public https URLs are accepted. POST /api/v1/webhooks/test sends a signed ping. If the workspace leaves Pro, generations created before still deliver to enabled endpoints; you can still disable or delete endpoints in the dashboard.

Signature header
Seedancee-Signature: t=1790000000,v1=<64 hex characters>

v1 = hex(HMAC-SHA256(signing_secret, t + "." + raw_request_body))
Verify a webhook (Node)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySeedanceeSignature(rawBody: string, header: string, secret: string, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // reject replays
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

// Use the raw body, not re-serialized JSON:
// const ok = verifySeedanceeSignature(await req.text(), req.headers.get("seedancee-signature")!, process.env.SEEDANCEE_WEBHOOK_SECRET!);
Verify a webhook (Python)
import hashlib, hmac, time

def verify_seedancee_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Examples

cURL
curl -X POST https://seedancee.com/api/v1/videos/generations \
  -H "Authorization: Bearer $SEEDANCEE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: espresso-001" \
  -d '{"model":"seedance-2.0-mini","mode":"text-to-video","prompt":"Vertical video. Espresso pours over ice in a clear glass, crema swirling, hard morning sunlight. Sound: the pour, ice clinking.","duration":5,"aspect_ratio":"9:16","resolution":"720p","generate_audio":true}'

curl https://seedancee.com/api/v1/videos/generations/$GENERATION_ID \
  -H "Authorization: Bearer $SEEDANCEE_API_KEY"
Node (fetch): upload a first frame, create, poll
import { readFile } from "node:fs/promises";

const API = "https://seedancee.com/api/v1";
const auth = { Authorization: `Bearer ${process.env.SEEDANCEE_API_KEY}`, "Content-Type": "application/json" };

// 1. Upload the first frame
const file = await readFile("frame.png");
const upload = await (await fetch(`${API}/uploads`, {
  method: "POST", headers: auth,
  body: JSON.stringify({ type: "image", mime: "image/png", bytes: file.length, filename: "frame.png", model: "seedance-2.5" }),
})).json();
await fetch(upload.upload_url, { method: "PUT", headers: upload.upload_headers, body: file });
let up = await (await fetch(`${API}/uploads/${upload.id}/complete`, { method: "POST", headers: auth, body: "{}" })).json();
while (up.status === "validating") {
  // Videos and audio (and some images) are verified before they can be used
  await new Promise((r) => setTimeout(r, 3_000));
  up = await (await fetch(`${API}/uploads/${upload.id}`, { headers: auth })).json();
}
if (up.status !== "ready") throw new Error(up.error?.message ?? "Upload rejected");

// 2. Create the generation
const job = await (await fetch(`${API}/videos/generations`, {
  method: "POST",
  headers: { ...auth, "Idempotency-Key": "living-room-001" },
  body: JSON.stringify({
    model: "seedance-2.5",
    mode: "image-to-video",
    prompt: "Slow push-in toward the window; furniture unchanged. Sound: quiet room tone.",
    duration: 8,
    resolution: "720p",
    references: { first_frame: upload.id },
  }),
})).json();

// 3. Poll (or use a webhook)
let gen = job;
while (gen.status === "queued" || gen.status === "processing") {
  await new Promise((r) => setTimeout(r, 10_000));
  gen = await (await fetch(`${API}/videos/generations/${job.id}`, { headers: auth })).json();
}
console.log(gen.status, gen.outputs[0]?.url, gen.credits_captured);
Python (requests)
import os, time, requests

API = "https://seedancee.com/api/v1"
headers = {"Authorization": f"Bearer {os.environ['SEEDANCEE_API_KEY']}"}

r = requests.post(
    f"{API}/videos/generations",
    headers={**headers, "Idempotency-Key": "dive-watch-001"},
    json={
        "model": "seedance-2.0",
        "mode": "text-to-video",
        "prompt": "A steel dive watch on wet basalt, slow tilt across the dial as sunlight sweeps the crystal. Sound: a distant wave.",
        "duration": 8,
        "aspect_ratio": "16:9",
        "resolution": "1080p",
        "generate_audio": True,
    },
)
r.raise_for_status()
job = r.json()

while True:
    gen = requests.get(f"{API}/videos/generations/{job['id']}", headers=headers).json()
    if gen["status"] not in ("queued", "processing"):
        break
    time.sleep(10)
print(gen["status"], gen["outputs"], gen["credits_captured"])

Errors

Every error has the same shape, { "error": { "type", "code", "message", "param" } }, where param names the offending field in the request's own snake_case (for example resolution or references.images[1]). Every response carries an X-Request-Id header; include it when you contact support.

Seedancee API error codes
HTTPCodeMeaning
400invalid_json / invalid_parameterMalformed JSON, bad query parameter or Idempotency-Key
401missing_api_key / invalid_api_keyNo key, unknown key, or a revoked or expired key
402insufficient_creditsBalance is lower than the quoted reservation (includes required and balance)
402subscription_requiredThe workspace has no active subscription, so it cannot generate; nothing was reserved
403api_disabled / plan_required / insufficient_scopeThe API is not switched on yet, the workspace is not on Pro (or in early access), or the key lacks the scope
403plan_required (with field)The workspace's plan does not include this model, mode, reference video, resolution or duration (required_tier names the plan that does); nothing was reserved
404not_foundUnknown generation or upload, or it belongs to another workspace
409not_cancellable / idempotency_key_reusedJob already processing, or the key was reused with a different body
413too_large / storage_quota_exceededThe upload is above the size limit, or your workspace storage is full
422content_mismatchThe uploaded bytes are not the declared file type
422missing_parameter / unknown_parameterRequired field missing or an unrecognised field sent
422resolution_unsupported, duration_out_of_range, …The model does not support the requested settings or reference count
422invalid_reference / invalid_webhook_urlReference is not an upload id or https URL; webhook URL is not public https
429rate_limitedPer-key limit, or the per-user limit on creating generations, exceeded; wait Retry-After seconds
502provider_errorThe upstream provider rejected the job; credits were released
503generation_unavailableGeneration is temporarily unavailable; nothing was charged
503tier_unavailableWe could not check the workspace's plan; nothing was charged. Retry after Retry-After seconds

Rate limits and pricing

Each key has its own per-minute limit (20 requests per minute by default, shown in the dashboard, and never more than 120). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; above the limit the API returns 429 with Retry-After. Creating generations is also limited to 10 per minute per user, counted together with generations started in the dashboard; that 429 carries limit_scope user_generations. API generations use exactly the same credit pricing as the workspace: GET /api/v1/models returns each model's credit rates and pricing_version, every create response states credits_reserved, and failed or cancelled jobs release the reservation automatically. Usage per day and per endpoint is shown in the dashboard.

Pricing and credit estimator

The same credit rates apply to API generations.

Pricing and credit estimator

Other programmatic access

The models are also available directly from Kie.ai, whose asynchronous jobs API with callbacks is what Seedancee uses under the hood. Kie's output URLs expire after 14 days, so plan to copy files to your own storage if you integrate with it directly. We link Kie's documentation below for completeness; Seedancee is not affiliated with Kie.ai or ByteDance.

FAQ

Is the Seedance API live on Seedancee?

It is in early access. The /api/v1 endpoints on this page are implemented; while the API is switched off they return 403 api_disabled to everyone. Once it is on, Pro workspaces (and workspaces admitted to early access) create a key under Developer API in the dashboard; other workspaces get 403 plan_required. Request early access through the contact page.

Is there a Seedance 2.0 API today?

Yes. Seedancee's API serves Seedance 2.0 (plus 2.0 Fast and 2.0 Mini), Seedance 2.5 and Seedance 1.5 Pro under stable ids such as seedance-2.0. Kie.ai also exposes hosted APIs for the same models, and ByteDance points developers to its own platform from its official model pages.

How much does the Seedance API cost?

API access is included in the Pro plan. API generations use the same credit pricing as the workspace and draw from the same balance. GET /api/v1/models returns each model's credit rates, and every create response states the credits reserved. See the pricing page for plans and the estimator.

Does the API validate my request before charging?

Yes. Requests are checked against the model registry (mode, resolution, duration, aspect ratio, reference limits) and credits are reserved only for valid requests. Failed or cancelled generations release the reservation automatically.

Does the API support webhooks?

Yes. Configure endpoints in the dashboard or pass webhook_url per request. Deliveries are signed with HMAC-SHA256 in the Seedancee-Signature header and retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. POST /api/v1/webhooks/test sends a signed ping.

How do I avoid duplicate generations when I retry?

Send an Idempotency-Key header on POST /api/v1/videos/generations. Repeating the request with the same key and body returns the original job without a second charge; reusing the key with a different body returns 409.

Does Seedancee expose provider model ids?

No. You use Seedancee's stable model ids such as seedance-2.5, so a provider change does not break your integration.