Skip to main content

Seedance 2.5 Spicy is ByteDance's uncensored image-to-video model, animating a still you supply as the first frame, with sound in the same pass. Clips run 4 to 30 seconds at 480p, 720p or 1080p native, plus an upscaled 4K tier.

From $0.216/s

Workbench

Send a request

Quickstart

Examples use the selected endpoint’s public parameter definitions. Replace the API key and example media, then send the request from your server.

SDKs & integrations
POSThttps://api.spicyapi.ai/api/v1/jobs/createTask

API authentication

Keep the API key in the server environment variable SPICY_API_KEY. Use Bearer authentication and Content-Type: application/json for JSON requests.

Authorization: Bearer $SPICY_API_KEY
Sample outputUse this example
# Generate the idempotency key once; reuse it when retrying after a lost response.
export SPICY_IDEMPOTENCY_KEY="${SPICY_IDEMPOTENCY_KEY:-$(uuidgen)}"
curl -X POST https://api.spicyapi.ai/api/v1/jobs/createTask \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  -d '{
  "model": "bytedance/seedance-2.5-spicy/image-to-video",
  "input": {
    "duration_seconds": 5,
    "generate_audio": true,
    "image_url": "https://example.com/reference.png",
    "resolution": "720p"
  }
}'

# Copy data.taskId from the response into TASK_ID, then poll the status.
TASK_ID="TASK_ID_FROM_RESPONSE"
curl "https://api.spicyapi.ai/api/v1/jobs/recordInfo?taskId=$TASK_ID" \
  -H "Authorization: Bearer $SPICY_API_KEY"

Generated from this model’s input schema — the same one the API validates every request against.

Input parameters

Place these fields inside the request body’s input object. Names and types match the API. Do not assume a default when none is specified.

image_urlstringRequired

Required, and the only required field. It becomes the first frame, so composition, likeness, wardrobe, grade and the quality ceiling all come from it — a sharp, well-lit still beats any amount of prompt patching afterwards. It is also the reason this endpoint handles uncensored material dependably: content that is already in the picture does not have to be argued for in words. Each side must be between 300 and 6000 pixels; a smaller plate is rejected by the input check before you are charged.

Default
Not specified
Constraints
contentMediaType:["image/jpeg","image/png","image/webp","image/gif"]
seedintegerOptional

Integer seed; omit it and each run draws its own. Fix it with everything else held constant to judge what a prompt edit actually changed. It is a comparison tool, not a promise of frame-identical repeats.

Default
Not specified
Constraints
minimum:0
maximum:2147483647
generate_audiobooleanOptional

Whether a native soundtrack is rendered in sync with the picture. On by default. Switch it off for silent loops, for material you will score in post, and whenever you are only checking framing.

Default
true
duration_secondsintegerOptionalAffects price

Length of the finished clip in whole seconds, 4 to 30, default 5, and the billed quantity — the charge scales directly with it. Thirty seconds is a lot to ask of one opening frame: confirm the motion short, then extend only what holds together.

Default
5
Constraints
minimum:4
maximum:30
resolutionstringOptionalAffects price

Output resolution: 480p, 720p by default, 1080p or 4k. ByteDance's own Seedance 2.5 renders natively at most in 1080p, so the 4k tier here is delivered by an extra upscaling pass on top of that native render — it is upscaled output, not native 4K, and the extra pass makes the task slower than 1080p at the same length. Resolution selects a rate tier and governs the delivered format only — it adds no detail the first frame does not already have. Higher tiers mean bigger files and longer renders.

Default
"720p"
Allowed values
"480p""720p""1080p""4k"
last_image_urlstringOptional

Optional closing frame. It decides the picture the shot lands on: a transition, a before-and-after pair, or a loop that opens and closes on the same still. Keep camera and light steady between the two and change one clear state, or the middle of the clip turns into a cut. Each side must be between 300 and 6000 pixels; a smaller plate is rejected by the input check before you are charged.

Default
Not specified
Constraints
contentMediaType:["image/jpeg","image/png","image/webp","image/gif"]
promptstringOptional

