API Get key

MiniMaxVideo

Hailuo 2.3

Animates your photo into 6 or 10 s clips.

Price
23 crper clip · standard · 6 s · 768p
Usual time
≈ 2 min
Model id
video.hailuo-2-3

Playground

Playground

First frame *

Sign in to upload a file.

Mode
Duration (s)
Resolution
Sign up to run it

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

video.hailuo-2-3
Length
6 s, 10 s
Quality
768p, 1080p
Mode
standard, pro
Takes
Text, Image

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.hailuo-2-3",
    "params": {
      "prompt": "A premium product shot of a glass serum bottle on wet stone, soft window light",
      "first_frame": "med_your_upload_id",
      "mode": "standard",
      "duration": 6,
      "resolution": "768p"
    },
    "max_credits": 23
  }' | 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.hailuo-2-3",
                    "params": {
                      "prompt": "A premium product shot of a glass serum bottle on wet stone, soft window light",
                      "first_frame": "med_your_upload_id",
                      "mode": "standard",
                      "duration": 6,
                      "resolution": "768p"
                    },
                    "max_credits": 23
                  })
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.hailuo-2-3",
    "params": {
      "prompt": "A premium product shot of a glass serum bottle on wet stone, soft window light",
      "first_frame": "med_your_upload_id",
      "mode": "standard",
      "duration": 6,
      "resolution": "768p"
    },
    "max_credits": 23
  }),
});
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.hailuo-2-3",
  "status": "queued",
  "credits": 23,
  "title": "",
  "params": {
    "prompt": "A premium product shot of a glass serum bottle on wet stone, soft window light",
    "first_frame": "med_your_upload_id",
    "mode": "standard",
    "duration": 6,
    "resolution": "768p"
  },
  "progress": null,
  "outputs": [],
  "data": null,
  "error": null,
  "run_id": null,
  "source": "api",
  "created_at": "2026-11-06T12:00:00Z",
  "finished_at": null,
  "eta_seconds": 150
}

What it costs

Duration (s)standardpro
6 s23 cr34 cr
10 s38 cr68 cr

Resolution also changes the price. 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
promptrequiredDescription string Up to 5,000 characters
first_framerequiredFirst frame media id Image
modeMode enum standard standard, pro
durationDuration (s) enum 6 6, 10
resolutionResolution enum 768p 768p, 1080p

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

More models