Skip to content

Image-to-video

Animate a still image into an MP4 by passing it as the first frame of a video-generation task. This page walks through a runnable Python script plus the parameters, routes, and error cases that matter for image-to-video.

  • pip install requests
  • MIRAPI_API_KEY exported as an environment variable
  • A video-capable model enabled for your key
  • A first-frame image reachable as an HTTP(S) URL or base64 data URI

Video generation is asynchronous: the API returns a task right away, and you poll until the clip is ready.

  1. POST /v1/video/generations — submit the job and receive a task_id.
  2. GET /v1/video/generations/{task_id} — poll until status is completed or failed.
  3. Download the MP4 from the url in the completed task.

The image field is what makes a request image-to-video: the model uses it as the first frame and keeps the composition, so you describe the motion in prompt instead of re-describing the scene.

import base64
import os
import time
import requests
BASE = "https://api.mirapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}
POLL_INTERVAL = 10
DEADLINE_SECONDS = 600
def image_to_data_uri(path: str) -> str:
mime = "image/png" if path.endswith(".png") else "image/jpeg"
with open(path, "rb") as f:
return f"data:{mime};base64,{base64.b64encode(f.read()).decode()}"
# Pass an HTTP(S) URL, or convert a local file to a data URI:
first_frame = "https://example.com/first-frame.png"
# first_frame = image_to_data_uri("first-frame.png")
resp = requests.post(
f"{BASE}/video/generations",
headers=HEADERS,
json={
"model": "deepseek-chat", # replace with a video model id
"prompt": "The photo comes to life; the person waves at the camera",
"image": first_frame,
"duration": 5,
},
timeout=60,
)
resp.raise_for_status()
task_id = resp.json()["task_id"]
deadline = time.time() + DEADLINE_SECONDS
while time.time() < deadline:
task = requests.get(
f"{BASE}/video/generations/{task_id}", headers=HEADERS, timeout=30
).json()
if task["status"] == "completed":
break
if task["status"] == "failed":
raise SystemExit(f"Video failed: {task.get('error')}")
time.sleep(POLL_INTERVAL)
else:
raise TimeoutError(f"Task {task_id} did not finish within {DEADLINE_SECONDS}s")
video = requests.get(task["url"], timeout=60)
video.raise_for_status()
with open("animated.mp4", "wb") as f:
f.write(video.content)
print("Saved animated.mp4")

The script submits once, then polls every 10 seconds with a 10-minute deadline. It exits on a failed status, raises on timeout, and otherwise downloads the completed task’s url.

  • Hosted images take an HTTP(S) URL; local files become a base64 data URI — both are accepted.
  • Describe the motion in prompt, not the scene. The model already has the first frame’s composition.
  • duration is billed output; start with 5 seconds and extend only if the model supports longer clips.
  • Make sure the image URL is directly downloadable — a plain image response, not an HTML page, redirect wall, or login page.

The unified and Kling routes accept the same JSON body:

Parameter Type Required Description
model string yes Video-capable model id from the catalogue.
prompt string yes Text description of the motion to animate.
image string yes First-frame image: HTTP(S) URL or base64 data URI.
duration number no Target length in seconds.
width integer no Output width in pixels.
height integer no Output height in pixels.
fps integer no Frame rate.
seed integer no Seed for reproducible output (not guaranteed by every model).
n integer no Number of videos to generate.
response_format string no Response format (e.g. url).
user string no End-user identifier for abuse tracking.
metadata object no Model-specific extras (negative_prompt, style, quality_level, …) where the model accepts them.

image is the field that turns the request into image-to-video; omit it and the same route behaves as text-to-video.

status is one of queued, in_progress, completed, failed. A completed task carries the video url, format (e.g. mp4), and a metadata object with the actual duration, width, height, and fps; a failed task includes an error.

{
"task_id": "abcd1234efgh",
"status": "completed",
"url": "https://…",
"format": "mp4",
"metadata": { "duration": 5, "width": 1280, "height": 720 }
}

Poll every 10–30 seconds — a job can take minutes, depending on the model and resolution.

Video generation is OpenAI-compatible only — there is no Anthropic or Gemini protocol variant. MirAPI exposes four route families that all follow submit → poll → download, but differ in request format and response shape.

  • POST /v1/video/generations — JSON body (the request parameters above). Returns task_id and status.
  • GET /v1/video/generations/{task_id} — status; a completed task carries url, format, and metadata.
  • POST /v1/videosmultipart/form-data with model, prompt, seconds (a string), and an input_reference image file. Returns an id (not task_id).
  • GET /v1/videos/{task_id} — task status, including progress and created_at.
  • GET /v1/videos/{task_id}/content — the finished file, returned as video/mp4.
  • POST /kling/v1/videos/image2video — the same JSON body as the unified route; takes image for the first frame.
  • GET /kling/v1/videos/image2video/{task_id} — poll status. The text2video route (/kling/v1/videos/text2video) shares this shape without image.
  • POST /jimeng/ — the native Jimeng API. Action and Version are query parameters: Action is CVSync2AsyncSubmitTask (submit) or CVSync2AsyncGetResult (fetch result), and Version is the API version (e.g. 2022-08-31). The JSON body carries req_key, prompt, and an optional binary_data_base64 image array.
Status Meaning What to do
400 Invalid parameters Check the model id, and that duration/width/height are numeric.
401 Missing or invalid key Verify Authorization: Bearer.
403 Key valid but rejected Top up (empty balance), or check the model whitelist, quota, and IP allowlist.
404 Task or content not found The task does not exist, or the video is not completed yet.
429 Rate limited Back off exponentially (1s → 2s → 4s, cap ~30s); there is no Retry-After header.
500 Gateway or upstream failure Retry idempotent requests safely.

Every error message ends with a request ID — include it in support tickets.

  • Task stays queued or in_progress — normal; jobs can take minutes. Keep polling at a steady interval.
  • Generation failed — read the error field, confirm the model has video output, and check that the reference image is reachable and in a supported format.
  • 404 on the content endpoint — the task is not completed yet; poll the status endpoint first.

Video is billed per second of generated output, settled when the job completes. Failed jobs produce no output and are not charged.

  • Balance exhausted → the request fails with 403; top up in the console and the same key recovers immediately.
  • Reconcile spend in the console billing log or with GET /api/log/token (per-request detail).