Optional here, and that is the point: the opening frame already fixes what is on screen, so the prompt only has to direct movement — the action, where the eyes go, the camera drift, the room sound. Briefs are not reviewed on our side, but Seedance's own layer refuses a narrow band of wording, kissing most consistently. Put that beat in the frame instead of the sentence.

Default
Not specified
Constraints
maxLength:5000

Track the task

HTTP 202 means the task was accepted, not completed. Save the returned taskId and query that task. Do not create another task while waiting for its result.

Accepted response
HTTP/1.1 202 Accepted
Location: /api/v1/jobs/recordInfo?taskId=job_01k3m8x9q2z4v7n5p6r8s0t1w3

{
  "code": 200,
  "msg": "success",
  "request_id": "req_example_create",
  "data": {
    "taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "state": "queued",
    "estimatedCost": "0.750000"
  }
}

The accepted response includes the estimate so your app can show the same amount in task history and billing.

Read the job
curl --get "https://api.spicyapi.ai/api/v1/jobs/recordInfo" \
  --data-urlencode "taskId=$TASK_ID" \
  -H "Authorization: Bearer $SPICY_API_KEY"

Start polling at about 2 seconds and gradually increase the interval. Stop at a terminal state. After a connection failure, query the original taskId. Retry creation only with the original idempotency key and body.

Or let the callback find you
import { verifyWebhook } from "@spicyapi/sdk";

// express.raw keeps the bytes we signed; express.json never matches.
app.post("/hooks/spicy", express.raw({ type: "application/json" }), (req, res) => {
  const { payload } = verifyWebhook({
    rawBody: req.body,
    timestamp: req.header("X-Webhook-Timestamp"),
    signature: req.header("X-Webhook-Signature"),
    payloadVersion: Number(req.header("X-Webhook-Payload-Version")),
    secret: process.env.SPICY_WEBHOOK_SECRET,
  });
  res.status(200).end();
  void handleFinishedTask(payload);
});

Pass callBackUrl when you create the task and the finished record is POSTed to you — no polling loop at all. Every delivery carries X-Webhook-Timestamp, X-Webhook-Signature and X-Webhook-Payload-Version. The signature is a base64 HMAC-SHA256 over taskId.timestamp.sha256(body), so it must be checked against the raw bytes: parsing the JSON and re-serialising it changes them and the check will fail. Failed deliveries are retried, so the same taskId can arrive twice — make the handler idempotent. Answer 2xx first and do the work afterwards; a slow handler is treated as a failed delivery.

Read the result

Consume output only when state is succeeded. On failure, use errorCode, errorMessage and request_id to investigate. Example costs and states do not represent an actual bill.

Completed response example
{
  "code": 200,
  "msg": "success",
  "request_id": "req_example_record",
  "data": {
    "taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "model": "bytedance/seedance-2.5-spicy/image-to-video",
    "state": "succeeded",
    "output": {
      "assets": [
        {
          "key": "tasks/2026/08/28/job_01k3m8x9q2z4v7n5p6r8s0t1w3/3f7a1c9e5b2d48f0a6c1e3d5b7f9a1c3.mp4",
          "mime": "video/mp4",
          "url": "https://example.r2.cloudflarestorage.com/results/job_01k3m8x9q2z4v7n5p6r8s0t1w3/result.mp4?X-Amz-Signature=SIGNATURE_FROM_RESPONSE",
          "expiresAt": "2026-08-28T12:20:08.000Z"
        }
      ]
    },
    "cost": "0.750000",
    "settled": true,
    "createdAt": "2026-08-28T12:00:00Z",
    "completedAt": "2026-08-28T12:00:30Z"
  }
}

For each available asset, open or download assets[].url directly without an API Authorization header. The signed URL expires at expiresAt; poll the task again to refresh it while the asset remains within its retention period. A pending or unavailable asset has no URL. Treat signed URLs as private.

SDKs & integrations

Use SpicyAPI in your app, terminal, AI assistant or visual workflow. Choose a setup guide below.

Use it in ComfyUI

Install the SpicyAPI nodes from ComfyUI Manager or with the command below, then search for Seedance 2.5 Spicy · Image to Video on the canvas. The node has the same options as this page and shows its price before it runs.

cd ComfyUI/custom_nodes
git clone https://github.com/spicyapi-ai/comfyui-spicyapi