Vellm
Start creating

Seedance 2.5 API: Turn a Photo into a Video in Code

By Vellm ·

If you want one video, Vellm is the quick route: pick a template, add your photo and you're done. If you're building your own app and want photo-to-video inside it, you need an API. This guide walks through calling Seedance 2.5 through VizoAPI, from the first request to a downloaded MP4.

What you can make

Everything goes through one endpoint, POST /v1/generations. You put the prompt and your media in a single list, input.content, and give every image, video or audio item a role. The items you send decide the mode:

You want toSend in content
Make a video from a promptA text item
Bring a photo to lifeA prompt and one image with role first_frame
Move from one picture to anotherA first_frame and a last_frame image
Reuse people, objects or a styleA prompt and up to 30 images with role reference_image
Copy the motion or rhythm of a clipReference videos and audio, with omni_reference_task_type set to reference
Change or lengthen an existing clipThe source video, with omni_reference_task_type set to edit or extend

Videos run 4 to 30 seconds at 480p, 720p or 1080p. They come with a soundtrack unless you set generate_audio to false. The Seedance 2.5 model page has a complete request body for every mode.

Before you start

Create an account, buy Credits and make a project key in the VizoAPI console. Keep the key on your server. It never belongs in a web page, a mobile app or a repository.

Your photo has to be a public https URL or a file you uploaded to your project. The model fetches it after you submit, sometimes a while later. Keep the link working until the task finishes. Otherwise the task fails with input_media_unreachable.

Check the price before you spend

Send your request body to POST /v1/estimates first. It's free: it creates no task and holds no Credits. It also checks your input the same way a real request would, so a broken body fails here instead of later.

const api = "https://api.vizoapi.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.VIZO_API_KEY}`,
  "Content-Type": "application/json",
};

const body = {
  model: "seedance-2-5",
  input: {
    content: [
      { type: "text", text: "The dog tilts its head, then trots toward the camera, slow push-in, soft daylight" },
      { type: "image_url", role: "first_frame", image_url: { url: "https://example.com/dog.jpg" } },
    ],
    resolution: "720p",
    ratio: "adaptive", // a first-frame video takes the photo's shape
    duration: 5,
  },
  metadata: { client_reference: "order-1042" }, // your own order number
};

const estimate = await fetch(`${api}/estimates`, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => r.json());
console.log(estimate.estimate.credits); // "283.000"

Submit once, then wait

Send the same body to POST /v1/generations. A 202 response means the task was accepted. Save its id right away.

// Send once: switch off automatic retries for this request
const task = await fetch(`${api}/generations`, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => r.json());
const taskId = task.id;

let result;
do {
  await new Promise((r) => setTimeout(r, 5000));
  result = await fetch(`${api}/generations/${taskId}`, { headers }).then((r) => r.json());
} while (!result.closed_at);

if (result.status !== "succeeded") throw new Error(`task closed: ${result.status} ${result.error?.code}`);

Polling every few seconds is fine for a script. In production, register a webhook endpoint in the console and pass webhook_endpoint_id when you submit.

Download the video

Ask for a short-lived download link, then fetch that link on its own, without your API key:

import { writeFile } from "node:fs/promises";

const dl = await fetch(`${api}/assets/${result.result.asset_id}/download`, { headers }).then((r) => r.json());
const media = await fetch(dl.url);
await writeFile("result.mp4", Buffer.from(await media.arrayBuffer()));

Finished files are kept for 7 days. If a link expires, request a new one for the same asset.

Don't pay twice

Every accepted POST /v1/generations is a new task with its own charge, even when the body is identical. That's why the example sets metadata.client_reference. If your request times out, don't just send it again. Look the task up first: read it by ID if you have one, or find it with GET /v1/generations?client_reference=order-1042. Also make sure your HTTP client and any proxy don't retry the request on their own.

What it costs

You pay in Credits. The standard pack is $10 for 2,000 Credits, so one Credit is half a cent. Seedance 2.5 is billed per second of video:

ResolutionCredits per secondAbout
480p26.4$0.13 a second
720p56.6$0.28 a second
1080p136.4$0.68 a second

That's about 40% less than fal per second, and less than many other video APIs. A 5-second photo-to-video clip at 720p holds 283 Credits, about $1.42. Requests that include a reference video use cheaper per-second tiers, but they also count the input video's seconds. A task that fails, times out or gets blocked by content moderation costs nothing. These are the prices on 11 November 2026; the model page always has the current ones.

Tips for better results

Rather not write code?

If you just want a trend video of yourself or your pet, you don't need an API. Browse the Vellm templates, or start from your own idea in Create.