API Get key

Alibaba CloudVideo

Wan 2.2 Animate

Moves your character with a video's motion (move) or puts it in place of the person in the video (replace).

Price
90 crper clip · move · 480p · varies with length or size
Usual time
≈ 4 min
Model id
video.wan-animate

Playground

Playground

Image *

Sign in to upload a file.

Video *

Sign in to upload a file.

Mode
Resolution
Sign up to run it

Change the settings: the price and the code follow. You need an account to run it.

video.wan-animate
Quality
480p, 580p, 720p
Mode
move, replace
Takes
Image, Video

No example of this model yet. These are its real settings.

# 0. Upload your file first and put the id it prints in params.
curl -s -X POST https://mobyvid.com/api/v1/uploads \
  -H "Authorization: Bearer $STAGLIGHT_API_KEY" -F file=@your-file | jq -r .id

# 1. Create the job. It is charged once and refunded if it fails.
JOB_ID=$(curl -s -X POST https://mobyvid.com/api/v1/jobs \
  -H "Authorization: Bearer $STAGLIGHT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "op": "video.wan-animate",
    "params": {
      "image": "med_your_upload_id",
      "video": "med_your_upload_id",
      "own_footage": true,
      "mode": "move",
      "resolution": "480p"
    }
  }' | jq -r .id)

# 2. Poll until it finishes. Each call waits up to 30 seconds.
while :; do
  JOB=$(curl -s "https://mobyvid.com/api/v1/jobs/$JOB_ID?wait=30" -H "Authorization: Bearer $STAGLIGHT_API_KEY")
  case "$(echo "$JOB" | jq -r .status)" in queued|running) ;; *) break ;; esac
done

# 3. Download the result from outputs[0].download_url.
DOWNLOAD_URL=$(echo "$JOB" | jq -r '.outputs[0].download_url')
curl -L -o result.mp4 "$DOWNLOAD_URL"
import os, uuid, requests

BASE = "https://mobyvid.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['STAGLIGHT_API_KEY']}"}

# 0. Upload your file first and put the id it prints in params.
with open("your-file", "rb") as f:
    print(requests.post(f"{BASE}/uploads", headers=HEADERS, files={"file": f}, timeout=300).json()["id"])

# 1. Create the job. It is charged once and refunded if it fails.
r = requests.post(f"{BASE}/jobs", timeout=60,
                  headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
                  json={
                    "op": "video.wan-animate",
                    "params": {
                      "image": "med_your_upload_id",
                      "video": "med_your_upload_id",
                      "own_footage": True,
                      "mode": "move",
                      "resolution": "480p"
                    }
                  })
r.raise_for_status()
job = r.json()

# 2. Poll until it finishes. Each call waits up to 30 seconds.
while job["status"] in ("queued", "running"):
    job = requests.get(f"{BASE}/jobs/{job['id']}", headers=HEADERS,
                       params={"wait": 30}, timeout=60).json()

# 3. Download the result from outputs[0].download_url.
print(job["outputs"][0]["download_url"] if job["outputs"] else job["data"] or job["error"])
// Node 18 or newer. Save as run.mjs: top-level await needs a module.
const BASE = "https://mobyvid.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.STAGLIGHT_API_KEY}` };

// 0. Upload your file first and put the id it prints in params.
// const form = new FormData(); form.append('file', yourFile);
// const { id } = await (await fetch(`${BASE}/uploads`, { method: 'POST', headers, body: form })).json();

// 1. Create the job. It is charged once and refunded if it fails.
let res = await fetch(`${BASE}/jobs`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({
    "op": "video.wan-animate",
    "params": {
      "image": "med_your_upload_id",
      "video": "med_your_upload_id",
      "own_footage": true,
      "mode": "move",
      "resolution": "480p"
    }
  }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
let job = await res.json();

// 2. Poll until it finishes. Each call waits up to 30 seconds.
while (job.status === "queued" || job.status === "running") {
  job = await (await fetch(`${BASE}/jobs/${job.id}?wait=30`, { headers })).json();
}

// 3. Download the result from outputs[0].download_url.
console.log(job.outputs[0]?.download_url ?? job.data ?? job.error);
{
  "id": "job_19a2b3c4d5e6f7a8b9c0d1",
  "op": "video.wan-animate",
  "status": "queued",
  "credits": 90,
  "title": "",
  "params": {
    "image": "med_your_upload_id",
    "video": "med_your_upload_id",
    "own_footage": true,
    "mode": "move",
    "resolution": "480p"
  },
  "progress": null,
  "outputs": [],
  "data": null,
  "error": null,
  "run_id": null,
  "source": "api",
  "created_at": "2026-11-06T12:00:00Z",
  "finished_at": null,
  "eta_seconds": 270
}

What it costs

ResolutionPrice
480p90 cr
580p135 cr
720p180 cr

These prices are at a default length: the price follows the length of your text or file, and the exact price shows before the job runs. The playground shows the exact price for your settings. A job is charged once when it is accepted, and refunded if it fails. The price is checked again with your file before it runs.

Parameters

NameTypeDefaultAllowed
imagerequiredImage media id Image
videorequiredVideo media id Video
own_footagerequiredI have the right to use this clip and these images boolean
modeMode enum move move, replace
resolutionResolution enum 480p 480p, 580p, 720p

Send these inside params. Files are media ids from POST /api/v1/uploads. View documentation

More models